Mercurial > hg > xemacs-beta
diff lisp/hyperbole/hbdata.el @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/hyperbole/hbdata.el Mon Aug 13 08:45:50 2007 +0200 @@ -0,0 +1,459 @@ +;;!emacs +;; +;; FILE: hbdata.el +;; SUMMARY: Hyperbole button attribute accessor methods. +;; USAGE: GNU Emacs Lisp Library +;; KEYWORDS: hypermedia +;; +;; AUTHOR: Bob Weiner +;; ORG: Brown U. +;; +;; ORIG-DATE: 2-Apr-91 +;; LAST-MOD: 14-Apr-95 at 15:59:49 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: +;; +;; This module handles Hyperbole button data/attribute storage. In +;; general, it should not be extended by anyone other than Hyperbole +;; maintainers. If you alter the formats or accessors herein, you are +;; likely to make your buttons incompatible with future releases. +;; System developers should instead work with and extend the "hbut.el" +;; module which provides much of the Hyperbole application programming +;; interface and which hides the low level details handled by this +;; module. +;; +;; +;; Button data is typically stored within a file that holds the button +;; data for all files within that directory. The name of this file is +;; given by the variable 'hattr:filename,' usually it is ".hypb". +;; +;; Here is a sample from a Hyperbole V2 button data file. Each button +;; data entry is a list of fields: +;; +;; +;; "TO-DO" +;; (Key Placeholders LinkType <arg-list> creator and modifier with times) +;; ("alt.mouse.el" nil nil link-to-file ("./ell/alt-mouse.el") "zzz@cs.brown.edu" "19911027:09:19:26" "zzz" "19911027:09:31:36") +;; +;; which means: button \<(alt.mouse.el)> found in file "TO-DO" in the current +;; directory provides a link to the local file "./ell/alt-mouse.el". It was +;; created and last modified by zzz@cs.brown.edu. +;; +;; All link entries that originate from the same source file are stored +;; contiguously, one per line, in reverse order of creation. +;; Preceding all such entries is the source name (in the case of a file +;; used as a source, no directory information is included, since only +;; sources within the same directory as the button data file are used as +;; source files within it. +;; +;; DESCRIP-END. + +;;; ************************************************************************ +;;; Other required Elisp libraries +;;; ************************************************************************ + +(require 'hbmap) + +;;; ************************************************************************ +;;; Public functions +;;; ************************************************************************ + +;;; ------------------------------------------------------------------------ +;;; Button data accessor functions +;;; ------------------------------------------------------------------------ +(defun hbdata:action (hbdata) + "[Hyp V2] Returns action overriding button's action type or nil." + (nth 1 hbdata)) + +(defun hbdata:actype (hbdata) + "Returns the action type in HBDATA as a string." + (let ((nm (symbol-name (nth 3 hbdata)))) + (and nm (if (or (= (length nm) 2) (string-match "::" nm)) + nm (concat "actypes::" nm))))) + +(defun hbdata:args (hbdata) + "Returns the list of any arguments given in HBDATA." + (nth 4 hbdata)) + +(defun hbdata:categ (hbdata) + "Returns the category of HBDATA's button." + 'explicit) + +(defun hbdata:creator (hbdata) + "Returns the user-id of the original creator of HBDATA's button." + (nth 5 hbdata)) + +(defun hbdata:create-time (hbdata) + "Returns the original creation time given for HBDATA's button." + (nth 6 hbdata)) + +(defun hbdata:key (hbdata) + "Returns the indexing key in HBDATA as a string." + (car hbdata)) + +(defun hbdata:loc-p (hbdata) + "[Hyp V1] Returns 'L iff HBDATA referent is within a local file system. +Returns 'R if remote and nil if irrelevant for button action type." + (nth 1 hbdata)) + +(defun hbdata:modifier (hbdata) + "Returns the user-id of the most recent modifier of HBDATA's button. +Nil is returned when button has not been modified." + (nth 7 hbdata)) + +(defun hbdata:mod-time (hbdata) + "Returns the time of the most recent change to HBDATA's button. +Nil is returned when button has not beened modified." + (nth 8 hbdata)) + +(defun hbdata:referent (hbdata) + "Returns the referent name in HBDATA." + (nth 2 hbdata)) + +(defun hbdata:search (buf label partial) + "Go to Hyperbole hbdata BUF and find LABEL whole or PARTIAL matches. + Search is case-insensitive. Returns list with elements: + (<button-src> <label-key1> ... <label-keyN>)." + (set-buffer buf) + (let ((case-fold-search t) (src-matches) (src) (matches) (end)) + (goto-char (point-min)) + (while (re-search-forward "^\^L\n\"\\([^\"]+\\)\"" nil t) + (setq src (buffer-substring (match-beginning 1) + (match-end 1)) + matches nil) + (save-excursion + (setq end (if (re-search-forward "^\^L" nil t) + (1- (point)) (point-max)))) + (while (re-search-forward + (concat "^(\"\\(" (if partial "[^\"]*") + (regexp-quote (ebut:label-to-key label)) + (if partial "[^\"]*") "\\)\"") nil t) + (setq matches (cons + (buffer-substring (match-beginning 1) + (match-end 1)) + matches))) + (if matches + (setq src-matches (cons (cons src matches) src-matches))) + (goto-char end)) + src-matches)) + +;;; ------------------------------------------------------------------------ +;;; Button data operators +;;; ------------------------------------------------------------------------ + +(defun hbdata:build (&optional mod-lbl-key but-sym) + "Tries to construct button data from optional MOD-LBL-KEY and BUT-SYM. +MOD-LBL-KEY nil means create a new entry, otherwise modify existing one. +BUT-SYM nil means use 'hbut:current'. If successful, returns a cons of + (button-data . button-instance-str), else nil." + (let* ((but) + (b (hattr:copy (or but-sym 'hbut:current) 'but)) + (l (hattr:get b 'loc)) + (key (or mod-lbl-key (hattr:get b 'lbl-key))) + (new-key (if mod-lbl-key (hattr:get b 'lbl-key) key)) + (lbl-instance) (creator) (create-time) (modifier) (mod-time) + (entry) loc dir) + (if (null l) + nil + (setq loc (if (bufferp l) l (file-name-nondirectory l)) + dir (if (bufferp l) nil (file-name-directory l))) + (if (setq entry (hbdata:to-entry key loc dir (not mod-lbl-key))) + (if mod-lbl-key + (progn + (setq creator (hbdata:creator entry) + create-time (hbdata:create-time entry) + modifier (let* ((user (user-login-name)) + (addr (concat user + hyperb:host-domain))) + (if (equal creator addr) + user addr)) + mod-time (htz:date-sortable-gmt) + entry (cons new-key (cdr entry))) + (hbdata:delete-entry-at-point) + (if (setq lbl-instance (hbdata:instance-last new-key loc dir)) + (progn + (setq lbl-instance (concat ebut:instance-sep + (1+ lbl-instance))) + ;; This line is needed to ensure that the highest + ;; numbered instance of a label appears before + ;; other instances, so 'hbdata:instance-last' will work. + (if (hbdata:to-entry-buf loc dir) (forward-line 1)))) + ) + (let ((inst-num (hbdata:instance-last new-key loc dir))) + (setq lbl-instance (if inst-num + (hbdata:instance-next + (concat new-key ebut:instance-sep + (int-to-string inst-num)))))) + )) + (if (or entry (not mod-lbl-key)) + (cons + (list (concat new-key lbl-instance) + (hattr:get b 'action) + ;; Hyperbole V1 referent compatibility, always nil in V2 + (hattr:get b 'referent) + ;; Save actype without class prefix + (let ((actype (hattr:get b 'actype))) + (and actype (symbolp actype) + (setq actype (symbol-name actype)) + (intern + (substring actype (if (string-match "::" actype) + (match-end 0) 0))))) + (let ((mail-dir (and (fboundp 'hmail:composing-dir) + (hmail:composing-dir l))) + (args (hattr:get b 'args))) + ;; Replace matches for Emacs Lisp directory variable + ;; values with their variable names in any pathname args. + (mapcar 'hpath:substitute-var + (if mail-dir + ;; Make pathname args absolute for outgoing mail and + ;; news messages. + (action:path-args-abs args mail-dir) + args))) + (or creator (concat (user-login-name) hyperb:host-domain)) + (or create-time (htz:date-sortable-gmt)) + modifier + mod-time) + lbl-instance) + )))) + +(defun hbdata:get-entry (lbl-key key-src &optional directory) + "Returns button data entry given by LBL-KEY, KEY-SRC and optional DIRECTORY. +Returns nil if no matching entry is found. +A button data entry is a list of attribute values. Use methods from +class 'hbdata' to operate on the entry." + (hbdata:apply-entry + (function (lambda () (read (current-buffer)))) + lbl-key key-src directory)) + +(defun hbdata:instance-next (lbl-key) + "Returns string for button instance number following LBL-KEY's. +nil if LBL-KEY is nil." + (and lbl-key + (if (string-match + (concat (regexp-quote ebut:instance-sep) "[0-9]+$") lbl-key) + (concat ebut:instance-sep + (int-to-string + (1+ (string-to-int + (substring lbl-key (1+ (match-beginning 0))))))) + ":2"))) + +(defun hbdata:instance-last (lbl-key key-src &optional directory) + "Returns highest instance number for repeated button label. +1 if not repeated, nil if no instance. +Takes arguments LBL-KEY, KEY-SRC and optional DIRECTORY." + (hbdata:apply-entry + (function (lambda () + (if (looking-at "[0-9]+") + (string-to-int (buffer-substring (match-beginning 0) + (match-end 0))) + 1))) + lbl-key key-src directory nil 'instance)) + +(defun hbdata:delete-entry (lbl-key key-src &optional directory) + "Deletes button data entry given by LBL-KEY, KEY-SRC and optional DIRECTORY. +Returns entry deleted (a list of attribute values) or nil. +Use methods from class 'hbdata' to operate on the entry." + (hbdata:apply-entry + (function + (lambda () + (prog1 (read (current-buffer)) + (let ((empty-file-entry "[ \t\n]*\\(\^L\\|\\'\\)") + (kill)) + (beginning-of-line) + (hbdata:delete-entry-at-point) + (if (looking-at empty-file-entry) + (let ((end (point)) + (empty-hbdata-file "[ \t\n]*\\'")) + (forward-line -1) + (if (= (following-char) ?\") + ;; Last button entry for filename, so del filename. + (progn (forward-line -1) (delete-region (point) end))) + (save-excursion + (goto-char (point-min)) + (if (looking-at empty-hbdata-file) + (setq kill t))) + (if kill + (let ((fname buffer-file-name)) + (erase-buffer) (save-buffer) (kill-buffer nil) + (hbmap:dir-remove (file-name-directory fname)) + (call-process "rm" nil 0 nil "-f" fname))))))))) + lbl-key key-src directory)) + +(defun hbdata:delete-entry-at-point () + (delete-region (point) (progn (forward-line 1) (point)))) + +(defun hbdata:to-entry (but-key key-src &optional directory instance) + "Returns button data entry indexed by BUT-KEY, KEY-SRC, optional DIRECTORY. +Returns nil if entry is not found. Leaves point at start of entry when +successful or where entry should be inserted if unsuccessful. +A button entry is a list. Use methods from class 'hbdata' to operate on the +entry. Optional INSTANCE non-nil means search for any button instance matching +but-key." + (let ((pos-entry-cons + (hbdata:apply-entry + (function + (lambda () + (beginning-of-line) + (cons (point) (read (current-buffer))))) + but-key key-src directory 'create instance))) + (hbdata:to-entry-buf key-src directory) + (forward-line 1) + (if pos-entry-cons + (progn + (goto-char (car pos-entry-cons)) + (cdr pos-entry-cons))))) + +;;; ************************************************************************ +;;; Private functions +;;; ************************************************************************ + +(defun hbdata:apply-entry (function lbl-key key-src &optional directory + create instance) + "Invokes FUNCTION with point at hbdata entry given by LBL-KEY, KEY-SRC, optional DIRECTORY. +With optional CREATE, if no such line exists, inserts a new file entry at the +beginning of the hbdata file (which is created if necessary). +INSTANCE non-nil means search for any button instance matching LBL-KEY and +call FUNCTION with point right after any 'ebut:instance-sep' in match. +Returns value of evaluation when a matching entry is found or nil." + (let ((found) + (rtn) + (opoint) + (end-func)) + (save-excursion + (unwind-protect + (progn + (if (not (bufferp key-src)) + nil + (set-buffer key-src) + (cond ((hmail:editor-p) + (setq end-func (function (lambda () + (hmail:msg-narrow))))) + ((and (hmail:lister-p) + (progn (rmail:summ-msg-to) (rmail:to))) + (setq opoint (point) + key-src (current-buffer) + end-func (function (lambda () + (hmail:msg-narrow) + (goto-char opoint) + (lmail:to))))) + ((and (hnews:lister-p) + (progn (rnews:summ-msg-to) (rnews:to))) + (setq opoint (point) + key-src (current-buffer) + end-func (function (lambda () + (hmail:msg-narrow) + (goto-char opoint) + (lnews:to))))))) + (setq found (hbdata:to-entry-buf key-src directory create))) + (if found + (let ((case-fold-search t) + (qkey (regexp-quote lbl-key)) + (end (save-excursion (if (search-forward "\n\^L" nil t) + (point) (point-max))))) + (if (if instance + (re-search-forward + (concat "\n(\"" qkey "[" + ebut:instance-sep "\"]") end t) + (search-forward (concat "\n(\"" lbl-key "\"") end t)) + (progn + (or instance (beginning-of-line)) + (let (buffer-read-only) + (setq rtn (funcall function))))))) + (if end-func (funcall end-func)))) + rtn)) + +(defun hbdata:to-hbdata-buffer (dir &optional create) + "Reads in the file containing DIR's button data, if any, and returns buffer. +If it does not exist and optional CREATE is non-nil, creates a new +one and returns buffer, otherwise returns nil." + (let* ((file (expand-file-name hattr:filename (or dir default-directory))) + (existing-file (or (file-exists-p file) (get-file-buffer file))) + (buf (or (get-file-buffer file) + (and (or create existing-file) + (find-file-noselect file))))) + (if buf + (progn (set-buffer buf) + (or (verify-visited-file-modtime (get-file-buffer file)) + (cond ((yes-or-no-p + "Hyperbole button data file has changed, read new contents? ") + (revert-buffer t t) + ))) + (or (= (point-max) 1) (eq (char-after 1) ?\^L) + (error "File %s is not a valid Hyperbole button data table." file)) + (or (equal (buffer-name) file) (rename-buffer file)) + (setq buffer-read-only nil) + (or existing-file (hbmap:dir-add (file-name-directory file))) + buf)))) + + +(defun hbdata:to-entry-buf (key-src &optional directory create) + "Moves point to end of line in but data buffer matching KEY-SRC. +Uses hbdata file in KEY-SRC's directory, or optional DIRECTORY or if nil, uses +default-directory. +With optional CREATE, if no such line exists, inserts a new file entry at the +beginning of the hbdata file (which is created if necessary). +Returns non-nil if KEY-SRC is found or created, else nil." + (let ((rtn) (ln-dir)) + (if (bufferp key-src) + ;; Button buffer has no file attached + (progn (setq rtn (set-buffer key-src) + buffer-read-only nil) + (if (not (hmail:hbdata-to-p)) + (insert "\n" hmail:hbdata-sep "\n")) + (backward-char 1) + ) + (setq directory (or (file-name-directory key-src) directory)) + (let ((ln-file) (link-p key-src)) + (while (setq link-p (file-symlink-p link-p)) + (setq ln-file link-p)) + (if ln-file + (setq ln-dir (file-name-directory ln-file) + key-src (file-name-nondirectory ln-file)) + (setq key-src (file-name-nondirectory key-src)))) + (if (or (hbdata:to-hbdata-buffer directory create) + (and ln-dir (hbdata:to-hbdata-buffer ln-dir nil) + (setq create nil + directory ln-dir))) + (progn + (goto-char 1) + (cond ((search-forward (concat "\^L\n\"" key-src "\"") + nil t) + (setq rtn t)) + (create + (setq rtn t) + (insert "\^L\n\"" key-src "\"\n") + (backward-char 1)) + )))) + rtn + )) + +(defun hbdata:write (&optional orig-lbl-key but-sym) + "Tries to write Hyperbole button data from optional ORIG-LBL-KEY and BUT-SYM. +ORIG-LBL-KEY nil means create a new entry, otherwise modify existing one. +BUT-SYM nil means use 'hbut:current'. If successful, returns +a button instance string to append to button label or t when first instance. +On failure, returns nil." + (let ((cns (hbdata:build orig-lbl-key but-sym)) + entry lbl-instance) + (if (or (and buffer-file-name + (not (file-writable-p buffer-file-name))) + (null cns)) + nil + (setq entry (car cns) lbl-instance (cdr cns)) + (prin1 entry (current-buffer)) + (terpri (current-buffer)) + (or lbl-instance t) + ))) + + +;;; ************************************************************************ +;;; Private variables +;;; ************************************************************************ + +(provide 'hbdata)