Mercurial > hg > xemacs-beta
diff lisp/efs/efs-vms.el @ 22:8fc7fe29b841 r19-15b94
Import from CVS: tag r19-15b94
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:50:29 +0200 |
parents | |
children | 7e54bd776075 9f59509498e1 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/efs/efs-vms.el Mon Aug 13 08:50:29 2007 +0200 @@ -0,0 +1,760 @@ +;; -*-Emacs-Lisp-*- +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; File: efs-vms.el +;; Release: $efs release: 1.15 $ +;; Version: $Revision: 1.1 $ +;; RCS: +;; Description: VMS support for efs +;; Authors: Andy Norman, Joe Wells, Sandy Rutherford <sandy@itp.ethz.ch> +;; Modified: Sun Nov 27 18:44:59 1994 by sandy on gandalf +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;; This file is part of efs. See efs.el for copyright +;;; (it's copylefted) and warrranty (there isn't one) information. + +(provide 'efs-vms) +(require 'efs) + +(defconst efs-vms-version + (concat (substring "$efs release: 1.15 $" 14 -2) + "/" + (substring "$Revision: 1.1 $" 11 -2))) + +;;;; ------------------------------------------------------------ +;;;; VMS support. +;;;; ------------------------------------------------------------ + +;;; efs has full support for VMS hosts, including tree dired support. It +;;; should be able to automatically recognize any VMS machine. However, if it +;;; fails to do this, you can use the command efs-add-vms-host. As well, +;;; you can set the variable efs-vms-host-regexp in your .emacs file. We +;;; would be grateful if you would report any failures to automatically +;;; recognize a VMS host as a bug. +;;; +;;; Filename Syntax: +;;; +;;; For ease of *implementation*, the user enters the VMS filename syntax in a +;;; UNIX-y way. For example: +;;; PUB$:[ANONYMOUS.SDSCPUB.NEXT]README.TXT;1 +;;; would be entered as: +;;; /PUB$$:/ANONYMOUS/SDSCPUB/NEXT/README.TXT;1 +;;; i.e. to log in as anonymous on ymir.claremont.edu and grab the file: +;;; [.CSV.POLICY]RULES.MEM +;;; you would type: +;;; C-x C-f /anonymous@ymir.claremont.edu:CSV/POLICY/RULES.MEM +;;; +;;; A legal VMS filename is of the form: FILE.TYPE;## +;;; where FILE can be up to 39 characters +;;; TYPE can be up to 39 characters +;;; ## is a version number (an integer between 1 and 32,767) +;;; Valid characters in FILE and TYPE are A-Z 0-9 _ - $ +;;; $ cannot begin a filename, and - cannot be used as the first or last +;;; character. +;;; +;;; Tips: +;;; 1. To access the latest version of file under VMS, you use the filename +;;; without the ";" and version number. You should always edit the latest +;;; version of a file. If you want to edit an earlier version, copy it to a +;;; new file first. This has nothing to do with efs, but is simply +;;; good VMS operating practice. Therefore, to edit FILE.TXT;3 (say 3 is +;;; latest version), do C-x C-f /ymir.claremont.edu:FILE.TXT. If you +;;; inadvertently do C-x C-f /ymir.claremont.edu:FILE.TXT;3, you will find +;;; that VMS will not allow you to save the file because it will refuse to +;;; overwrite FILE.TXT;3, but instead will want to create FILE.TXT;4, and +;;; attach the buffer to this file. To get out of this situation, M-x +;;; write-file /ymir.claremont.edu:FILE.TXT will attach the buffer to +;;; latest version of the file. For this reason, in tree dired "f" +;;; (dired-find-file), always loads the file sans version, whereas "v", +;;; (dired-view-file), always loads the explicit version number. The +;;; reasoning being that it reasonable to view old versions of a file, but +;;; not to edit them. +;;; 2. EMACS has a feature in which it does environment variable substitution +;;; in filenames. Therefore, to enter a $ in a filename, you must quote it +;;; by typing $$. There is a bug in EMACS, in that it neglects to quote the +;;; $'s in the default directory when it writes it in the minibuffer. You +;;; must edit the minibuffer to quote the $'s manually. Hopefully, this bug +;;; will be fixed in EMACS 19. If you use Sebastian Kremer's gmhist (V 4.26 +;;; or newer), you will not have this problem. + + +;; Because some VMS ftp servers convert filenames to lower case +;; we allow a-z in the filename regexp. + +(defconst efs-vms-filename-regexp + "\\([_A-Za-z0-9$][-_A-Za-z0-9$]*\\)?\\.\\([-_A-Za-z0-9$]*\\);[0-9]+") +;; Regular expression to match for a valid VMS file name in Dired buffer. + +(defvar efs-vms-month-alist + '(("JAN" . 1) ("FEB". 2) ("MAR" . 3) ("APR" . 4) ("MAY" . 5) ("JUN" . 6) + ("JUL" . 7) ("AUG" . 8) ("SEP" . 9) ("OCT" . 10) + ("NOV" . 11) ("DEC" . 12))) + +(defvar efs-vms-date-regexp + (concat + "\\([0-3]?[0-9]\\)-" + "\\(JAN\\|FEB\\|MAR\\|APR\\|MAY\\|JUN\\|" + "JUL\\|AUG\\|SEP\\|OCT\\|NOV\\|DEC\\)-" + "\\([0-9][0-9][0-9]?[0-9]?\\) \\(\\([0-5][0-9]\\):\\([0-5][0-9]\\)" + "\\(:[0-5][0-9]\\)?\\)? ")) + + +;;; The following two functions are entry points to this file. +;;; They are defined as efs-autoloads in efs.el + +(efs-defun efs-fix-path vms (path &optional reverse) + ;; Convert PATH from UNIX-ish to VMS. + ;; If REVERSE given then convert from VMS to UNIX-ish. + (efs-save-match-data + (if reverse + (if (string-match + "^\\([^:]+:\\)?\\(\\[[^]]+\\]\\)?\\([^][]*\\)$" path) + (let (drive dir file) + (if (match-beginning 1) + (setq drive (substring path + (match-beginning 1) + (match-end 1)))) + (if (match-beginning 2) + (setq dir + (substring path (match-beginning 2) (match-end 2)))) + (if (match-beginning 3) + (setq file + (substring path (match-beginning 3) (match-end 3)))) + (and dir + (setq dir (apply (function concat) + (mapcar (function + (lambda (char) + (if (= char ?.) + (vector ?/) + (vector char)))) + (substring dir 1 -1))))) + (concat (and drive + (concat "/" drive "/")) + dir (and dir "/") + file)) + (error "path %s didn't match" path)) + (let (drive dir file) + (if (string-match "^/[^:/]+:/" path) + (setq drive (substring path 1 (1- (match-end 0))) + path (substring path (1- (match-end 0))))) + (setq dir (file-name-directory path) + file (efs-internal-file-name-nondirectory path)) + (if dir + (let ((len (1- (length dir))) + (n 0)) + (if (<= len 0) + (setq dir nil) + (while (<= n len) + (and (char-equal (aref dir n) ?/) + (cond + ((zerop n) (aset dir n ?\[)) + ((= n len) (aset dir n ?\])) + (t (aset dir n ?.)))) + (setq n (1+ n)))))) + (concat drive dir file))))) + +;; It is important that this function barf for directories for which we know +;; that we cannot possibly get a directory listing, such as "/" and "/DEV:/". +;; This is because it saves an unnecessary FTP error, or possibly the listing +;; might succeed, but give erroneous info. This last case is particularly +;; likely for OS's (like MTS) for which we need to use a wildcard in order +;; to list a directory. + +(efs-defun efs-fix-dir-path vms (dir-path) + ;; Convert path from UNIX-ish to VMS ready for a DIRectory listing. + ;; Should there be entries for .. -> [-] and . -> [] below. Don't + ;; think so, because expand-filename should have already short-circuited + ;; them. + (cond ((string-equal dir-path "/") + (error "Cannot get listing for fictitious \"/\" directory.")) + ((string-match "^/[-A-Z0-9_$]+:/$" dir-path) + (error "Cannot get listing for device.")) + ((efs-fix-path 'vms dir-path)))) + +;; These parsing functions are as general as possible because the syntax +;; of ftp listings from VMS hosts is a bit erratic. What saves us is that +;; the VMS filename syntax is so rigid. If they bomb on a listing in the +;; standard VMS Multinet format, then this is a bug. If they bomb on a listing +;; from vms.weird.net, then too bad. + +(defmacro efs-parse-vms-filename () + "Extract the next filename from a VMS dired-like listing." + (` (if (re-search-forward + efs-vms-filename-regexp + nil t) + (buffer-substring (match-beginning 0) (match-end 0))))) + +(defun efs-parse-vms-listing () + ;; Parse the current buffer which is assumed to be a VMS DIR + ;; listing (either a short (NLIST) or long listing). + ;; Assumes that point is at the beginning of the buffer. + (let ((tbl (efs-make-hashtable)) + file) + (goto-char (point-min)) + (efs-save-match-data + (while (setq file (efs-parse-vms-filename)) + (if (string-match "\\.\\(DIR\\|dir\\);[0-9]+" file) + ;; deal with directories + (efs-put-hash-entry + (substring file 0 (match-beginning 0)) '(t) tbl) + (efs-put-hash-entry file '(nil) tbl) + (if (string-match ";[0-9]+$" file) ; deal with extension + ;; sans extension + (efs-put-hash-entry + (substring file 0 (match-beginning 0)) '(nil) tbl))) + (forward-line 1)) + ;; Would like to look for a "Total" line, or a "Directory" line to + ;; make sure that the listing isn't complete garbage before putting + ;; in "." and "..", but we can't even count on all VAX's giving us + ;; either of these. + (efs-put-hash-entry "." '(t) tbl) + (efs-put-hash-entry ".." '(t) tbl)) + tbl)) + +(efs-defun efs-parse-listing vms + (host user dir path &optional switches) + ;; Parse the current buffer which is assumed to be a VMS FTP dir + ;; format, and return a hashtable as the result. SWITCHES are never used, + ;; but they must be specified in the argument list for compatibility + ;; with the unix version of this function. + ;; HOST = remote host name + ;; USER = user name + ;; DIR = directory in as a full remote path + ;; PATH = directory in full efs path syntax + ;; SWITCHES = ls switches (not relevant here) + (goto-char (point-min)) + (efs-save-match-data + ;; check for a DIR/FULL monstrosity + (if (search-forward "\nSize:" nil t) + (progn + (efs-add-listing-type 'vms:full host user) + ;; This will cause the buffer to be refilled with an NLIST + (let ((efs-ls-uncache t)) + (efs-ls path nil (format "Relisting %s" + (efs-relativize-filename path)) + t)) + (goto-char (point-min)) + (efs-parse-vms-listing)) + (efs-parse-vms-listing)))) + + +;;;; Sorting of listings + +(efs-defun efs-t-converter vms (&optional regexp reverse) + (if regexp + nil + (goto-char (point-min)) + (efs-save-match-data + (if (re-search-forward efs-vms-filename-regexp nil t) + (let (list-start start end list) + (beginning-of-line) + (setq list-start (point)) + (while (and (looking-at efs-vms-filename-regexp) + (progn + (setq start (point)) + (goto-char (match-end 0)) + (forward-line (if (eolp) 2 1)) + (setq end (point)) + (goto-char (match-end 0)) + (re-search-forward efs-vms-date-regexp nil t))) + (setq list + (cons + (cons + (nconc + (list (string-to-int (buffer-substring + (match-beginning 3) + (match-end 3))) ; year + (cdr (assoc + (buffer-substring (match-beginning 2) + (match-end 2)) + efs-vms-month-alist)) ; month + (string-to-int (buffer-substring + (match-beginning 1) + (match-end 1)))) ;day + (if (match-beginning 4) + (list + (string-to-int (buffer-substring + (match-beginning 5) + (match-end 5))) ; hour + (string-to-int (buffer-substring + (match-beginning 6) + (match-end 6))) ; minute + (if (match-beginning 7) + (string-to-int (buffer-substring + (1+ (match-beginning 7)) + (match-end 7))) ; seconds + 0)) + (list 0 0 0))) + (buffer-substring start end)) + list)) + (goto-char end)) + (if list + (progn + (setq list + (mapcar 'cdr + (sort list 'efs-vms-t-converter-sort-pred))) + (if reverse (setq list (nreverse list))) + (delete-region list-start (point)) + (apply 'insert list))) + t))))) + +(defun efs-vms-t-converter-sort-pred (elt1 elt2) + (let* ((data1 (car elt1)) + (data2 (car elt2)) + (year1 (car data1)) + (year2 (car data2)) + (month1 (nth 1 data1)) + (month2 (nth 1 data2)) + (day1 (nth 2 data1)) + (day2 (nth 2 data2)) + (hour1 (nth 3 data1)) + (hour2 (nth 3 data2)) + (minute1 (nth 4 data1)) + (minute2 (nth 4 data2))) + (or (> year1 year2) + (and (= year1 year2) + (or (> month1 month2) + (and (= month1 month2) + (or (> day1 day2) + (and (= day1 day2) + (or (> hour1 hour2) + (and (= hour1 hour2) + (or (> minute1 minute2) + (and (= minute1 minute2) + (or (> (nth 5 data1) + (nth 5 data2))) + )))))))))))) + + +(efs-defun efs-X-converter vms (&optional regexp reverse) + ;; Sorts by extension + (if regexp + nil + (goto-char (point-min)) + (efs-save-match-data + (if (re-search-forward efs-vms-filename-regexp nil t) + (let (list-start start list) + (beginning-of-line) + (setq list-start (point)) + (while (looking-at efs-vms-filename-regexp) + (setq start (point)) + (goto-char (match-end 0)) + (forward-line (if (eolp) 2 1)) + (setq list + (cons + (cons (buffer-substring (match-beginning 2) + (match-end 2)) + (buffer-substring start (point))) + list))) + (setq list + (mapcar 'cdr + (sort list + (if reverse + (function + (lambda (x y) + (string< (car y) (car x)))) + (function + (lambda (x y) + (string< (car x) (car y)))))))) + (delete-region list-start (point)) + (apply 'insert list) + t))))) + +;; This version only deletes file entries which have +;; explicit version numbers, because that is all VMS allows. + +(efs-defun efs-delete-file-entry vms (path &optional dir-p) + (let ((ignore-case (memq 'vms efs-case-insensitive-host-types))) + (if dir-p + (let ((path (file-name-as-directory path)) + files) + (efs-del-hash-entry path efs-files-hashtable ignore-case) + (setq path (directory-file-name path) + files (efs-get-hash-entry (file-name-directory path) + efs-files-hashtable + ignore-case)) + (if files + (efs-del-hash-entry (efs-get-file-part path) + files ignore-case))) + (efs-save-match-data + (let ((file (efs-get-file-part path))) + (if (string-match ";[0-9]+$" file) + ;; In VMS you can't delete a file without an explicit + ;; version number, or wild-card (e.g. FOO;*) + ;; For now, we give up on wildcards. + (let ((files (efs-get-hash-entry + (file-name-directory path) + efs-files-hashtable ignore-case))) + (if files + (let ((root (substring file 0 + (match-beginning 0))) + (completion-ignore-case ignore-case) + (len (match-beginning 0))) + (efs-del-hash-entry file files ignore-case) + ;; Now we need to check if there are any + ;; versions left. If not, then delete the + ;; root entry. + (or (all-completions + root files + (function + (lambda (sym) + (string-match ";[0-9]+$" + (symbol-name sym) len)))) + (efs-del-hash-entry root files + ignore-case))))))))) + (efs-del-from-ls-cache path t ignore-case))) + +(efs-defun efs-add-file-entry vms (path dir-p size owner + &optional modes nlinks mdtm) + ;; The vms version of this function needs to keep track + ;; of vms's file versions. + (let ((ignore-case (memq 'vms efs-case-insensitive-host-types)) + (ent (let ((dir-p (null (null dir-p)))) + (if mdtm + (list dir-p size owner nil nil mdtm) + (list dir-p size owner))))) + (if dir-p + (let* ((path (directory-file-name path)) + (files (efs-get-hash-entry (file-name-directory path) + efs-files-hashtable + ignore-case))) + (if files + (efs-put-hash-entry (efs-get-file-part path) + ent files ignore-case))) + (let ((files (efs-get-hash-entry + (file-name-directory path) + efs-files-hashtable ignore-case))) + (if files + (let ((file (efs-get-file-part path))) + (efs-save-match-data + ;; In VMS files must have an extension. If there isn't + ;; one, it will be added. + (or (string-match "^[^;]*\\." file) + (if (string-match ";" file) + (setq file (concat + (substring file 0 (match-beginning 0)) + ".;" + (substring file (match-end 0)))) + (setq file (concat file ".")))) + (if (string-match ";[0-9]+$" file) + (efs-put-hash-entry + (substring file 0 (match-beginning 0)) + ent files ignore-case) + ;; Need to figure out what version of the file + ;; is being added. + (let* ((completion-ignore-case ignore-case) + (len (length file)) + (versions (all-completions + file files + (function + (lambda (sym) + (string-match ";[0-9]+$" + (symbol-name sym) len))))) + (N (1+ len)) + (max (apply + 'max + (cons 0 (mapcar + (function + (lambda (x) + (string-to-int (substring x N)))) + versions))))) + ;; No need to worry about case here. + (efs-put-hash-entry + (concat file ";" (int-to-string (1+ max))) ent files)))) + (efs-put-hash-entry file ent files ignore-case))))) + (efs-del-from-ls-cache path t ignore-case))) + +(efs-defun efs-really-file-p vms (file ent) + ;; Returns whether the hash entry FILE with entry ENT is a real file. + (or (car ent) ; file-directory-p + (efs-save-match-data + (string-match ";" file)))) + +(efs-defun efs-internal-file-name-as-directory vms (name) + (efs-save-match-data + (if (string-match "\\.\\(DIR\\|dir\\)\\(;[0-9]+\\)?$" name) + (setq name (substring name 0 (match-beginning 0)))) + (let (file-name-handler-alist) + (file-name-as-directory name)))) + +(efs-defun efs-remote-directory-file-name vms (dir) + ;; Returns the VMS filename in unix directory syntax for directory DIR. + ;; This is something like /FM/SANDY/FOOBAR.DIR;1 + (efs-save-match-data + (setq dir (directory-file-name dir)) + (concat dir + (if (string-match "[a-z]" (nth 2 (efs-ftp-path dir))) + ".dir;1" + ".DIR;1")))) + +(efs-defun efs-allow-child-lookup vms (host user dir file) + ;; Returns t if FILE in directory DIR could possibly be a subdir + ;; according to its file-name syntax, and therefore a child listing should + ;; be attempted. + + ;; Subdirs in VMS can't have an extension (other than .DIR, which we + ;; have truncated). + (not (or (string-match "\\." file) + (and (boundp 'dired-local-variables-file) + (stringp dired-local-variables-file) + (string-equal dired-local-variables-file file))))) + +;;; Tree dired support: + +;; For this code I have borrowed liberally from Sebastian Kremer's +;; dired-vms.el + + +;; These regexps must be anchored to beginning of line. +;; Beware that the ftpd may put the device in front of the filename. + +(defconst efs-dired-vms-re-exe + "^. [^ \t.]+\\.\\(EXE\\|exe\\)[; ]") + +(or (assq 'vms efs-dired-re-exe-alist) + (setq efs-dired-re-exe-alist + (cons (cons 'vms efs-dired-vms-re-exe) + efs-dired-re-exe-alist))) + +(defconst efs-dired-vms-re-dir + "^. [^ \t.]+\\.\\(DIR\\|dir\\)[; ]") + +(or (assq 'vms efs-dired-re-dir-alist) + (setq efs-dired-re-dir-alist + (cons (cons 'vms efs-dired-vms-re-dir) + efs-dired-re-dir-alist))) + +(efs-defun efs-dired-insert-headerline vms (dir) + ;; VMS inserts a headerline. I would prefer the headerline + ;; to be in efs format. This version tries to + ;; be careful, because we can't count on a headerline + ;; over ftp, and we wouldn't want to delete anything + ;; important. + (save-excursion + (if (looking-at "^ \\(list \\)?wildcard ") + (forward-line 1)) + ;; This is really aggressive. Too aggressive? + (let ((start (point))) + (skip-chars-forward " \t\n") + (if (looking-at efs-vms-filename-regexp) + (beginning-of-line) + (forward-line 1) + (skip-chars-forward " \t\n") + (beginning-of-line)) + (delete-region start (point))) + (insert " \n")) + (efs-real-dired-insert-headerline dir)) + +(efs-defun efs-dired-fixup-listing vms (file path &optional switches wildcard) + ;; Some vms machines list the entire path. Scrape this off. + (setq path (efs-fix-path + 'vms + ;; Need the file-name-directory, in case of widcards. + ;; Note that path is a `local' path rel. the remote host. + ;; Lose on wildcards in parent dirs. Fix if somebody complains. + (let (file-name-handler-alist) + (file-name-directory path)))) + ;; Some machines put a Node name down too. + (let ((regexp (concat "^\\([_A-Za-z0-9][-_A-Za-z0-9]*\\$\\)?" + (regexp-quote path)))) + (goto-char (point-min)) + (while (re-search-forward regexp nil t) + (delete-region (match-beginning 0) (match-end 0)))) + ;; Now need to deal with continuation lines. + (goto-char (point-min)) + (let (col start end) + (while (re-search-forward + ";[0-9]+[ \t]*\\(\n[ \t]+\\)[^; \t\n]+[^\n;]*\n" nil t) + (setq start (match-beginning 1) + end (match-end 1)) + ;; guess at the column dimensions + (or col + (save-excursion + (goto-char (point-min)) + (if (re-search-forward + (concat efs-vms-filename-regexp + "[ \t]+[^ \t\n\r]") nil t) + (setq col (- (goto-char (match-end 0)) + (progn (beginning-of-line) (point)) + 1)) + (setq col 0)))) + ;; join cont. lines. + (delete-region start end) + (goto-char start) + (insert-char ? (max (- col (current-column)) 2)))) + ;; Some vms dir listings put a triple null line before the total line. + (goto-char (point-min)) + (skip-chars-forward "\n") + (if (search-forward "\n\n\n" nil t) + (delete-char -1))) + +(efs-defun efs-dired-manual-move-to-filename vms + (&optional raise-error bol eol) + ;; In dired, move to first char of filename on this line. + ;; Returns position (point) or nil if no filename on this line. + ;; This is the VMS version. + (or eol (setq eol (save-excursion (skip-chars-forward "^\r\n") (point)))) + (let (case-fold-search) + (if bol + (goto-char bol) + (skip-chars-backward "^\n\r")) + (if (re-search-forward efs-vms-filename-regexp eol t) + (goto-char (match-beginning 0)) + (and raise-error (error "No file on this line"))))) + +(efs-defun efs-dired-manual-move-to-end-of-filename vms + (&optional no-error bol eol) + ;; Assumes point is at beginning of filename. + ;; So, it should be called only after (dired-move-to-filename t). + ;; case-fold-search must be nil, at least for VMS. + ;; On failure, signals an error or returns nil. + ;; This is the VMS version. + (let ((opoint (point))) + (and selective-display + (null no-error) + (eq (char-after + (1- (or bol (save-excursion + (skip-chars-backward "^\r\n") + (point))))) + ?\r) + ;; File is hidden or omitted. + (cond + ((dired-subdir-hidden-p (dired-current-directory)) + (error + (substitute-command-keys + "File line is hidden. Type \\[dired-hide-subdir] to unhide."))) + ((error + (substitute-command-keys + "File line is omitted. Type \\[dired-omit-toggle] to un-omit." + ))))) + (skip-chars-forward "-_A-Za-z0-9$.;") + (if (or (= opoint (point)) (not (memq (following-char) '(?\ ?\t ?\n ?\r)))) + (if no-error + nil + (error "No file on this line")) + (point)))) + +(efs-defun efs-dired-ls-trim vms () + (goto-char (point-min)) + (let ((case-fold-search nil)) + (re-search-forward efs-vms-filename-regexp)) + (beginning-of-line) + (delete-region (point-min) (point)) + (forward-line 1) + (delete-region (point) (point-max))) + +(efs-defun efs-internal-file-name-sans-versions vms + (name &optional keep-backup-version) + (efs-save-match-data + (if (string-match ";[0-9]+$" name) + (substring name 0 (match-beginning 0)) + name))) + +(efs-defun efs-dired-collect-file-versions vms () + ;; If it looks like file FN has versions, return a list of the versions. + ;; That is a list of strings which are file names. + ;; The caller may want to flag some of these files for deletion. + (let ((completion-ignore-case (memq 'vms efs-case-insensitive-host-types)) + result) + (dired-map-dired-file-lines + (function + (lambda (fn) + (if (string-match ";[0-9]+$" fn) + (let* ((base-fn (substring fn 0 (match-beginning 0))) + (base-version (file-name-nondirectory + (substring fn 0 (1+ (match-beginning 0))))) + (bv-length (length base-version)) + (possibilities (and + (null (assoc base-fn result)) + (file-name-all-completions + base-version + (file-name-directory fn))))) + (if possibilities + (setq result + (cons (cons base-fn + ;; code this explicitly + ;; using backup-extract-version has a + ;; lot of function-call overhead. + (mapcar (function + (lambda (fn) + (string-to-int + (substring fn bv-length)))) + possibilities)) result)))))))) + result)) + +(efs-defun efs-dired-flag-backup-files vms (&optional unflag-p) + (interactive "P") + (let ((dired-kept-versions 1) + (kept-old-versions 0) + marker msg) + (if unflag-p + (setq marker ?\040 msg "Unflagging old versions") + (setq marker dired-del-marker msg "Purging old versions")) + (dired-clean-directory 1 marker msg))) + +(efs-defun efs-internal-diff-latest-backup-file vms (fn) + ;; For FILE;#, returns the filename FILE;N, where N + ;; is the largest number less than #, for which this file exists. + ;; Returns nil if none found. + (efs-save-match-data + (and (string-match ";[0-9]+$" fn) + (let ((base (substring fn 0 (1+ (match-beginning 0)))) + (num (1- (string-to-int (substring fn + (1+ (match-beginning 0)))))) + found file) + (while (and (setq found (> num 0)) + (not (file-exists-p + (setq file + (concat base (int-to-string num)))))) + (setq num (1- num))) + (and found file))))) + +;;;;-------------------------------------------------------------- +;;;; Support for VMS DIR/FULL listings. (listing type vms:full) +;;;;-------------------------------------------------------------- + +(efs-defun efs-parse-listing vms:full + (host user dir path &optional switches) + ;; Parse the current buffer which is assumed to be a VMS FTP dir + ;; format, and return a hashtable as the result. SWITCHES are never used, + ;; but they must be specified in the argument list for compatibility + ;; with the unix version of this function. + ;; HOST = remote host name + ;; USER = user name + ;; DIR = directory in as a full remote path + ;; PATH = directory in full efs path syntax + ;; SWITCHES = ls switches (not relevant here) + (goto-char (point-min)) + (efs-save-match-data + (efs-parse-vms-listing))) + +;;; Tree Dired + +(or (assq 'vms:full efs-dired-re-exe-alist) + (setq efs-dired-re-exe-alist + (cons (cons 'vms:full efs-dired-vms-re-exe) + efs-dired-re-exe-alist))) + +(or (assq 'vms:full efs-dired-re-dir-alist) + (setq efs-dired-re-dir-alist + (cons (cons 'vms:full efs-dired-vms-re-dir) + efs-dired-re-dir-alist))) + +(efs-defun efs-dired-insert-headerline vms:full (dir) + ;; Insert a blank line for aesthetics. + (insert " \n") + (forward-char -2) + (efs-real-dired-insert-headerline dir)) + +(efs-defun efs-dired-manual-move-to-filename vms:full + (&optional raise-error bol eol) + (let ((efs-dired-listing-type 'vms)) + (efs-dired-manual-move-to-filename raise-error bol eol))) + +(efs-defun efs-dired-manual-move-to-end-of-filename vms:full + (&optional no-error bol eol) + (let ((efs-dired-listing-type 'vms)) + (efs-dired-manual-move-to-end-of-filename no-error bol eol))) + +;;; end of efs-vms.el