Fixed error message.
[riece] / lisp / riece-addon.el
1 ;;; riece-addon.el --- add-on management
2 ;; Copyright (C) 1998-2004 Daiki Ueno
3
4 ;; Author: Daiki Ueno <ueno@unixuser.org>
5 ;; Created: 1998-09-28
6 ;; Keywords: IRC, riece
7
8 ;; This file is part of Riece.
9
10 ;; This program is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
14
15 ;; This program is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
24
25 ;;; Code:
26
27 (require 'font-lock)
28 (require 'riece-options)
29 (require 'riece-compat)
30 (require 'riece-misc)
31 (require 'riece-addon-modules)
32
33 (defgroup riece-addon-list nil
34   "Add-on listing."
35   :tag "Addon list"
36   :prefix "riece-addon-list-"
37   :group 'riece)
38
39 (defgroup riece-addon-list-faces nil
40   "Faces for riece-addon-list-mode."
41   :tag "Faces"
42   :prefix "riece-addon-list-"
43   :group 'riece-addon-list)
44
45 (defface riece-addon-list-enabled-face
46   '((((class color) (background dark))
47      (:foreground "PaleTurquoise" :bold t))
48     (t
49      (:bold t)))
50   "Face used for displaying the enabled addon."
51   :group 'riece-addon-list-faces)
52 (defvar riece-addon-list-enabled-face 'riece-addon-list-enabled-face)
53
54 (defface riece-addon-list-disabled-face
55   '((((class color) (background dark))
56      (:foreground "PaleTurquoise" :italic t))
57     (t
58      ()))
59   "Face used for displaying the disabled addon."
60   :group 'riece-addon-list-faces)
61 (defvar riece-addon-list-disabled-face 'riece-addon-list-disabled-face)
62
63 (defface riece-addon-list-unsupported-face
64   '((((class color) (background dark))
65      (:foreground "PaleTurquoise"))
66     (t
67      ()))
68   "Face used for displaying the unsupported addon."
69   :group 'riece-addon-list-faces)
70 (defvar riece-addon-list-unsupported-face 'riece-addon-list-unsupported-face)
71
72 (defface riece-addon-list-uninstalled-face
73   '((t
74      (:italic t)))
75   "Face used for displaying the uninstalled addon."
76   :group 'riece-addon-list-faces)
77 (defvar riece-addon-list-uninstalled-face 'riece-addon-list-uninstalled-face)
78
79 (defface riece-addon-list-description-face
80   '((((class color)
81       (background dark))
82      (:foreground "lightyellow"))
83     (((class color)
84       (background light))
85      (:foreground "blue4"))
86     (t
87      ()))
88   "Face used for displaying the description addon."
89   :group 'riece-addon-list-faces)
90 (defvar riece-addon-list-description-face 'riece-addon-list-description-face)
91
92 (defcustom riece-addon-list-mark-face-alist
93   '((?+ . riece-addon-list-enabled-face)
94     (?- . riece-addon-list-disabled-face)
95     (?! . riece-addon-list-unsupported-face)
96     (?  . riece-addon-list-uninstalled-face))
97   "An alist mapping marks on riece-addon-list-buffer to faces."
98   :type 'list
99   :group 'riece-addon-list)
100
101 (defcustom riece-addon-list-font-lock-keywords
102   '(("^\\([-+! ] [^:]+\\): \\(.*\\)"
103      (1 (cdr (assq (aref (match-string 1) 0)
104                    riece-addon-list-mark-face-alist)))
105      (2 riece-addon-list-description-face)))
106   "Default expressions to addon in riece-addon-list-mode."
107   :type '(repeat (list string))
108   :group 'riece-addon-list)
109
110 (defvar riece-addon-list-mode-map
111   (let ((keymap (make-sparse-keymap)))
112     (define-key keymap "+" 'riece-command-enable-addon)
113     (define-key keymap "-" 'riece-command-disable-addon)
114     (define-key keymap "I" 'riece-command-insinuate-addon)
115     (define-key keymap "D" 'riece-command-uninstall-addon)
116     (define-key keymap "n" 'next-line)
117     (define-key keymap "p" 'previous-line)
118     (define-key keymap " " 'scroll-up)
119     (define-key keymap [delete] 'scroll-down)
120     (define-key keymap "q" 'bury-buffer)
121     keymap))
122
123 (defun riece-load-and-build-addon-dependencies (addons)
124   (let ((load-path (cons riece-addon-directory load-path))
125         dependencies)
126     (while addons
127       (require (car addons))            ;error will be reported here
128       (let* ((requires
129               (funcall (or (intern-soft
130                             (concat (symbol-name (car addons)) "-requires"))
131                            #'ignore)))
132              (pointer requires)
133              entry)
134         ;; Increment succs' pred count.
135         (if (setq entry (assq (car addons) dependencies))
136             (setcar (cdr entry) (+ (length requires) (nth 1 entry)))
137           (setq dependencies (cons (list (car addons) (length requires))
138                                    dependencies)))
139         ;; Merge pred's succs.
140         (while pointer
141           (if (setq entry (assq (car pointer) dependencies))
142               (setcdr (cdr entry) (cons (car addons) (nthcdr 2 entry)))
143             (setq dependencies (cons (list (car pointer) 0 (car addons))
144                                      dependencies)))
145           (setq pointer (cdr pointer))))
146       (setq addons (cdr addons)))
147     dependencies))
148
149 (defun riece-sort-addon-dependencies (dependencies)
150   (let ((pointer dependencies)
151         addons queue)
152     (while pointer
153       (if (zerop (nth 1 (car pointer)))
154           (setq dependencies (delq (car pointer) dependencies)
155                 queue (cons (car pointer) queue)))
156       (setq pointer (cdr pointer)))
157     (while queue
158       (setq addons (cons (cons (car (car queue)) (nthcdr 2 (car queue)))
159                          addons)
160             pointer (nthcdr 2 (car queue)))
161       (while pointer
162         (let* ((entry (assq (car pointer) dependencies))
163                (count (1- (nth 1 entry))))
164           (if (zerop count)
165               (setq dependencies (delq entry dependencies)
166                     queue (nconc queue (list entry)))
167             (setcar (cdr entry) count)))
168         (setq pointer (cdr pointer)))
169       (setq queue (cdr queue)))
170     (if dependencies
171         (error "Circular add-on dependency found: %S" dependencies))
172     (nreverse addons)))
173
174 (defun riece-resolve-addons (addons)
175   ;; Add files in riece-addon-directory to addons.
176   (if (file-directory-p riece-addon-directory)
177       (setq addons (nconc
178                     addons
179                     (mapcar
180                      (lambda (name)
181                        (unless (file-directory-p
182                                 (expand-file-name name riece-addon-directory))
183                          (intern (file-name-sans-extension name))))
184                      (directory-files riece-addon-directory nil "\\`[^.]")))))
185   ;; Sort & uniquify.
186   (setq addons (sort addons (lambda (symbol1 symbol2)
187                               (string-lessp (symbol-name symbol1)
188                                             (symbol-name symbol2)))))
189   (let ((pointer addons))
190     (while pointer
191       (if (memq (car pointer) (cdr pointer))
192           (setcar pointer nil))
193       (setq pointer (cdr pointer)))
194     (delq nil addons))
195   ;; Build & resolve dependencies.
196   (riece-sort-addon-dependencies
197    (riece-load-and-build-addon-dependencies addons)))
198
199 (defun riece-insinuate-addon (addon &optional verbose)
200   (unless (assq addon riece-addon-dependencies)
201     (setq riece-addons (cons addon riece-addons)
202           riece-addon-dependencies (riece-resolve-addons
203                                     (copy-sequence riece-addons))))
204   (if (get addon 'riece-addon-insinuated)
205       (if verbose
206           (message "Add-on %S is already insinuated" addon))
207     (funcall (intern (concat (symbol-name addon) "-insinuate")))
208     (put addon 'riece-addon-insinuated t)
209     (if verbose
210         (message "Add-on %S is insinuated" addon))
211     (unless (get addon 'riece-addon-default-disabled)
212       (riece-enable-addon addon t))))
213
214 (defun riece-uninstall-addon (addon &optional verbose)
215   (if (not (get addon 'riece-addon-insinuated))
216       (if verbose
217           (message "Add-on %S is not insinuated" addon))
218     (let ((entry (assq addon riece-addon-dependencies))
219           (enabled (intern-soft (concat (symbol-name addon) "-enabled"))))
220       (if entry
221           (if (cdr entry)
222               (error "dependency problem: %S <- %S" addon (cdr entry))
223             (if (and enabled
224                      (symbol-value enabled))
225                 (riece-disable-addon addon verbose))
226             (funcall (or (intern-soft (concat (symbol-name addon)
227                                               "-uninstall"))
228                          #'ignore))
229             (setq riece-addon-dependencies
230                   (delq entry riece-addon-dependencies))
231             (remprop addon 'riece-addon-insinuated)
232             (setq riece-addons (delq addon riece-addons)
233                   riece-addon-dependencies (riece-resolve-addons
234                                             (copy-sequence riece-addons)))))
235       (if verbose
236           (message "Add-on %S is uninstalled" addon)))))
237
238 (defun riece-enable-addon (addon &optional verbose)
239   (unless (get addon 'riece-addon-insinuated)
240     (error "Add-on %S is not insinuated" addon))
241   (let ((enabled (intern-soft (concat (symbol-name addon) "-enabled"))))
242     (if (null enabled)
243         (if verbose
244             (message "Add-on %S doesn't support enable/disable" addon))
245       (if (symbol-value enabled)
246           (if verbose
247               (message "Add-on %S is already enabled" addon))
248         (funcall (intern (concat (symbol-name addon) "-enable")))
249         (if verbose
250             (message "Add-on %S enabled" addon))))))
251
252 (defun riece-disable-addon (addon &optional verbose)
253   (unless (get addon 'riece-addon-insinuated)
254     (error "Add-on %S is not insinuated" addon))
255   (let ((enabled (intern-soft (concat (symbol-name addon) "-enabled"))))
256     (if (null enabled)
257         (if verbose
258             (message "Add-on %S doesn't support enable/disable" addon))
259       (if (symbol-value enabled)
260           (progn
261             (funcall (intern (concat (symbol-name addon) "-disable")))
262             (if verbose
263                 (message "Add-on %S disabled" addon)))
264         (if verbose
265             (message "Add-on %S is already enabled" addon))))))
266
267 (put 'riece-addon-list-mode 'font-lock-defaults
268      '(riece-addon-list-font-lock-keywords t))
269
270 (defun riece-addon-list-mode ()
271   "Major mode for displaying addon list.
272 All normal editing commands are turned off."
273   (kill-all-local-variables)
274   (buffer-disable-undo)
275   (setq major-mode 'riece-addon-list-mode
276         mode-name "AddOns"
277         mode-line-buffer-identification
278         (riece-mode-line-buffer-identification '("Riece: %12b"))
279         truncate-lines t
280         buffer-read-only t)
281   (use-local-map riece-addon-list-mode-map)
282   (make-local-variable 'font-lock-defaults)
283   (setq font-lock-defaults '(riece-addon-list-font-lock-keywords t))
284   ;; In XEmacs, auto-initialization of font-lock is not effective
285   ;; if buffer-file-name is not set.
286   (font-lock-set-defaults)
287   (run-hooks 'riece-addon-list-mode-hook))
288
289 (defun riece-command-list-addons ()
290   (interactive)
291   (set-buffer (riece-get-buffer-create "*AddOns*" 'riece-addon-list-mode))
292   (riece-addon-list-mode)
293   (let ((inhibit-read-only t)
294         buffer-read-only
295         (pointer riece-addon-dependencies)
296         module-description-alist
297         description enabled point)
298     (while pointer
299       
300       (setq description (intern-soft (concat (symbol-name (car (car pointer)))
301                                              "-description"))
302             module-description-alist
303             (cons (cons (car (car pointer))
304                         (if description
305                             (symbol-value description)
306                           "(no description)"))
307                   module-description-alist)
308             pointer (cdr pointer)))
309     (setq pointer riece-addon-modules)
310     (while pointer
311       (unless (assq (car (car pointer))
312                     module-description-alist)
313         (setq module-description-alist
314               (cons (car pointer) module-description-alist)))
315       (setq pointer (cdr pointer)))
316     (erase-buffer)
317     (riece-kill-all-overlays)
318     (setq pointer (sort module-description-alist
319                         (lambda (entry1 entry2)
320                           (string-lessp (symbol-name (car entry1))
321                                         (symbol-name (car entry2))))))
322     (while pointer
323       (setq enabled (intern-soft (concat (symbol-name (car (car pointer)))
324                                          "-enabled")))
325       (setq point (point))
326       (insert (format "%c %S: %s\n"
327                       (if (not (get (car (car pointer))
328                                     'riece-addon-insinuated))
329                           ? 
330                         (if (null enabled)
331                             ?!
332                           (if (symbol-value enabled)
333                               ?+
334                             ?-)))
335                       (car (car pointer))
336                       (cdr (car pointer))))
337       (put-text-property point (point) 'riece-addon (car (car pointer)))
338       (setq pointer (cdr pointer)))
339     (insert "
340 Symbols in the leftmost column:
341
342   +     The add-on is enabled.
343   -     The add-on is disabled.
344   !     The add-on doesn't support enable/disable operation.
345         The add-on is not insinuated.
346 ")
347     (insert (substitute-command-keys "
348 Useful keys:
349
350   `\\[riece-command-enable-addon]' to enable the current add-on.
351   `\\[riece-command-disable-addon]' to disable the current add-on.
352 "))
353     (goto-char (point-min))
354     (pop-to-buffer (current-buffer))
355     (delete-other-windows)))
356
357 (defun riece-command-enable-addon (addon)
358   (interactive
359    (list
360     (or (if (eq major-mode 'riece-addon-list-mode)
361             (get-text-property (point) 'riece-addon))
362         (intern-soft
363          (completing-read "Add-on: "
364                           (mapcar (lambda (dependency)
365                                     (list (symbol-name (car dependency))))
366                                   riece-addon-dependencies)
367                           (lambda (pointer)
368                             (let ((enabled
369                                    (intern-soft (concat (car pointer)
370                                                         "-enabled"))))
371                               (and enabled
372                                    (null (symbol-value enabled)))))
373                           t)))))
374   (riece-enable-addon addon t)
375   (when (eq major-mode 'riece-addon-list-mode)
376     (riece-command-list-addons)
377     (let ((point (point-min)))
378       (while (and (not (eq (get-text-property point 'riece-addon) addon))
379                   (setq point (next-single-property-change point
380                                                            'riece-addon))))
381       (if point
382           (goto-char point)))))
383
384 (defun riece-command-disable-addon (addon)
385   (interactive
386    (list
387     (or (if (eq major-mode 'riece-addon-list-mode)
388             (get-text-property (point) 'riece-addon))
389         (intern-soft
390          (completing-read "Add-on: "
391                           (mapcar (lambda (dependency)
392                                     (list (symbol-name (car dependency))))
393                                   riece-addon-dependencies)
394                           (lambda (pointer)
395                             (let ((enabled
396                                    (intern-soft (concat (car pointer)
397                                                         "-enabled"))))
398                               (and enabled
399                                    (symbol-value enabled))))
400                           t)))))
401   (riece-disable-addon addon t)
402   (when (eq major-mode 'riece-addon-list-mode)
403     (riece-command-list-addons)
404     (let ((point (point-min)))
405       (while (and (not (eq (get-text-property point 'riece-addon) addon))
406                   (setq point (next-single-property-change point
407                                                            'riece-addon))))
408       (if point
409           (goto-char point)))))
410
411 (defun riece-command-insinuate-addon (addon)
412   (interactive
413    (list
414     (or (if (eq major-mode 'riece-addon-list-mode)
415             (get-text-property (point) 'riece-addon))
416         (intern-soft
417          (completing-read "Add-on: "
418                           (mapcar (lambda (dependency)
419                                     (list (symbol-name (car dependency))))
420                                   riece-addon-modules)
421                           (lambda (pointer)
422                             (get (car pointer) 'riece-addon-insinuated))
423                           t)))))
424   (riece-insinuate-addon addon t)
425   (when (eq major-mode 'riece-addon-list-mode)
426     (riece-command-list-addons)
427     (let ((point (point-min)))
428       (while (and (not (eq (get-text-property point 'riece-addon) addon))
429                   (setq point (next-single-property-change point
430                                                            'riece-addon))))
431       (if point
432           (goto-char point)))))
433
434 (defun riece-command-uninstall-addon (addon)
435   (interactive
436    (list
437     (or (if (eq major-mode 'riece-addon-list-mode)
438             (get-text-property (point) 'riece-addon))
439         (intern-soft
440          (completing-read "Add-on: "
441                           (mapcar (lambda (dependency)
442                                     (list (symbol-name (car dependency))))
443                                   riece-addon-dependencies)
444                           nil t)))))
445   (riece-uninstall-addon addon t)
446   (when (eq major-mode 'riece-addon-list-mode)
447     (riece-command-list-addons)
448     (let ((point (point-min)))
449       (while (and (not (eq (get-text-property point 'riece-addon) addon))
450                   (setq point (next-single-property-change point
451                                                            'riece-addon))))
452       (if point
453           (goto-char point)))))
454
455 (provide 'riece-addon)
456
457 ;;; riece-addon.el ends here