1 ;;; hbdata.el --- Hyperbole button attribute accessor methods.
3 ;; Copyright (C) 1991-1995, 2008 Free Software Foundation, Inc.
4 ;; Developed with support from Motorola Inc.
6 ;; Author: Bob Weiner, Brown U.
7 ;; Maintainer: Mats Lidell <matsl@contactor.se>
8 ;; Keywords: hypermedia
10 ;; This file is part of GNU Hyperbole.
12 ;; GNU Hyperbole is free software; you can redistribute it and/or
13 ;; modify it under the terms of the GNU General Public License as
14 ;; published by the Free Software Foundation; either version 3, or (at
15 ;; your option) any later version.
17 ;; GNU Hyperbole is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 ;; General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING. If not, write to the
24 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25 ;; Boston, MA 02110-1301, USA.
29 ;; This module handles Hyperbole button data/attribute storage. In
30 ;; general, it should not be extended by anyone other than Hyperbole
31 ;; maintainers. If you alter the formats or accessors herein, you are
32 ;; likely to make your buttons incompatible with future releases.
33 ;; System developers should instead work with and extend the "hbut.el"
34 ;; module which provides much of the Hyperbole application programming
35 ;; interface and which hides the low level details handled by this
39 ;; Button data is typically stored within a file that holds the button
40 ;; data for all files within that directory. The name of this file is
41 ;; given by the variable 'hattr:filename,' usually it is ".hypb".
43 ;; Here is a sample from a Hyperbole V2 button data file. Each button
44 ;; data entry is a list of fields:
48 ;; (Key Placeholders LinkType <arg-list> creator and modifier with times)
49 ;; ("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")
51 ;; which means: button \<(alt.mouse.el)> found in file "TO-DO" in the current
52 ;; directory provides a link to the local file "./ell/alt-mouse.el". It was
53 ;; created and last modified by zzz@cs.brown.edu.
55 ;; All link entries that originate from the same source file are stored
56 ;; contiguously, one per line, in reverse order of creation.
57 ;; Preceding all such entries is the source name (in the case of a file
58 ;; used as a source, no directory information is included, since only
59 ;; sources within the same directory as the button data file are used as
60 ;; source files within it.
65 ;;; Other required Elisp libraries
75 ;;; Button data accessor functions
77 (defun hbdata:action (hbdata)
78 "[Hyp V2] Returns action overriding button's action type or nil."
81 (defun hbdata:actype (hbdata)
82 "Returns the action type in HBDATA as a string."
83 (let ((nm (symbol-name (nth 3 hbdata))))
84 (and nm (if (or (= (length nm) 2) (string-match "::" nm))
85 nm (concat "actypes::" nm)))))
87 (defun hbdata:args (hbdata)
88 "Returns the list of any arguments given in HBDATA."
91 (defun hbdata:categ (hbdata)
92 "Returns the category of HBDATA's button."
95 (defun hbdata:creator (hbdata)
96 "Returns the user-id of the original creator of HBDATA's button."
99 (defun hbdata:create-time (hbdata)
100 "Returns the original creation time given for HBDATA's button."
103 (defun hbdata:key (hbdata)
104 "Returns the indexing key in HBDATA as a string."
107 (defun hbdata:loc-p (hbdata)
108 "[Hyp V1] Returns 'L iff HBDATA referent is within a local file system.
109 Returns 'R if remote and nil if irrelevant for button action type."
112 (defun hbdata:modifier (hbdata)
113 "Returns the user-id of the most recent modifier of HBDATA's button.
114 Nil is returned when button has not been modified."
117 (defun hbdata:mod-time (hbdata)
118 "Returns the time of the most recent change to HBDATA's button.
119 Nil is returned when button has not beened modified."
122 (defun hbdata:referent (hbdata)
123 "Returns the referent name in HBDATA."
126 (defun hbdata:search (buf label partial)
127 "Go to Hyperbole hbdata BUF and find LABEL whole or PARTIAL matches.
128 Search is case-insensitive. Returns list with elements:
129 (<button-src> <label-key1> ... <label-keyN>)."
131 (let ((case-fold-search t) (src-matches) (src) (matches) (end))
132 (goto-char (point-min))
133 (while (re-search-forward "^\^L\n\"\\([^\"]+\\)\"" nil t)
134 (setq src (buffer-substring (match-beginning 1)
138 (setq end (if (re-search-forward "^\^L" nil t)
139 (1- (point)) (point-max))))
140 (while (re-search-forward
141 (concat "^(\"\\(" (if partial "[^\"]*")
142 (regexp-quote (ebut:label-to-key label))
143 (if partial "[^\"]*") "\\)\"") nil t)
145 (buffer-substring (match-beginning 1)
149 (setq src-matches (cons (cons src matches) src-matches)))
154 ;;; Button data operators
157 (defun hbdata:build (&optional mod-lbl-key but-sym)
158 "Tries to construct button data from optional MOD-LBL-KEY and BUT-SYM.
159 MOD-LBL-KEY nil means create a new entry, otherwise modify existing one.
160 BUT-SYM nil means use 'hbut:current'. If successful, returns a cons of
161 (button-data . button-instance-str), else nil."
163 (b (hattr:copy (or but-sym 'hbut:current) 'but))
164 (l (hattr:get b 'loc))
165 (key (or mod-lbl-key (hattr:get b 'lbl-key)))
166 (new-key (if mod-lbl-key (hattr:get b 'lbl-key) key))
167 (lbl-instance) (creator) (create-time) (modifier) (mod-time)
171 (setq loc (if (bufferp l) l (file-name-nondirectory l))
172 dir (if (bufferp l) nil (file-name-directory l)))
173 (if (setq entry (hbdata:to-entry key loc dir (not mod-lbl-key)))
176 (setq creator (hbdata:creator entry)
177 create-time (hbdata:create-time entry)
178 modifier (let* ((user (user-login-name))
180 hyperb:host-domain)))
181 (if (equal creator addr)
183 mod-time (htz:date-sortable-gmt)
184 entry (cons new-key (cdr entry)))
185 (hbdata:delete-entry-at-point)
186 (if (setq lbl-instance (hbdata:instance-last new-key loc dir))
188 (setq lbl-instance (concat ebut:instance-sep
190 ;; This line is needed to ensure that the highest
191 ;; numbered instance of a label appears before
192 ;; other instances, so 'hbdata:instance-last' will work.
193 (if (hbdata:to-entry-buf loc dir) (forward-line 1))))
195 (let ((inst-num (hbdata:instance-last new-key loc dir)))
196 (setq lbl-instance (if inst-num
197 (hbdata:instance-next
198 (concat new-key ebut:instance-sep
199 (int-to-string inst-num))))))
201 (if (or entry (not mod-lbl-key))
203 (list (concat new-key lbl-instance)
204 (hattr:get b 'action)
205 ;; Hyperbole V1 referent compatibility, always nil in V2
206 (hattr:get b 'referent)
207 ;; Save actype without class prefix
208 (let ((actype (hattr:get b 'actype)))
209 (and actype (symbolp actype)
210 (setq actype (symbol-name actype))
212 (substring actype (if (string-match "::" actype)
214 (let ((mail-dir (and (fboundp 'hmail:composing-dir)
215 (hmail:composing-dir l)))
216 (args (hattr:get b 'args)))
217 ;; Replace matches for Emacs Lisp directory variable
218 ;; values with their variable names in any pathname args.
219 (mapcar 'hpath:substitute-var
221 ;; Make pathname args absolute for outgoing mail and
223 (action:path-args-abs args mail-dir)
225 (or creator (concat (user-login-name) hyperb:host-domain))
226 (or create-time (htz:date-sortable-gmt))
232 (defun hbdata:get-entry (lbl-key key-src &optional directory)
233 "Returns button data entry given by LBL-KEY, KEY-SRC and optional DIRECTORY.
234 Returns nil if no matching entry is found.
235 A button data entry is a list of attribute values. Use methods from
236 class 'hbdata' to operate on the entry."
238 (function (lambda () (read (current-buffer))))
239 lbl-key key-src directory))
241 (defun hbdata:instance-next (lbl-key)
242 "Returns string for button instance number following LBL-KEY's.
243 nil if LBL-KEY is nil."
246 (concat (regexp-quote ebut:instance-sep) "[0-9]+$") lbl-key)
247 (concat ebut:instance-sep
249 (1+ (string-to-number
250 (substring lbl-key (1+ (match-beginning 0)))))))
253 (defun hbdata:instance-last (lbl-key key-src &optional directory)
254 "Returns highest instance number for repeated button label.
255 1 if not repeated, nil if no instance.
256 Takes arguments LBL-KEY, KEY-SRC and optional DIRECTORY."
259 (if (looking-at "[0-9]+")
260 (string-to-number (buffer-substring (match-beginning 0)
263 lbl-key key-src directory nil 'instance))
265 (defun hbdata:delete-entry (lbl-key key-src &optional directory)
266 "Deletes button data entry given by LBL-KEY, KEY-SRC and optional DIRECTORY.
267 Returns entry deleted (a list of attribute values) or nil.
268 Use methods from class 'hbdata' to operate on the entry."
272 (prog1 (read (current-buffer))
273 (let ((empty-file-entry "[ \t\n]*\\(\^L\\|\\'\\)")
276 (hbdata:delete-entry-at-point)
277 (if (looking-at empty-file-entry)
279 (empty-hbdata-file "[ \t\n]*\\'"))
281 (if (= (following-char) ?\")
282 ;; Last button entry for filename, so del filename.
283 (progn (forward-line -1) (delete-region (point) end)))
285 (goto-char (point-min))
286 (if (looking-at empty-hbdata-file)
289 (let ((fname buffer-file-name))
290 (erase-buffer) (save-buffer) (kill-buffer nil)
291 (hbmap:dir-remove (file-name-directory fname))
292 (call-process "rm" nil 0 nil "-f" fname)))))))))
293 lbl-key key-src directory))
295 (defun hbdata:delete-entry-at-point ()
296 (delete-region (point) (progn (forward-line 1) (point))))
298 (defun hbdata:to-entry (but-key key-src &optional directory instance)
299 "Returns button data entry indexed by BUT-KEY, KEY-SRC, optional DIRECTORY.
300 Returns nil if entry is not found. Leaves point at start of entry when
301 successful or where entry should be inserted if unsuccessful.
302 A button entry is a list. Use methods from class 'hbdata' to operate on the
303 entry. Optional INSTANCE non-nil means search for any button instance matching
305 (let ((pos-entry-cons
310 (cons (point) (read (current-buffer)))))
311 but-key key-src directory 'create instance)))
312 (hbdata:to-entry-buf key-src directory)
316 (goto-char (car pos-entry-cons))
317 (cdr pos-entry-cons)))))
320 ;;; Private functions
323 (defun hbdata:apply-entry (function lbl-key key-src &optional directory
325 "Invokes FUNCTION with point at hbdata entry given by LBL-KEY, KEY-SRC, optional DIRECTORY.
326 With optional CREATE, if no such line exists, inserts a new file entry at the
327 beginning of the hbdata file (which is created if necessary).
328 INSTANCE non-nil means search for any button instance matching LBL-KEY and
329 call FUNCTION with point right after any 'ebut:instance-sep' in match.
330 Returns value of evaluation when a matching entry is found or nil."
338 (if (not (bufferp key-src))
341 (cond ((hmail:editor-p)
342 (setq end-func (function (lambda ()
343 (hmail:msg-narrow)))))
344 ((and (hmail:lister-p)
345 (progn (rmail:summ-msg-to) (rmail:to)))
347 key-src (current-buffer)
348 end-func (function (lambda ()
352 ((and (hnews:lister-p)
353 (progn (rnews:summ-msg-to) (rnews:to)))
355 key-src (current-buffer)
356 end-func (function (lambda ()
360 (setq found (hbdata:to-entry-buf key-src directory create)))
362 (let ((case-fold-search t)
363 (qkey (regexp-quote lbl-key))
364 (end (save-excursion (if (search-forward "\n\^L" nil t)
365 (point) (point-max)))))
368 (concat "\n(\"" qkey "["
369 ebut:instance-sep "\"]") end t)
370 (search-forward (concat "\n(\"" lbl-key "\"") end t))
372 (or instance (beginning-of-line))
373 (let (buffer-read-only)
374 (setq rtn (funcall function)))))))
375 (if end-func (funcall end-func))))
378 (defun hbdata:to-hbdata-buffer (dir &optional create)
379 "Reads in the file containing DIR's button data, if any, and returns buffer.
380 If it does not exist and optional CREATE is non-nil, creates a new
381 one and returns buffer, otherwise returns nil."
382 (let* ((file (expand-file-name hattr:filename (or dir default-directory)))
383 (existing-file (or (file-exists-p file) (get-file-buffer file)))
384 (buf (or (get-file-buffer file)
385 (and (or create existing-file)
386 (find-file-noselect file)))))
388 (progn (set-buffer buf)
389 (or (verify-visited-file-modtime (get-file-buffer file))
391 "Hyperbole button data file has changed, read new contents? ")
394 (or (= (point-max) 1) (eq (char-after 1) ?\^L)
395 (error "File %s is not a valid Hyperbole button data table." file))
396 (or (equal (buffer-name) file) (rename-buffer file))
397 (setq buffer-read-only nil)
398 (or existing-file (hbmap:dir-add (file-name-directory file)))
402 (defun hbdata:to-entry-buf (key-src &optional directory create)
403 "Moves point to end of line in but data buffer matching KEY-SRC.
404 Uses hbdata file in KEY-SRC's directory, or optional DIRECTORY or if nil, uses
406 With optional CREATE, if no such line exists, inserts a new file entry at the
407 beginning of the hbdata file (which is created if necessary).
408 Returns non-nil if KEY-SRC is found or created, else nil."
409 (let ((rtn) (ln-dir))
410 (if (bufferp key-src)
411 ;; Button buffer has no file attached
412 (progn (setq rtn (set-buffer key-src)
413 buffer-read-only nil)
414 (if (not (hmail:hbdata-to-p))
415 (insert "\n" hmail:hbdata-sep "\n"))
418 (setq directory (or (file-name-directory key-src) directory))
419 (let ((ln-file) (link-p key-src))
420 (while (setq link-p (file-symlink-p link-p))
421 (setq ln-file link-p))
423 (setq ln-dir (file-name-directory ln-file)
424 key-src (file-name-nondirectory ln-file))
425 (setq key-src (file-name-nondirectory key-src))))
426 (if (or (hbdata:to-hbdata-buffer directory create)
427 (and ln-dir (hbdata:to-hbdata-buffer ln-dir nil)
432 (cond ((search-forward (concat "\^L\n\"" key-src "\"")
437 (insert "\^L\n\"" key-src "\"\n")
443 (defun hbdata:write (&optional orig-lbl-key but-sym)
444 "Tries to write Hyperbole button data from optional ORIG-LBL-KEY and BUT-SYM.
445 ORIG-LBL-KEY nil means create a new entry, otherwise modify existing one.
446 BUT-SYM nil means use 'hbut:current'. If successful, returns
447 a button instance string to append to button label or t when first instance.
448 On failure, returns nil."
449 (let ((cns (hbdata:build orig-lbl-key but-sym))
451 (if (or (and buffer-file-name
452 (not (file-writable-p buffer-file-name)))
455 (setq entry (car cns) lbl-instance (cdr cns))
456 (prin1 entry (current-buffer))
457 (terpri (current-buffer))
463 ;;; Private variables
468 ;;; hbdata.el ends here