e31a139d208f1802d72cc92ec4c9b1916369b531
[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-nodes 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) nil)
163     (set (make-local-variable 'assistant-previous-nodes) 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 (and node
178            (not (eq node 'finish)))
179       (format "Proceed to %s >>" node)
180     "Finish"))
181
182 (defun assistant-set-defaults (node &optional forcep)
183   (dolist (variable (assistant-get-list node "variable"))
184     (setq variable (cadr variable))
185     (when (or (eq (nth 3 variable) 'default)
186               forcep)
187       (setcar (nthcdr 3 variable)
188               (assistant-eval (nth 2 variable))))))
189
190 (defun assistant-get-variable (node variable &optional type raw)
191   (let ((variables (assistant-get-list node "variable"))
192         (result nil)
193         elem)
194     (while (and (setq elem (pop variables))
195                 (not result))
196       (setq elem (cadr elem))
197       (when (eq (intern variable) (car elem))
198         (if type
199             (setq result (nth 1 elem))
200           (setq result (if raw (nth 3 elem)
201                          (format "%s" (nth 3 elem)))))))
202     result))
203     
204 (defun assistant-set-variable (node variable value)
205   (let ((variables (assistant-get-list node "variable"))
206         elem)
207     (while (setq elem (pop variables))
208       (setq elem (cadr elem))
209       (when (eq (intern variable) (car elem))
210         (setcar (nthcdr 3 elem) value)))))
211     
212 (defun assistant-render-text (text node)
213   (dolist (elem text)
214     (if (stringp elem)
215         ;; Ordinary text
216         (insert elem)
217       ;; A variable to be inserted as a widget.
218       (let* ((start (point))
219              (variable (cadr elem))
220              (type (assistant-get-variable node variable 'type)))
221         (cond
222          ((and (consp type)
223                (eq (car type) :radio))
224           (push 
225            (apply
226             #'widget-create
227             'radio-button-choice
228             :assistant-variable variable
229             :assistant-node node
230             :value (assistant-get-variable node variable)
231             :notify (lambda (widget &rest ignore)
232                       (assistant-set-variable
233                        (widget-get widget :assistant-node)
234                        (widget-get widget :assistant-variable)
235                        (widget-value widget))
236                       (assistant-render-node
237                        (assistant-get
238                         (widget-get widget :assistant-node)
239                         "node")))
240             (cadr type))
241            assistant-widgets))
242          (t
243           (push 
244            (widget-create
245             'editable-field
246             :value-face 'assistant-field-face
247             :assistant-variable variable
248             (assistant-get-variable node variable))
249            assistant-widgets)
250           ;; The editable-field widget apparently inserts a newline;
251           ;; remove it.
252           (delete-char -1)
253           (add-text-properties start (point)
254                                (list
255                                 'bold t
256                                 'face 'assistant-field-face
257                                 'not-read-only t))))))))
258
259 (defun assistant-render-node (node-name)
260   (let ((node (assistant-find-node node-name))
261         (inhibit-read-only t)
262         (previous assistant-current-node)
263         (buffer-read-only nil))
264     (set (make-local-variable 'assistant-widgets) nil)
265     (assistant-set-defaults node)
266     (if (equal (assistant-get node "type") "interstitial")
267         (assistant-render-node (assistant-find-next-node node-name))
268       (setq assistant-current-node node-name)
269       (when previous
270         (push previous assistant-previous-nodes))
271       (erase-buffer)
272       (insert (cadar assistant-data) "\n\n")
273       (insert node-name "\n\n")
274       (assistant-render-text (assistant-get node "text") node)
275       (insert "\n\n")
276       (when assistant-previous-nodes
277         (assistant-node-button 'previous (car assistant-previous-nodes)))
278       (widget-create
279        'push-button
280        :assistant-node node-name
281        :notify (lambda (widget &rest ignore)
282                  (let* ((node (widget-get widget :assistant-node)))
283                    (assistant-set-defaults (assistant-find-node node) 'force)
284                    (assistant-render-node node)))
285        "Reset")
286       (insert " ")
287       (assistant-node-button 'next (assistant-find-next-node))
288       (insert "\n")
289       (goto-char (point-min))
290       (assistant-make-read-only))))
291
292 (defun assistant-make-read-only ()
293   (let ((start (point-min))
294         end)
295     (while (setq end (text-property-any start (point-max) 'not-read-only t))
296       (put-text-property start end 'read-only t)
297       (put-text-property start end 'rear-nonsticky t)
298       (while (get-text-property end 'not-read-only)
299         (incf end))
300       (setq start end))
301     (put-text-property start (point-max) 'read-only t)))
302
303 (defun assistant-node-button (type node)
304   (let ((text (if (eq type 'next)
305                   (assistant-next-node-text node)
306                 (assistant-previous-node-text node))))
307     (widget-create
308      'push-button
309      :assistant-node node
310      :assistant-type type
311      :notify (lambda (widget &rest ignore)
312                (let* ((node (widget-get widget :assistant-node))
313                       (type (widget-get widget :assistant-type)))
314                  (if (eq type 'previous)
315                      (progn
316                        (setq assistant-current-node nil)
317                        (pop assistant-previous-nodes))
318                    (assistant-get-widget-values)
319                    (assistant-validate))
320                  (if (null node)
321                      (assistant-finish)
322                    (assistant-render-node node))))
323      text)
324     (use-local-map widget-keymap)))
325
326 (defun assistant-validate-types (node)
327   (dolist (variable (assistant-get-list node "variable"))
328     (setq variable (cadr variable))
329     (let ((type (nth 1 variable))
330           (value (nth 3 variable)))
331       (when 
332           (cond
333            ((eq type :number)
334             (string-match "[^0-9]" value))
335            (t
336             nil))
337         (error "%s is not of type %s: %s"
338                (car variable) type value)))))
339
340 (defun assistant-get-widget-values ()
341   (let ((node (assistant-find-node assistant-current-node)))
342     (dolist (widget assistant-widgets)
343       (assistant-set-variable
344        node (widget-get widget :assistant-variable)
345        (widget-value widget)))))
346
347 (defun assistant-validate ()
348   (let* ((node (assistant-find-node assistant-current-node))
349          (validation (assistant-get node "validate"))
350          result)
351     (assistant-validate-types node)
352     (when validation
353       (when (setq result (assistant-eval validation))
354         (unless (y-or-n-p (format "Error: %s.  Continue? " result))
355           (error "%s" result))))
356     (assistant-set node "save" t)))
357
358 (defun assistant-find-next-node (&optional node)
359   (let* ((node (assistant-find-node (or node assistant-current-node)))
360          (nexts (assistant-get-list node "next"))
361          next elem)
362     (while (and (setq elem (cadr (pop nexts)))
363                 (not next))
364       (when (setq next (assistant-eval (car elem)))
365         (setq next (or (cadr elem) next))))
366     next))
367
368 (defun assistant-get-all-variables ()
369   (let ((variables nil))
370     (dolist (node (cdr assistant-data))
371       (setq variables
372             (append (assistant-get-list node "variable")
373                     variables)))
374     variables))
375   
376 (defun assistant-eval (form)
377   (let ((bindings nil))
378     (dolist (variable (assistant-get-all-variables))
379       (setq variable (cadr variable))
380       (push (list (car variable) (if (eq (nth 3 variable) 'default)
381                                      nil
382                                    (nth 3 variable)))
383             bindings))
384     (eval
385      `(let ,bindings
386         ,form))))
387
388 (defun assistant-finish ()
389   (let ((results nil)
390         result)
391     (dolist (node (cdr assistant-data))
392       (when (assistant-get node "save")
393         (setq result (assistant-get node "result"))
394         (push (list (car result)
395                     (assistant-eval (cadr result)))
396               results)))
397     (message "Results: %s"
398              (nreverse results))))
399
400 ;;; Validation functions.
401
402 (defun assistant-validate-connect-to-server (server port)
403   (let* ((error nil)
404          (stream
405           (condition-case err
406               (open-network-stream "nntpd" nil server port)
407             (error (setq error err)))))
408     (if (and (processp stream)
409              (memq (process-status stream) '(open run)))
410         (progn
411           (delete-process stream)
412           nil)
413       error)))
414
415 (defun assistant-authinfo-data (server port type)
416   (when (file-exists-p "~/.authinfo")
417     (netrc-get (netrc-machine (netrc-parse "~/.authinfo")
418                               server port)
419                (if (eq type 'user)
420                    "login"
421                  "password"))))
422
423 (defun assistant-password-required-p ()
424   nil)
425
426 (provide 'assistant)
427
428 ;;; arch-tag: 0404bfa2-9226-4611-8d3f-335c2416175b
429 ;;; assistant.el ends here