1 ;;; patcher-util.el --- General utilities
3 ;; Copyright (C) 2008, 2009, 2010 Didier Verna
4 ;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2007 Didier Verna
6 ;; Author: Didier Verna <didier@xemacs.org>
7 ;; Maintainer: Didier Verna <didier@xemacs.org>
8 ;; Created: Sat Feb 13 14:31:32 2010
9 ;; Last Revision: Fri Dec 2 22:06:27 2011
13 ;; This file is part of Patcher.
15 ;; Patcher is free software; you can redistribute it and/or modify
16 ;; it under the terms of the GNU General Public License version 3,
17 ;; as published by the Free Software Foundation.
19 ;; Patcher is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 ;; GNU General Public License for more details.
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with this program; if not, write to the Free Software
26 ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
31 ;; Contents management by FCM version 0.1.
38 (eval-when-compile (require 'patcher-cutil))
42 ;; ===========================================================================
43 ;; 21.4 Backward compatibility
44 ;; ===========================================================================
46 ;; Byte compilation warnings =================================================
48 (unless (fboundp 'with-fboundp)
49 (defmacro* with-fboundp (functions &body body)
52 (unless (fboundp 'with-boundp)
53 (defmacro* with-boundp (variables &body body)
56 (unless (fboundp 'declare-fboundp)
57 (defmacro declare-fboundp (form)
60 (unless (fboundp 'declare-boundp)
61 (defmacro declare-boundp (form)
64 (unless (fboundp 'globally-declare-boundp)
65 (defmacro globally-declare-boundp (variables)
66 (setq variables (eval variables))
67 (if (not (consp variables))
68 (setq variables (list variables)))
70 ,@(mapcar #'(lambda (sym) `(defvar ,sym)) variables))))
75 ;; ===========================================================================
77 ;; ===========================================================================
79 (defun patcher-symbol (symbol)
80 ;; Return SYMBOL prefixed with `patcher-'.
81 (intern (concat "patcher-" (symbol-name symbol))))
83 (defmacro patcher-endpush (value location)
84 ;; Like PUSH, but at the end.
85 `(setf ,location (nconc ,location (list ,value))))
87 ;; Hacked from read-number.
88 (defun* patcher-read-natnum
89 (prompt &optional default-value (min 1)
90 &aux (pred (lambda (val) (and (integerp val) (>= val min))))
92 ;; Read a natural number from the minibuffer, prompting with PROMPT.
93 ;; If optional second argument DEFAULT-VALUE is non-nil, return that if user
94 ;; enters an empty line.
95 ;; MIN (1 by default) specifies the lowest permissible value.
96 (while (not (funcall pred num))
97 (setq num (condition-case ()
98 (let ((minibuffer-completion-table nil))
100 prompt (if num (prin1-to-string num)) nil t
101 nil nil (when default-value
102 (prin1-to-string default-value))))
104 (invalid-read-syntax nil)
106 (or (funcall pred num) (beep)))
109 ;; Hacked from y-or-n-p-minibuf. Isn't there something to do this already ??
110 (defun patcher-read-char (prompt chars)
111 ;; PROMPT for one character from CHARS in the minibuffer.
114 (answer (concat "(" chars ") "))
116 (while (stringp answer)
117 (if (let ((cursor-in-echo-area t)
119 (message "%s%s%s" pre prompt answer)
120 (setq event (next-command-event event))
123 (or quit-flag (eq 'keyboard-quit (key-binding event)))
124 (setq quit-flag nil))
125 (wrong-type-argument t)))
127 (message "%s%s%s%s" pre prompt answer
128 (single-key-description event))
131 (let* ((keys (events-to-keys (vector event))))
132 (cond ((and (= (length keys) 1)
133 (find (string-to-char keys) chars))
134 (message "%s%s%s" prompt answer keys)
135 (setq answer (string-to-char keys)))
136 ((button-release-event-p event) ; ignore them
139 (message "%s%s%s%s" pre prompt answer
140 (single-key-description event))
143 (when (= (length pre) 0)
144 (setq pre (format "Please answer one of %s. " chars)))))))
147 (put 'patcher-list= 'lisp-indent-function 2)
148 (defun* patcher-list=
151 &aux spurious (missing (copy-list list2)) common)
152 ;; Compare LIST1 to LIST2 using TEST to compare elements (EQL by default).
153 ;; Return 4 values: whether the two lists contain exactly the same elements
154 ;; (regardless of their order), the list of spurious elements in LIST1, the
155 ;; list of missing elements in LIST1 and the list of common elements.
156 (dolist (elt list1 (values (and (not spurious) (not missing))
160 (if (member* elt list2 :test test)
163 (setq missing (delete* elt missing :count 1 :test test)))
164 (patcher-endpush elt spurious))))
166 ;; #### NOTE: this function is currently only used as a gross hack to make
167 ;; hooks project-specific. Otherwise, we would need to refcount them in case
168 ;; overlapping instances have hooks in common. However, it might turn out one
169 ;; day that some hooks actually like having the patcher-project variable set,
171 (defun patcher-wrap-hook (project hook)
172 ;; Return a lambda expression wrapping a call to HOOK.
173 ;; The call is wrapped in a dynamic biding of patcher-project.
175 (let ((patcher-project ,project))
181 ;; ===========================================================================
183 ;; ===========================================================================
185 (defun patcher-message (msg &rest args)
186 ;; Print a message, letting XEmacs time to display it. Also, handle command
188 (message (substitute-command-keys (apply #'format msg args)))
190 ;; sit-for may change the current buffer and we don't want that.
193 (defun patcher-warning (msg &rest args)
194 ;; Like `patcher-message, but triggers a Patcher warning instead.
195 (warn (substitute-command-keys (apply #'format msg args))))
198 (put 'patcher-with-progression 'lisp-indent-function 1)
199 (defmacro* patcher-with-progression (msg &body body)
200 ;; Wrap BODY in "msg..." / "msg...done" messages.
201 ;; Return the value of BODY execution.
203 (patcher-message (concat ,msg "... please wait."))
205 (patcher-message (concat ,msg "... done."))))
207 (put 'patcher-with-message 'lisp-indent-function 1)
208 (defmacro* patcher-with-message (message &body body)
209 ;; Display MESSAGE in a temporary buffer and execute BODY.
210 ;; Command keys in MESSAGE are substituted first.
211 (let ((msg (gensym "msg")))
212 `(save-window-excursion
214 (let ((,msg (substitute-command-keys ,message)))
215 (with-output-to-temp-buffer " *Patcher Message*"
216 (set-buffer " *Patcher Message*")
220 (defun patcher-modal-message (message)
221 ;; Display MESSAGE and wait for user acknowledgment.
222 (patcher-with-message message
223 (read-string "Type return to proceed.")))
228 ;; ===========================================================================
230 ;; ===========================================================================
232 (define-error 'patcher
233 "Root of the Patcher error hierarchy.")
235 (put 'patcher-define-error 'lisp-indent-function 1)
236 (defun* patcher-define-error
238 &optional docstring (super-error 'patcher super-error-given-p))
239 ;; Define a new Patcher error named PATCHER-<ERROR-SYMBOL>.
240 ;; Optionally provide a DOCSTRING.
241 ;; Define the error as a sub-error of SUPER-ERROR (PATCHER by default).
242 (when super-error-given-p
243 (setq super-error (patcher-symbol super-error)))
244 (define-error (patcher-symbol error-symbol) docstring super-error))
246 (defun patcher-error (error &rest data)
247 ;; Signal patcher ERROR with DATA.
248 ;; #### WARNING: temporary compatibility hack.
250 (error (substitute-command-keys (apply #'format error data)))
251 (apply #'error (patcher-symbol error) data)))
253 (put 'patcher-condition-case 'lisp-indent-function 2)
254 (defmacro* patcher-condition-case (var bodyform &rest handlers)
255 ;; Like condition-case, but prefix condition names with patcher-.
256 `(condition-case ,var ,bodyform
257 ,@(mapcar (lambda (handler)
258 (cons (patcher-symbol (car handler))
262 (defun patcher-display-error-message (message)
263 ;; Display MESSAGE, beep and wait for user acknowledgment.
264 (patcher-with-message message
266 (read-string "Type return to proceed.")))
271 ;; ===========================================================================
273 ;; ===========================================================================
275 (defun* patcher-file-buffer
277 &aux (existing (get-file-buffer file))
280 (find-file-noselect file)))))
281 ;; Find a buffer visiting FILE.
282 ;; Return 2 values: a buffer visiting FILE and a boolean indicating whether
283 ;; FILE was already visited. If FILE is not visited, return nil unless FIND,
284 ;; in which case force visiting.
285 (values buffer existing))
287 (defun* patcher-file-relative-name (file &optional (dir default-directory))
288 ;; Construct a filename from FILE relative to DIR.
289 (file-relative-name (expand-file-name file (expand-file-name dir))
290 (expand-file-name dir)))
292 (defun patcher-files-string (files)
293 ;; Convert FILES to a string of relative file names.
294 (mapconcat #'patcher-file-relative-name files " "))
296 (defun patcher-buffers-string (buffers)
297 ;; Convert BUFFERS file names to a string of relative file names.
298 (patcher-files-string (mapcar #'buffer-file-name buffers)))
300 (defun patcher-sort-files (files)
301 ;; Sort FILES by lexicographic order.
302 (sort (copy-list files) #'string<))
304 (defun patcher-save-buffer (buffer &optional force)
305 ;; Offer to save BUFFER, or FORCE saving.
306 (when (and (buffer-modified-p buffer)
307 (not (buffer-base-buffer buffer))
308 (buffer-file-name buffer)
310 (save-window-excursion
311 (display-buffer buffer)
314 (patcher-file-relative-name
315 (buffer-file-name buffer)))))))
322 (defun patcher-save-buffers (buffers &optional force)
323 ;; Offer to save some BUFFERS, or FORCE saving.
324 (dolist (buffer buffers)
325 (patcher-save-buffer buffer force)))
330 ;; ===========================================================================
332 ;; ===========================================================================
334 ;; #### WARNING: dynamic scoping fuckage at places. MAPCAR-EXTENTS uses some
335 ;; PROPERTY and VALUE arguments so I need other names in BODIES and
338 (put 'patcher-mapcar-extents 'lisp-indent-function 1)
339 (defmacro* patcher-mapcar-extents
340 ((extent property &key predicate here value) &body body)
341 ;; Map BODY over all extents having patcher-PROPERTY in HERE.
342 ;; Bind EXTENT to every extent in turn.
343 ;; Optionally restrict patcher-PROPERTY to have VALUE.
344 ;; If HERE is a buffer and narrowing is in effect, restrict to the narrowed
346 `(mapcar-extents (lambda (,extent) ,@body)
349 (and (or (null ,here) (bufferp ,here))
351 (and (or (null ,here) (bufferp ,here))
353 nil (patcher-symbol ,property) ,value))
355 (defun patcher-collect-extents-property (property &optional here)
356 ;; Collect the values of patcher-PROPERTY from all extents in HERE.
357 (let ((dynamic-scoping-sucks-bones (patcher-symbol property)))
358 (patcher-mapcar-extents (extent property :here here)
359 (extent-property extent dynamic-scoping-sucks-bones))))
361 (put 'patcher-extents 'lisp-indent-function 1)
362 (defun* patcher-extents (property &key value (test #'eq) here)
363 ;; Get all extents having patcher-PROPERTY equal to VALUE by TEST in HERE.
365 (mapcar-extents #'identity
366 nil here nil nil nil (patcher-symbol property) value)
367 (let ((dynamic-scoping-sucks-bones (patcher-symbol property))
368 (dynamic-scoping-sucks-bones-big-time value))
369 (mapcar-extents #'identity
372 (extent-property extent dynamic-scoping-sucks-bones)
373 dynamic-scoping-sucks-bones-big-time))
376 (put 'patcher-extent 'lisp-indent-function 1)
377 (defun* patcher-extent (property &key value (test #'eq) here)
378 ;; Get the first extent having patcher-PROPERTY equal to VALUE by TEST in
380 (car (patcher-extents property :value value :test test :here here)))
382 (defun patcher-delete-extent (extent)
384 ;; Return t an extent has actually been deleted.
386 (delete-extent extent)
389 (defun patcher-delete-extent-and-region (extent)
390 ;; Delete EXTENT and the corresponding region.
391 ;; Return t an extent has actually been deleted.
393 (delete-region (extent-start-position extent) (extent-end-position extent)
394 (extent-object extent))
395 (delete-extent extent)
398 (put 'patcher-within-extent 'lisp-indent-function 1)
399 (defmacro* patcher-within-extent ((extent property) &body body)
400 ;; Find an extent having the patcher-PROPERTY set, execute BODY in it.
401 ;; EXTENT is bound to the extent when BODY is executed.
402 ;; Start-close EXTENT around BODY so that insertion is possible.
403 `(let ((,extent (patcher-extent ,property)))
406 (goto-char (extent-start-position ,extent))
407 (set-extent-property ,extent 'start-open nil)
409 (set-extent-property ,extent 'start-open t)))))
414 ;; ===========================================================================
416 ;; ===========================================================================
418 (patcher-define-error 'process
419 "Patcher process error.")
421 (defun* patcher-call-process
422 (command &optional (progression (format "Running `%s'" command))
424 ;; Call a shell process to execute COMMAND.
425 ;; Make people wait with PROGRESSION message.
426 ;; Process output goes to current buffer, before current point.
427 ;; Return point delimiting the end of the process output.
428 ;; Throw a PATCHER-PROCESS error for non-zero exit status.
429 (patcher-with-progression progression
430 (unless (or (zerop (funcall #'call-process shell-file-name
431 nil t nil shell-command-switch command))
433 (patcher-error 'process command)))
437 (provide 'patcher-util)
439 ;;; patcher-util.el ends here