(wid-edit): Fix compilation.
[gnus] / lisp / assistant.el
1 ;;; assistant.el --- guiding users through Emacs setup
2 ;; Copyright (C) 2004 Free Software Foundation, Inc.
3
4 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
5 ;; Keywords: util
6
7 ;; This file is part of GNU Emacs.
8
9 ;; GNU Emacs is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; any later version.
13
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
21 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 ;; Boston, MA 02111-1307, USA.
23
24 ;;; Commentary:
25
26 ;;; Code:
27
28 (eval-when-compile
29   (require 'cl))
30
31 (require 'widget)
32 (require 'wid-edit)
33
34 (defvar assistant-readers
35   '(("variable" assistant-variable-reader)
36     ("validate" assistant-sexp-reader)
37     ("result" assistant-list-reader)
38     ("next" assistant-list-reader)
39     ("text" assistant-text-reader)))
40
41 (defface assistant-field-face '((t (:bold t)))
42   "Face used for editable fields."
43   :group 'gnus-article-emphasis)
44
45 ;;; Internal variables
46
47 (defvar assistant-data nil)
48 (defvar assistant-current-node nil)
49 (defvar assistant-previous-node nil)
50 (defvar assistant-widgets nil)
51
52 (defun assistant-parse-buffer ()
53   (let (results command value)
54     (goto-char (point-min))
55     (while (search-forward "@" nil t)
56       (if (not (looking-at "[^ \t\n]+"))
57           (error "Dangling @")
58         (setq command (downcase (match-string 0)))
59         (goto-char (match-end 0)))
60       (setq value
61             (if (looking-at "[ \t]*\n")
62                 (let (start)
63                   (forward-line 1)
64                   (setq start (point))
65                   (unless (re-search-forward (concat "^@end " command) nil t)
66                     (error "No @end %s found" command))
67                   (beginning-of-line)
68                   (prog1
69                       (buffer-substring start (point))
70                     (forward-line 1)))
71               (skip-chars-forward " \t")
72               (prog1
73                   (buffer-substring (point) (line-end-position))
74                 (forward-line 1))))
75       (push (list command (assistant-reader command value))
76             results))
77     (assistant-segment (nreverse results))))
78
79 (defun assistant-text-reader (text)
80   (with-temp-buffer
81     (insert text)
82     (goto-char (point-min))
83     (let ((start (point))
84           (sections nil))
85       (while (re-search-forward "@\\([^{]+\\){\\([^}]+\\)}" nil t)
86         (push (buffer-substring start (match-beginning 0))
87               sections)
88         (push (list (match-string 1) (match-string 2))
89               sections)
90         (setq start (point)))
91       (push (buffer-substring start (point-max))
92             sections)
93       (nreverse sections))))
94
95 ;; Segment the raw assistant data into a list of nodes.
96 (defun assistant-segment (list)
97   (let ((ast nil)
98         (node nil)
99         (title (pop list)))
100     (dolist (elem list)
101       (when (and (equal (car elem) "node")
102                  node)
103         (push (list "save" nil) node)
104         (push (nreverse node) ast)
105         (setq node nil))
106       (push elem node))
107     (when node
108       (push (list "save" nil) node)
109       (push (nreverse node) ast))
110     (cons title (nreverse ast))))
111
112 (defun assistant-reader (command value)
113   (let ((formatter (cadr (assoc command assistant-readers))))
114     (if (not formatter)
115         value
116       (funcall formatter value))))
117
118 (defun assistant-list-reader (value)
119   (car (read-from-string (concat "(" value ")"))))
120
121 (defun assistant-variable-reader (value)
122   (let ((section (car (read-from-string (concat "(" value ")")))))
123     (append section (list 'default))))
124
125 (defun assistant-sexp-reader (value)
126   (if (zerop (length value))
127       nil
128     (car (read-from-string value))))
129
130 (defun assistant-buffer-name (title)
131   (format "*Assistant %s*" title))
132
133 (defun assistant-get (ast command)
134   (cadr (assoc command ast)))
135
136 (defun assistant-set (ast command value)
137   (let ((elem (assoc command ast)))
138     (when elem
139       (setcar (cdr elem) value))))
140
141 (defun assistant-get-list (ast command)
142   (let ((result nil))
143     (dolist (elem ast)
144       (when (equal (car elem) command)
145         (push elem result)))
146     (nreverse result)))
147
148 ;;;###autoload
149 (defun assistant (file)
150   "Assist setting up Emacs based on FILE."
151   (interactive "fAssistant file name: ")
152   (let ((ast
153          (with-temp-buffer
154            (insert-file-contents file)
155            (assistant-parse-buffer))))
156     (pop-to-buffer (assistant-buffer-name (assistant-get ast "title")))
157     (assistant-render ast)))
158
159 (defun assistant-render (ast)
160   (let ((first-node (assistant-get (nth 1 ast) "node")))
161     (set (make-local-variable 'assistant-data) ast)
162     (set (make-local-variable 'assistant-current-node) first-node)
163     (set (make-local-variable 'assistant-previous-node) nil)
164     (assistant-render-node first-node)))
165
166 (defun assistant-find-node (node-name)
167   (let ((ast (cdr assistant-data)))
168     (while (and ast
169                 (not (string= node-name (assistant-get (car ast) "node"))))
170       (pop ast))
171     (car ast)))
172
173 (defun assistant-previous-node-text (node)
174   (format "[ << Go back to %s ]  " node))
175
176 (defun assistant-next-node-text (node)
177   (if node
178       (format "Proceed to %s >>" node)
179     "Finish"))
180
181 (defun assistant-set-defaults (node &optional forcep)
182   (dolist (variable (assistant-get-list node "variable"))
183     (setq variable (cadr variable))
184     (when (or (eq (nth 3 variable) 'default)
185               forcep)
186       (setcar (nthcdr 3 variable)
187               (eval (nth 2 variable))))))
188
189 (defun assistant-get-variable (node variable)
190   (let ((variables (assistant-get-list node "variable"))
191         (result nil)
192         elem)
193     (while (and (setq elem (pop variables))
194                 (not result))
195       (setq elem (cadr elem))
196       (when (eq (intern variable) (car elem))
197         (setq result (format "%s" (nth 3 elem)))))
198     result))
199     
200 (defun assistant-set-variable (node variable value)
201   (let ((variables (assistant-get-list node "variable"))
202         elem)
203     (while (setq elem (pop variables))
204       (setq elem (cadr elem))
205       (when (eq (intern variable) (car elem))
206         (setcar (nthcdr 3 elem) value)))))
207     
208 (defun assistant-render-text (text node)
209   (dolist (elem text)
210     (if (stringp elem)
211         (insert elem)
212       (let ((start (point)))
213         (push 
214          (widget-create
215           'editable-field
216           :value-face 'assistant-field-face
217           :assistant-variable (cadr elem)
218           (assistant-get-variable node (cadr elem)))
219          assistant-widgets)
220         ;; The editable-field widget apparently inserts a newline;
221         ;; remove it.
222         (delete-char -1)
223         (add-text-properties start (point)
224                              (list
225                               'bold t
226                               'face 'assistant-field-face
227                               'not-read-only t))))))
228
229 (defun assistant-render-node (node-name)
230   (let ((node (assistant-find-node node-name))
231         (inhibit-read-only t)
232         (buffer-read-only nil))
233     (set (make-local-variable 'assistant-widgets) nil)
234     (assistant-set-defaults node)
235     (setq assistant-current-node node-name)
236     (erase-buffer)
237     (insert (cadar assistant-data) "\n\n")
238     (insert node-name "\n\n")
239     (assistant-render-text (assistant-get node "text") node)
240     (insert "\n\n")
241     (when assistant-previous-node
242       (assistant-node-button 'previous assistant-previous-node))
243     (widget-create
244      'push-button
245      :assistant-node node-name
246      :notify (lambda (widget &rest ignore)
247                (let* ((node (widget-get widget :assistant-node)))
248                  (assistant-set-defaults (assistant-find-node node) 'force)
249                  (assistant-render-node node)))
250      "Reset")
251     (insert " ")
252     (assistant-node-button 'next (assistant-find-next-node))
253     (insert "\n")
254     (goto-char (point-min))
255     (assistant-make-read-only)))
256
257 (defun assistant-make-read-only ()
258   (let ((start (point-min))
259         end)
260     (while (setq end (text-property-any start (point-max) 'not-read-only t))
261       (put-text-property start end 'read-only t)
262       (put-text-property start end 'rear-nonsticky t)
263       (while (get-text-property end 'not-read-only)
264         (incf end))
265       (setq start end))
266     (put-text-property start (point-max) 'read-only t)))
267
268 (defun assistant-node-button (type node)
269   (let ((text (if (eq type 'next)
270                   (assistant-next-node-text node)
271                 (assistant-previous-node-text node))))
272     (widget-create
273      'push-button
274      :assistant-node node
275      :assistant-type type
276      :notify (lambda (widget &rest ignore)
277                (let* ((node (widget-get widget :assistant-node))
278                       (type (widget-get widget :assistant-type)))
279                  (when (eq type 'next)
280                    (assistant-get-widget-values)
281                    (assistant-validate))
282                  (if (null node)
283                      (assistant-finish)
284                    (assistant-render-node node))))
285      text)
286     (use-local-map widget-keymap)))
287
288 (defun assistant-validate-types (node)
289   (dolist (variable (assistant-get-list node "variable"))
290     (setq variable (cadr variable))
291     (let ((type (nth 1 variable))
292           (value (nth 3 variable)))
293       (when 
294           (cond
295            ((eq type :number)
296             (string-match "[^0-9]" value))
297            (t
298             nil))
299         (error "%s is not of type %s: %s"
300                (car variable) type value)))))
301
302 (defun assistant-get-widget-values ()
303   (let ((node (assistant-find-node assistant-current-node)))
304     (dolist (widget assistant-widgets)
305       (assistant-set-variable
306        node (widget-get widget :assistant-variable)
307        (widget-value widget)))))
308
309 (defun assistant-validate ()
310   (let* ((node (assistant-find-node assistant-current-node))
311          (validation (assistant-get node "validate"))
312          result)
313     (assistant-validate-types node)
314     (when validation
315       (when (setq result (assistant-eval validation node))
316         (unless (y-or-n-p (format "Error: %s.  Continue? " result))
317           (error "%s" result))))
318     (assistant-set node "save" t)))
319
320 (defun assistant-find-next-node ()
321   (let* ((node (assistant-find-node assistant-current-node))
322          (nexts (assistant-get-list node "next"))
323          next elem)
324     (while (and (setq elem (pop nexts))
325                 (not next))
326       (when (assistant-eval (car elem) node)
327         (setq next (cadr elem))))
328     next))
329       
330 (defun assistant-eval (form node)
331   (let ((bindings nil))
332     (dolist (variable (assistant-get-list node "variable"))
333       (setq variable (cadr variable))
334       (push (list (car variable) (nth 3 variable))
335             bindings))
336     (eval
337      `(let ,bindings
338         ,form))))
339
340 (defun assistant-finish ()
341   (let ((results nil)
342         result)
343     (dolist (node (cdr assistant-data))
344       (when (assistant-get node "save")
345         (setq result (assistant-get node "result"))
346         (push (list (car result)
347                     (assistant-eval (cadr result) node))
348               results)))
349     (message "Results: %s"
350              (nreverse results))))
351
352 ;;; Validation functions.
353
354 (defun assistant-validate-connect-to-server (server port)
355   (let* ((error nil)
356          (stream
357           (condition-case err
358               (open-network-stream "nntpd" nil server port)
359             (error (setq error err)))))
360     (if (and (processp stream)
361              (memq (process-status stream) '(open run)))
362         (progn
363           (delete-process stream)
364           nil)
365       error)))
366
367 (provide 'assistant)
368
369 ;;; arch-tag: 0404bfa2-9226-4611-8d3f-335c2416175b
370 ;;; assistant.el ends here