Mercurial > hg > xemacs-beta
comparison 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 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:376386a54a3c |
---|---|
1 ;;!emacs | |
2 ;; | |
3 ;; FILE: hpath.el | |
4 ;; SUMMARY: Hyperbole support routines for handling UNIX paths. | |
5 ;; USAGE: GNU Emacs Lisp Library | |
6 ;; KEYWORDS: comm, hypermedia, unix | |
7 ;; | |
8 ;; AUTHOR: Bob Weiner | |
9 ;; ORG: Brown U. | |
10 ;; | |
11 ;; ORIG-DATE: 1-Nov-91 at 00:44:23 | |
12 ;; LAST-MOD: 10-Oct-95 at 23:31:56 by Bob Weiner | |
13 ;; | |
14 ;; This file is part of Hyperbole. | |
15 ;; Available for use and distribution under the same terms as GNU Emacs. | |
16 ;; | |
17 ;; Copyright (C) 1991-1995, Free Software Foundation, Inc. | |
18 ;; Developed with support from Motorola Inc. | |
19 ;; | |
20 ;; DESCRIPTION: | |
21 ;; DESCRIP-END. | |
22 | |
23 ;;; ************************************************************************ | |
24 ;;; Public variables | |
25 ;;; ************************************************************************ | |
26 | |
27 (defvar hpath:rfc "/anonymous@ds.internic.net:rfc/rfc%s.txt" | |
28 "*String to be used in the call: (hpath:rfc rfc-num) | |
29 to create an path to the RFC document for 'rfc-num'.") | |
30 | |
31 (defvar hpath:suffixes '(".gz" ".Z") | |
32 "*List of filename suffixes to add or remove within (hpath:exists-p) calls.") | |
33 | |
34 (defvar hpath:tmp-prefix "/tmp/remote-" | |
35 "*Pathname prefix to attach to remote files copied locally for use with external viewers.") | |
36 | |
37 ;;; ************************************************************************ | |
38 ;;; Public functions | |
39 ;;; ************************************************************************ | |
40 | |
41 (defun hpath:absolute-to (path &optional default-dirs) | |
42 "Returns PATH as an absolute path relative to one directory from optional DEFAULT-DIRS or 'default-directory'. | |
43 Returns PATH unchanged when it is not a valid path or when DEFAULT-DIRS | |
44 is invalid. DEFAULT-DIRS when non-nil may be a single directory or a list of | |
45 directories. The first one in which PATH is found is used." | |
46 (if (not (and (stringp path) (hpath:is-p path nil t))) | |
47 path | |
48 (if (not (cond ((null default-dirs) | |
49 (setq default-dirs (cons default-directory nil))) | |
50 ((stringp default-dirs) | |
51 (setq default-dirs (cons default-dirs nil))) | |
52 ((listp default-dirs)) | |
53 (t nil))) | |
54 path | |
55 (let ((rtn) dir) | |
56 (while (and default-dirs (null rtn)) | |
57 (setq dir (expand-file-name | |
58 (file-name-as-directory (car default-dirs))) | |
59 rtn (expand-file-name path dir) | |
60 default-dirs (cdr default-dirs)) | |
61 (or (file-exists-p rtn) (setq rtn nil))) | |
62 (or rtn path))))) | |
63 | |
64 (defun hpath:ange-ftp-at-p () | |
65 "Returns an ange-ftp pathname that point is within or nil. | |
66 See the 'ange-ftp' or 'efs' Elisp packages for pathname format details. | |
67 Always returns nil if (hpath:ange-ftp-available-p) returns nil." | |
68 (if (hpath:ange-ftp-available-p) | |
69 (let ((user (hpath:ange-ftp-default-user)) | |
70 path) | |
71 (setq path | |
72 (save-excursion | |
73 (skip-chars-backward "^[ \t\n\"`'\(\{<") | |
74 (cond | |
75 ((hpath:url-at-p) | |
76 (if (string-equal | |
77 (buffer-substring (match-beginning 1) (match-end 1)) | |
78 "ftp") | |
79 (concat | |
80 "/" | |
81 ;; user | |
82 (if (match-beginning 2) | |
83 (buffer-substring | |
84 (match-beginning 2) (match-end 2)) | |
85 (concat user "@")) | |
86 ;; domain | |
87 (hpath:delete-trailer | |
88 (buffer-substring (match-beginning 3) (match-end 3))) | |
89 ":" | |
90 ;; path | |
91 (if (match-beginning 5) | |
92 (buffer-substring (match-beginning 5) | |
93 (match-end 5)))) | |
94 ;; else ignore this other type of WWW path | |
95 )) | |
96 ;; user, domain and path | |
97 ((looking-at "/?[^/:@ \t\n\^M\"`']+@[^/:@ \t\n\^M\"`']+:[^]@ \t\n\^M\"`'\)\}]*") | |
98 (buffer-substring (match-beginning 0) (match-end 0))) | |
99 ;; @domain and path | |
100 ((looking-at "@[^/:@ \t\n\^M\"`']+:[^]@ \t\n\^M\"`'\)\}]*") | |
101 (concat "/" user (buffer-substring | |
102 (match-beginning 0) (match-end 0)))) | |
103 ;; domain and path | |
104 ((and (looking-at | |
105 "/?\\(\\([^/:@ \t\n\^M\"`']+\\):[^]@:, \t\n\^M\"`'\)\}]*\\)[] \t\n\^M,.\"`'\)\}]") | |
106 (setq path (buffer-substring | |
107 (match-beginning 1) (match-end 1))) | |
108 (string-match "[^.]\\.[^.]" | |
109 (buffer-substring (match-beginning 2) | |
110 (match-end 2)))) | |
111 (concat "/" user "@" path)) | |
112 ;; host and path | |
113 ((and (looking-at | |
114 "/\\([^/:@ \t\n\^M\"`']+:[^]@:, \t\n\^M\"`'\)\}]*\\)") | |
115 (setq path (buffer-substring | |
116 (match-beginning 1) (match-end 1)))) | |
117 (concat "/" user "@" path)) | |
118 ))) | |
119 (hpath:delete-trailer path)))) | |
120 | |
121 (defun hpath:ange-ftp-p (path) | |
122 "Returns non-nil iff PATH is an ange-ftp pathname. | |
123 See the 'ange-ftp' or 'efs' Elisp package for pathname format details. | |
124 Always returns nil if (hpath:ange-ftp-available-p) returns nil." | |
125 (and (stringp path) | |
126 (or (featurep 'ange-ftp) (featurep 'efs)) | |
127 (let ((user (hpath:ange-ftp-default-user)) | |
128 result) | |
129 (setq result | |
130 (cond | |
131 ((hpath:url-p path) | |
132 (if (string-equal | |
133 (substring path (match-beginning 1) (match-end 1)) | |
134 "ftp") | |
135 (concat | |
136 "/" | |
137 ;; user | |
138 (if (match-beginning 2) | |
139 (substring path (match-beginning 2) (match-end 2)) | |
140 (concat user "@")) | |
141 ;; domain | |
142 (hpath:delete-trailer | |
143 (substring path (match-beginning 3) (match-end 3))) | |
144 ":" | |
145 ;; path | |
146 (if (match-beginning 5) | |
147 (substring path (match-beginning 5) | |
148 (match-end 5)))) | |
149 ;; else ignore this other type of WWW path | |
150 )) | |
151 ;; user, domain and path | |
152 ((string-match "/?[^/:@ \t\n\^M\"`']+@[^/:@ \t\n\^M\"`']+:[^]@ \t\n\^M\"`'\)\}]*" | |
153 path) | |
154 (substring path (match-beginning 0) (match-end 0))) | |
155 ;; @domain and path | |
156 ((string-match "@[^/:@ \t\n\^M\"`']+:[^]@ \t\n\^M\"`'\)\}]*" | |
157 path) | |
158 (concat "/" user | |
159 (substring path (match-beginning 0) (match-end 0)))) | |
160 ;; domain and path | |
161 ((and (string-match | |
162 "/?\\(\\([^/:@ \t\n\^M\"`']+\\):[^]@:, \t\n\^M\"`'\)\}]*\\)" | |
163 path) | |
164 (setq result (substring path | |
165 (match-beginning 1) (match-end 1))) | |
166 (string-match "[^.]\\.[^.]" | |
167 (substring path (match-beginning 2) | |
168 (match-end 2)))) | |
169 (concat "/" user "@" result)) | |
170 ;; host and path | |
171 ((and (string-match | |
172 "/\\([^/:@ \t\n\^M\"`']+:[^]@:, \t\n\^M\"`'\)\}]*\\)" | |
173 path) | |
174 (setq result (substring | |
175 path | |
176 (match-beginning 1) (match-end 1)))) | |
177 (concat "/" user "@" result)) | |
178 )) | |
179 (hpath:delete-trailer result)))) | |
180 | |
181 (defun hpath:at-p (&optional type non-exist) | |
182 "Returns delimited path or non-delimited ange-ftp path at point, if any. | |
183 World wide web urls are treated as non-paths so they are handled elsewhere. | |
184 Delimiters may be: double quotes, open and close single quote, or | |
185 Texinfo file references. | |
186 If optional TYPE is the symbol 'file or 'directory, then only that path type is | |
187 accepted as a match. Only locally reachable paths are checked for existence. | |
188 With optional NON-EXIST, nonexistent local paths are allowed. | |
189 Absolute pathnames must begin with a '/' or '~'. Relative pathnames | |
190 must begin with a './' or '../' to be recognized." | |
191 (cond (;; Local file URLs | |
192 (hpath:is-p (hargs:delimited | |
193 "file://localhost" "[ \t\n\^M\"\'\}]" nil t))) | |
194 ((hpath:ange-ftp-at-p)) | |
195 ((hpath:www-at-p) nil) | |
196 ((hpath:is-p (or (hargs:delimited "\"" "\"") | |
197 ;; Filenames in Info docs | |
198 (hargs:delimited "\`" "\'") | |
199 ;; Filenames in TexInfo docs | |
200 (hargs:delimited "@file{" "}")) | |
201 type non-exist)))) | |
202 | |
203 (defun hpath:find (filename &optional other-window-p) | |
204 "Edit file FILENAME using program from hpath:find-alist if available. | |
205 Otherwise, switch to a buffer visiting file FILENAME, creating one if none | |
206 already exists. | |
207 | |
208 FILENAME may start with a special prefix character which is | |
209 handled as follows: | |
210 !filename - execute as a non-windowed program within a shell; | |
211 &filename - execute as a windowed program; | |
212 -filename - load as an Emacs Lisp program. | |
213 | |
214 Return non-nil iff file is displayed within a buffer (not with an external | |
215 program)." | |
216 (interactive "FFind file: ") | |
217 (let (modifier) | |
218 (if (string-match hpath:prefix-regexp filename) | |
219 (setq modifier (aref filename 0) | |
220 filename (substring filename (match-end 0)))) | |
221 (setq filename (hpath:substitute-value filename)) | |
222 (cond (modifier (cond ((eq modifier ?!) | |
223 (hact 'exec-shell-cmd filename)) | |
224 ((eq modifier ?&) | |
225 (hact 'exec-window-cmd filename)) | |
226 ((eq modifier ?-) | |
227 (load filename))) | |
228 nil) | |
229 (t (let ((find-program (hpath:find-program filename))) | |
230 (cond ((stringp find-program) | |
231 (hact 'exec-window-cmd find-program) | |
232 nil) | |
233 ((hypb:functionp find-program) | |
234 (funcall find-program filename) | |
235 nil) | |
236 (t (setq filename (hpath:validate filename)) | |
237 (funcall (if (and other-window-p | |
238 (not (br-in-browser))) | |
239 'switch-to-buffer-other-window | |
240 'switch-to-buffer) | |
241 (find-file-noselect filename)) | |
242 t))))))) | |
243 | |
244 (defun hpath:find-other-window (filename) | |
245 "Edit file FILENAME, in another window or using program from hpath:find-alist. | |
246 May create a new window, or reuse an existing one; see the function display-buffer. | |
247 | |
248 Alternatively, FILENAME may start with a prefix character to indicate special | |
249 handling. See documentation of `hpath:find' for details. | |
250 | |
251 Return non-nil iff file is displayed within a buffer." | |
252 (interactive "FFind file in other window: ") | |
253 (hpath:find filename t)) | |
254 | |
255 (defun hpath:is-p (path &optional type non-exist) | |
256 "Returns PATH if PATH is a Unix path, else nil. | |
257 If optional TYPE is the symbol 'file or 'directory, then only that path type | |
258 is accepted as a match. The existence of the path is checked only for | |
259 locally reachable paths (Info paths are not checked). Single spaces are | |
260 permitted in middle of existing pathnames, but not at the start or end. Tabs | |
261 and newlines are converted to space before the pathname is checked, this | |
262 normalized path form is what is returned for PATH. With optional NON-EXIST, | |
263 nonexistent local paths are allowed." | |
264 (let ((rtn-path path) | |
265 (suffix)) | |
266 (and (stringp path) | |
267 ;; Path may be a link reference with other components other than a | |
268 ;; pathname. These components always follow a comma, so strip them, | |
269 ;; if any, before checking path. | |
270 (if (string-match "[ \t\n\^M]*," path) | |
271 (setq rtn-path (concat (substring path 0 (match-beginning 0)) | |
272 "%s" (substring path (match-beginning 0))) | |
273 path (substring path 0 (match-beginning 0))) | |
274 (setq rtn-path (concat rtn-path "%s"))) | |
275 (if (string-match hpath:prefix-regexp path) | |
276 (setq path (substring path (match-end 0))) | |
277 t) | |
278 (not (or (string= path "") | |
279 (string-match "\\`\\s \\|\\s \\'" path))) | |
280 ;; Convert tabs and newlines to space. | |
281 (setq path (hbut:key-to-label (hbut:label-to-key path))) | |
282 (or (not (string-match "[()]" path)) | |
283 (string-match "\\`([^ \t\n\^M\)]+)[ *A-Za-z0-9]" path)) | |
284 (if (string-match "\\${[^}]+}" path) | |
285 (setq path (hpath:substitute-value path)) | |
286 t) | |
287 (not (string-match "[\t\n\^M\"`'{}|\\]" path)) | |
288 (or (not (hpath:www-p path)) | |
289 (string-match "^ftp:" path)) | |
290 (let ((remote-path (string-match "@.+:\\|^/.+:\\|.+:/" path))) | |
291 (if (cond (remote-path | |
292 (cond ((eq type 'file) | |
293 (not (string-equal "/" (substring path -1)))) | |
294 ((eq type 'directory) | |
295 (string-equal "/" (substring path -1))) | |
296 (t))) | |
297 ((or (and non-exist | |
298 (or | |
299 ;; Info or ange-ftp path, so don't check for. | |
300 (string-match "[()]" path) | |
301 (hpath:ange-ftp-p path) | |
302 (setq suffix (hpath:exists-p path t)) | |
303 ;; Don't allow spaces in non-existent | |
304 ;; pathnames. | |
305 (not (string-match " " path)))) | |
306 (setq suffix (hpath:exists-p path t))) | |
307 (cond ((eq type 'file) | |
308 (not (file-directory-p path))) | |
309 ((eq type 'directory) | |
310 (file-directory-p path)) | |
311 (t))) | |
312 ) | |
313 ;; Return path if non-nil return value | |
314 (if (stringp suffix) ;; suffix could = t, which we ignore | |
315 (if (string-match | |
316 (concat (regexp-quote suffix) "%s") rtn-path) | |
317 ;; remove suffix | |
318 (concat (substring rtn-path 0 (match-beginning 0)) | |
319 (substring rtn-path (match-end 0))) | |
320 ;; add suffix | |
321 (format rtn-path suffix)) | |
322 (format rtn-path ""))))))) | |
323 | |
324 (defun hpath:relative-to (path &optional default-dir) | |
325 "Returns PATH relative to optional DEFAULT-DIR or 'default-directory'. | |
326 Returns PATH unchanged when it is not a valid path." | |
327 (if (not (and (stringp path) (hpath:is-p path))) | |
328 path | |
329 (setq default-dir | |
330 (expand-file-name | |
331 (file-name-as-directory (or default-dir default-directory))) | |
332 path (expand-file-name path)) | |
333 (and path default-dir | |
334 (if (string-equal "/" default-dir) | |
335 path | |
336 (let ((end-dir (min (length path) (length default-dir)))) | |
337 (cond | |
338 ((string-equal (substring path 0 end-dir) default-dir) | |
339 (concat "./" (substring path end-dir))) | |
340 ((progn (setq default-dir (file-name-directory (directory-file-name default-dir)) | |
341 end-dir (min (length path) (length default-dir))) | |
342 (string-equal (substring path 0 end-dir) default-dir)) | |
343 (concat "../" (substring path end-dir))) | |
344 ((progn (setq default-dir (file-name-directory (directory-file-name default-dir)) | |
345 end-dir (min (length path) (length default-dir))) | |
346 (string-equal (substring path 0 end-dir) default-dir)) | |
347 (concat "../../" (substring path end-dir))) | |
348 (t path))))))) | |
349 | |
350 (defun hpath:rfc (rfc-num) | |
351 "Return pathname to textual rfc document indexed by RFC-NUM. | |
352 See the documentation of the 'hpath:rfc' variable." | |
353 (format hpath:rfc rfc-num)) | |
354 | |
355 (defun hpath:substitute-value (path) | |
356 "Substitutes matching value for Emacs Lisp variables and environment variables in PATH. | |
357 Returns path with variable values substituted." | |
358 (substitute-in-file-name | |
359 (hypb:replace-match-string | |
360 "\\${[^}]+}" | |
361 path | |
362 (function | |
363 (lambda (str) | |
364 (let* ((var-group (substring path match start)) | |
365 (var-name (substring path (+ match 2) (1- start))) | |
366 (rest-of-path (substring path start)) | |
367 (sym (intern-soft var-name))) | |
368 (if (file-name-absolute-p rest-of-path) | |
369 (setq rest-of-path (substring rest-of-path 1))) | |
370 (if (and sym (boundp sym)) | |
371 (directory-file-name | |
372 (hpath:substitute-dir var-name rest-of-path)) | |
373 var-group)))) | |
374 t))) | |
375 | |
376 (defun hpath:substitute-var (path) | |
377 "Replaces up to one match in PATH with first matching variable from 'hpath:variables'." | |
378 (if (not (and (stringp path) (string-match "/" path) (hpath:is-p path))) | |
379 path | |
380 (setq path (hpath:symlink-referent path)) | |
381 (let ((new-path) | |
382 (vars hpath:variables) | |
383 result var val) | |
384 (while (and vars (null new-path)) | |
385 (setq var (car vars) vars (cdr vars)) | |
386 (if (boundp var) | |
387 (progn (setq val (symbol-value var)) | |
388 (cond ((stringp val) | |
389 (if (setq result | |
390 (hpath:substitute-var-name var val path)) | |
391 (setq new-path result))) | |
392 ((null val)) | |
393 ((listp val) | |
394 (while (and val (null new-path)) | |
395 (if (setq result | |
396 (hpath:substitute-var-name var (car val) path)) | |
397 (setq new-path result)) | |
398 (setq val (cdr val)))) | |
399 (t (error "(hpath:substitute-var): '%s' has invalid value for hpath:variables" var)))))) | |
400 (or new-path path) | |
401 ))) | |
402 | |
403 ;; | |
404 ;; The following function recursively resolves all UNIX links to their | |
405 ;; final referents. | |
406 ;; Works with Apollo's variant and other strange links like: | |
407 ;; /usr/local -> $(SERVER_LOCAL)/usr/local, /usr/bin -> | |
408 ;; ../$(SYSTYPE)/usr/bin and /tmp -> `node_data/tmp. It also handles | |
409 ;; relative links properly as in /usr/local/emacs -> gnu/emacs which must | |
410 ;; be resolved relative to the '/usr/local' directory. | |
411 ;; It will fail on Apollos if the '../' notation is used to move just | |
412 ;; above the '/' directory level. This is fairly uncommon and so the | |
413 ;; problem has not been fixed. | |
414 ;; | |
415 (defun hpath:symlink-referent (linkname) | |
416 "Returns expanded file or directory referent of LINKNAME. | |
417 LINKNAME should not end with a directory delimiter. | |
418 Returns nil if LINKNAME is not a string. | |
419 Returns LINKNAME unchanged if it is not a symbolic link but is a pathname." | |
420 (if (stringp linkname) | |
421 (or (file-symlink-p linkname) linkname))) | |
422 | |
423 (defun hpath:symlink-expand (referent dirname) | |
424 "Returns expanded file or directory REFERENT relative to DIRNAME." | |
425 (let ((var-link) | |
426 (dir dirname)) | |
427 (while (string-match "\\$(\\([^\)]*\\))" referent) | |
428 (setq var-link (getenv (substring referent (match-beginning 1) | |
429 (match-end 1))) | |
430 referent (concat (substring referent 0 (match-beginning 0)) | |
431 var-link | |
432 (substring referent (match-end 0))))) | |
433 ;; If referent is not an absolute path | |
434 (let ((nd-abbrev (string-match "`node_data" referent))) | |
435 (if (and nd-abbrev (= nd-abbrev 0)) | |
436 (setq referent (concat | |
437 ;; Prepend node name given in dirname, if any | |
438 (and (string-match "^//[^/]+" dirname) | |
439 (substring dirname 0 (match-end 0))) | |
440 "/sys/" (substring referent 1))))) | |
441 (while (string-match "\\(^\\|/\\)\\.\\.\\(/\\|$\\)" referent) | |
442 ;; Match to "//.." or "/.." at the start of link referent | |
443 (while (string-match "^\\(//\\.\\.\\|/\\.\\.\\)\\(/\\|$\\)" referent) | |
444 (setq referent (substring referent (match-end 1)))) | |
445 ;; Match to "../" or ".." at the start of link referent | |
446 (while (string-match "^\\.\\.\\(/\\|$\\)" referent) | |
447 (setq dir (file-name-directory (directory-file-name dir)) | |
448 referent (concat dir (substring referent (match-end 0))))) | |
449 ;; Match to rest of "../" in link referent | |
450 (while (string-match "[^/]+/\\.\\./" referent) | |
451 (setq referent (concat (substring referent 0 (match-beginning 0)) | |
452 (substring referent (match-end 0)))))) | |
453 (and (/= (aref referent 0) ?~) | |
454 (/= (aref referent 0) ?/) | |
455 (setq referent (expand-file-name referent dirname)))) | |
456 referent) | |
457 | |
458 (defun hpath:validate (path) | |
459 "Returns PATH if PATH is a valid, readable path, else signals error. | |
460 Info and ange-ftp remote pathnames are considered readable without any | |
461 validation checks. | |
462 Default-directory should be equal to current Hyperbole button source | |
463 directory when called, so that PATH is expanded relative to it." | |
464 (cond ((not (stringp path)) | |
465 (error "(hpath:validate): \"%s\" is not a pathname." path)) | |
466 ((or (string-match "[()]" path) (hpath:ange-ftp-p path)) | |
467 ;; info or ange-ftp path, so don't validate | |
468 path) | |
469 ((if (not (hpath:www-p path)) | |
470 ;; Otherwise, must not be a WWW link ref and must be a readable | |
471 ;; path. | |
472 (let ((return-path (hpath:exists-p path))) | |
473 (and return-path (file-readable-p return-path) | |
474 return-path)))) | |
475 (t (error "(hpath:validate): \"%s\" is not readable." path)))) | |
476 | |
477 (defun hpath:url-at-p () | |
478 "Return world-wide-web universal resource locator (url) that point immediately precedes or nil. | |
479 Use buffer-substring with match-beginning and match-end on the following | |
480 groupings: | |
481 1 = access protocol | |
482 2 = optional username | |
483 3 = host and domain to connect to | |
484 4 = optional port number to use | |
485 5 = pathname to access." | |
486 ;; WWW URL format: <protocol>:/[<user>@]<domain>[:<port>]/<path> | |
487 ;; or <protocol>://[<user>@]<domain>[:<port>]<path> | |
488 ;; Avoid [a-z]:/path patterns since these may be disk paths on OS/2, DOS or | |
489 ;; Windows. | |
490 (if (looking-at "\\([a-zA-Z][a-zA-Z]+\\)://?\\([^@/: \t\n\^M]+@\\)?\\([^/:@ \t\n\^M\"`']+\\)\\(:[0-9]+\\)?\\([/~][^]@ \t\n\^M\"`'\)\}>]*\\)?") | |
491 (save-excursion | |
492 (goto-char (match-end 0)) | |
493 (skip-chars-backward ".?#!*()") | |
494 (buffer-substring (match-beginning 0) (point))))) | |
495 | |
496 (defun hpath:url-p (obj) | |
497 "Return t if OBJ is a world-wide-web universal resource locator (url) string, else nil. | |
498 Use string-match with match-beginning and match-end on the following groupings: | |
499 1 = access protocol | |
500 2 = optional username | |
501 3 = host and domain to connect to | |
502 4 = optional port number to use | |
503 5 = pathname to access." | |
504 ;; WWW URL format: <protocol>:/[<user>@]<domain>[:<port>]/<path> | |
505 ;; or <protocol>://[<user>@]<domain>[:<port>]<path> | |
506 ;; Avoid [a-z]:/path patterns since these may be disk paths on OS/2, DOS or | |
507 ;; Windows. | |
508 (and (stringp obj) | |
509 (string-match "\\`<?\\([a-zA-Z][a-zA-Z]+\\)://?\\([^@/: \t\n\^M]+@\\)?\\([^/:@ \t\n\^M\"`']+\\)\\(:[0-9]+\\)?\\([/~][^]@ \t\n\^M\"`'\)\}>]*\\)?>?\\'" | |
510 obj) | |
511 t)) | |
512 | |
513 (defun hpath:www-at-p (&optional include-start-and-end-p) | |
514 "Returns a world-wide-web link reference that point is within or nil. | |
515 With optional INCLUDE-START-AND-END-P non-nil, returns list of: | |
516 (link-string begin-position end-position)." | |
517 (save-excursion | |
518 (skip-chars-backward "^[ \t\n\"`'\(\{<") | |
519 (cond ((not include-start-and-end-p) | |
520 (hpath:url-at-p)) | |
521 ((hpath:url-at-p) | |
522 (list (buffer-substring (match-beginning 0) (match-end 0)) | |
523 (match-beginning 0) | |
524 (match-end 0)))))) | |
525 | |
526 (defun hpath:www-p (path) | |
527 "Returns non-nil iff PATH is a world-wide-web link reference." | |
528 (and (stringp path) (hpath:url-p path) path)) | |
529 | |
530 ;;; ************************************************************************ | |
531 ;;; Private functions | |
532 ;;; ************************************************************************ | |
533 | |
534 (defun hpath:ange-ftp-available-p () | |
535 "Return t if the ange-ftp or efs package is available, nil otherwise. | |
536 Either the package must have been loaded already or under versions of Emacs | |
537 19, it must be set for autoloading via 'file-name-handler-alist'." | |
538 (or (featurep 'ange-ftp) (featurep 'efs) | |
539 (and (boundp 'file-name-handler-alist) ; v19 | |
540 (or (rassq 'ange-ftp-hook-function file-name-handler-alist) | |
541 (rassq 'efs-file-handler-function file-name-handler-alist)) | |
542 t))) | |
543 | |
544 | |
545 | |
546 (defun hpath:ange-ftp-default-user () | |
547 "Return default user account for remote file access with ange-ftp or efs. | |
548 Returns \"anonymous\" if neither ange-ftp-default-user nor efs-default-user | |
549 is set." | |
550 (cond ((and (boundp 'ange-ftp-default-user) | |
551 (stringp ange-ftp-default-user)) | |
552 ange-ftp-default-user) | |
553 ((and (boundp 'efs-default-user) | |
554 (stringp efs-default-user)) | |
555 efs-default-user) | |
556 (t "anonymous"))) | |
557 | |
558 (defun hpath:delete-trailer (string) | |
559 "Return string minus any trailing .?#!*() characters." | |
560 (save-match-data | |
561 (if (and (stringp string) (> (length string) 0) | |
562 (string-match "[.?#!*()]+\\'" string)) | |
563 (substring string 0 (match-beginning 0)) | |
564 string))) | |
565 | |
566 (defun hpath:exists-p (path &optional suffix-flag) | |
567 "Return PATH if it exists. (This does not mean you can read it.) | |
568 If PATH exists with or without a suffix from hpath:suffixes, then that | |
569 pathname is returned. | |
570 | |
571 With optional SUFFIX-FLAG and PATH exists, return suffix added or removed | |
572 from path or t." | |
573 (let ((return-path) | |
574 (suffix) suffixes) | |
575 (if (file-exists-p path) | |
576 (setq return-path path) | |
577 (setq suffixes hpath:suffixes) | |
578 (while suffixes | |
579 (setq suffix (car suffixes)) | |
580 (if (string-match (concat (regexp-quote suffix) "\\'") path) | |
581 ;; Remove suffix | |
582 (setq return-path (substring path 0 (match-beginning 0))) | |
583 ;; Add suffix | |
584 (setq return-path (concat path suffix))) | |
585 (if (file-exists-p return-path) | |
586 (setq suffixes nil);; found a match | |
587 (setq suffix nil | |
588 suffixes (cdr suffixes) | |
589 return-path nil)))) | |
590 (if return-path | |
591 (if suffix-flag | |
592 (or suffix t) | |
593 return-path)))) | |
594 | |
595 (defun hpath:find-program (filename) | |
596 "Return shell or Lisp command to execute to display FILENAME or nil. | |
597 Return nil if FILENAME is a directory name. | |
598 See also documentation for 'hpath:find-alist' and 'hpath:display-alist'." | |
599 (let ((cmd)) | |
600 (cond ((and (stringp filename) (file-directory-p filename)) | |
601 nil) | |
602 ((stringp (setq cmd (hpath:match filename hpath:find-alist))) | |
603 (let ((orig-path filename)) | |
604 ;; If filename is a remote path, we have to copy it to a | |
605 ;; temporary local file and then display that. | |
606 (if (hpath:ange-ftp-p filename) | |
607 (copy-file orig-path | |
608 (setq filename | |
609 (concat hpath:tmp-prefix | |
610 (file-name-nondirectory orig-path))) | |
611 t t)) | |
612 ;; | |
613 ;; Permit %s substitution of filename within program. | |
614 (if (string-match "[^%]%s" cmd) | |
615 (format cmd filename) | |
616 (concat cmd " " filename)))) | |
617 ((null cmd) | |
618 (hpath:match filename hpath:display-alist)) | |
619 (t cmd)))) | |
620 | |
621 (defun hpath:match (filename regexp-alist) | |
622 "If FILENAME matches the car of any element in REGEXP-ALIST, return its cdr. | |
623 REGEXP-ALIST elements must be of the form (<filename-regexp> | |
624 . <command-to-display-file>). <command-to-display-file> may be a string | |
625 representing an external window-system command to run or it may be a Lisp | |
626 function to call with FILENAME as its single argument." | |
627 (let ((cmd) | |
628 elt) | |
629 (while (and (not cmd) regexp-alist) | |
630 (if (string-match (car (setq elt (car regexp-alist))) filename) | |
631 (setq cmd (cdr elt))) | |
632 (setq regexp-alist (cdr regexp-alist))) | |
633 cmd)) | |
634 | |
635 (defun hpath:substitute-dir (var-name rest-of-path) | |
636 "Returns a dir for VAR-NAME using REST-OF-PATH to find match or triggers an error when no match. | |
637 VAR-NAME's value may be a directory or a list of directories. If it is a | |
638 list, the first directory prepended to REST-OF-PATH which produces a valid | |
639 local pathname is returned." | |
640 (let (sym val) | |
641 (cond ((not (stringp var-name)) | |
642 (error "(hpath:substitute-dir): VAR-NAME arg, '%s', must be a string" var-name)) | |
643 ((not (and (setq sym (intern-soft var-name)) | |
644 (boundp sym))) | |
645 (error "(hpath:substitute-dir): VAR-NAME arg, \"%s\", is not a bound variable" | |
646 var-name)) | |
647 ((stringp (setq val (symbol-value sym))) | |
648 (if (hpath:validate (expand-file-name rest-of-path val)) | |
649 val)) | |
650 ((listp val) | |
651 (let ((dir)) | |
652 (while (and val (not dir)) | |
653 (setq dir (car val) val (cdr val)) | |
654 (or (and (stringp dir) | |
655 (file-name-absolute-p dir) | |
656 (file-readable-p (expand-file-name rest-of-path dir))) | |
657 (setq dir nil))) | |
658 (if dir (hpath:validate (directory-file-name dir)) | |
659 (error "(hpath:substitute-dir): Can't find match for \"%s\"" | |
660 (concat "${" var-name "}/" rest-of-path)) | |
661 ))) | |
662 (t (error "(hpath:substitute-dir): Value of VAR-NAME, \"%s\", must be a string or list" var-name)) | |
663 ))) | |
664 | |
665 (defun hpath:substitute-var-name (var-symbol var-dir-val path) | |
666 "Replaces with VAR-SYMBOL any occurrences of VAR-DIR-VAL in PATH. | |
667 Replacement is done iff VAR-DIR-VAL is an absolute path. | |
668 If PATH is modified, returns PATH, otherwise returns nil." | |
669 (if (and (stringp var-dir-val) (file-name-absolute-p var-dir-val)) | |
670 (let ((new-path (hypb:replace-match-string | |
671 (regexp-quote (file-name-as-directory | |
672 (or var-dir-val default-directory))) | |
673 path (concat "${" (symbol-name var-symbol) "}/") | |
674 t))) | |
675 (if (equal new-path path) nil new-path)))) | |
676 | |
677 | |
678 ;;; ************************************************************************ | |
679 ;;; Private variables | |
680 ;;; ************************************************************************ | |
681 | |
682 | |
683 (defvar hpath:prefix-regexp "\\`[-!&][ ]*" | |
684 "Regexp matching command characters which may precede a pathname. | |
685 These are used to indicate how to display or execute the pathname. | |
686 - means evaluate it as Emacs Lisp; | |
687 ! means execute it as a shell script | |
688 & means run it under the current window system.") | |
689 | |
690 (provide 'hpath) |