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