Continue...
[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
37 ;;; Internal variables
38
39 (defvar assistant-data nil)
40 (defvar assistant-current-node nil)
41
42 (defun assistant-parse-buffer ()
43   (let (results command value)
44     (goto-char (point-min))
45     (while (search-forward "@" nil t)
46       (if (not (looking-at "[^ \t\n]+"))
47           (error "Dangling @")
48         (setq command (downcase (match-string 0)))
49         (goto-char (match-end 0)))
50       (setq value
51             (if (looking-at "[ \t]*\n")
52                 (let (start)
53                   (forward-line 1)
54                   (setq start (point))
55                   (unless (re-search-forward (concat "^@end " command) nil t)
56                     (error "No @end %s found" command))
57                   (beginning-of-line)
58                   (prog1
59                       (buffer-substring start (point))
60                     (forward-line 1)))
61               (skip-chars-forward " \t")
62               (prog1
63                   (buffer-substring (point) (line-end-position))
64                 (forward-line 1))))
65       (push (list command (assistant-reader command value))
66             results))
67     (assistant-segment (nreverse results))))
68
69 ;; Segment the raw assistant data into a list of nodes.
70 (defun assistant-segment (list)
71   (let ((ast nil)
72         (node nil)
73         (title (pop list)))
74     (dolist (elem list)
75       (when (and (equal (car elem) "node")
76                  node)
77         (push (list "save" nil) node)
78         (push (nreverse node) ast)
79         (setq node nil))
80       (push elem node))
81     (when node
82       (push (list "save" nil) node)
83       (push (nreverse node) ast))
84     (cons title (nreverse ast))))
85
86 (defun assistant-reader (command value)
87   (let ((formatter (cadr (assoc command assistant-readers))))
88     (if (not formatter)
89         value
90       (funcall formatter value))))
91
92 (defun assistant-list-reader (value)
93   (car (read-from-string (concat "(" value ")"))))
94
95 (defun assistant-variable-reader (value)
96   (let ((section (car (read-from-string (concat "(" value ")")))))
97     (append section (list (nth 2 section)))))
98
99 (defun assistant-sexp-reader (value)
100   (if (zerop (length value))
101       nil
102     (car (read-from-string value))))
103
104 (defun assistant-buffer-name (title)
105   (format "*Assistant %s*" title))
106
107 (defun assistant-get (ast command)
108   (cadr (assoc command ast)))
109
110 (defun assistant-set (ast command value)
111   (let ((elem (assoc command ast)))
112     (when elem
113       (setcar (nthcdr 3 elem) value))))
114
115 (defun assistant-get-list (ast command)
116   (let ((result nil))
117     (dolist (elem ast)
118       (when (equal (car elem) command)
119         (push elem result)))
120     (nreverse result)))
121
122 (defun assistant (file)
123   "Assist setting up Emacs based on FILE."
124   (interactive "fAssistant file name: ")
125   (let ((ast
126          (with-temp-buffer
127            (insert-file-contents file)
128            (assistant-parse-buffer))))
129     (pop-to-buffer (assistant-buffer-name (assistant-get ast "title")))
130     (assistant-render ast)))
131
132 (defun assistant-render (ast)
133   (let ((first-node (assistant-get (nth 1 ast) "node")))
134     (set (make-local-variable 'assistant-data) ast)
135     (set (make-local-variable 'assistant-current-node) first-node)
136     (set (make-local-variable 'assistant-previous-node) nil)
137     (assistant-render-node first-node)))
138
139 (defun assistant-find-node (node-name)
140   (let ((ast (cdr assistant-data)))
141     (while (and ast
142                 (not (string= node-name (assistant-get (car ast) "node"))))
143       (pop ast))
144     (car ast)))
145
146 (defun assistant-previous-node-text (node)
147   (format "[ << Go back to %s ]  " node))
148
149 (defun assistant-next-node-text (node)
150   (if node
151       (format "[ Proceed to %s >> ]" node)
152     "[ Finish ]"))
153
154 (defun assistant-render-node (node-name)
155   (let ((node (assistant-find-node node-name)))
156     (setq assistant-current-node node-name)
157     (erase-buffer)
158     (insert (cadar assistant-data) "\n\n")
159     (insert node-name "\n\n")
160     (insert (assistant-get node "text") "\n\n")
161     (when assistant-previous-node
162       (assistant-node-button 'previous assistant-previous-node))
163     (assistant-node-button 'next (assistant-find-next-node))
164     (insert "\n")))
165
166 (defun assistant-node-button (type node)
167   (let ((text (if (eq type 'next)
168                   (assistant-next-node-text node)
169                 (assistant-previous-node-text node))))
170     (widget-create
171      'push-button
172      :assistant-node node
173      :assistant-type type
174      :notify (lambda (widget &rest ignore)
175                (let* ((node (widget-get widget ':assistant-node))
176                       (type (widget-get widget ':assistant-type)))
177                  (when (eq type 'next)
178                    (assistant-validate node))
179                  (if (null node)
180                      (assistant-finish)
181                    (assistant-render-node node))))
182      text)
183     (use-local-map widget-keymap)))
184
185 (defun assistant-validate (node-name)
186   (let* ((node (assistant-find-node node-name))
187          (validation (assistant-get node "validate"))
188          result)
189     (when validation
190       (when (setq result (assistant-eval validation node))
191         (unless (y-or-n-p (format "Error: %s.  Continue? " result))
192           (error "%s" result))))
193     (assistant-set node "save" t)))
194
195 (defun assistant-find-next-node ()
196   (let* ((node (assistant-find-node node-name))
197          (nexts (assistant-get-list node "next"))
198          next)
199     (while (and (setq elem (pop nexts))
200                 (not next))
201       (when (assistant-eval (car elem) node)
202         (setq next (cadr elem))))
203     next))
204       
205 (defun assistant-eval (form node)
206   (let ((bindings nil))
207     (dolist (variable (assistant-get-list node "variable"))
208       (push (list (car variable) (nth 3 variable))
209             bingdings))
210     (eval
211      `(let ,bindings
212         ,@form))))
213
214 (defun assistant-finish ()
215   (let ((results nil)
216         result)
217     (dolist (node (cdr assistant-data))
218       (when (assistant-get node "save")
219         (setq result (assistant-get node "result"))
220         (push (list (car result)
221                     (assistant-eval (cadr result) node))
222               results)))
223     (message "Results: "
224              (nreverse results))))
225
226 (provide 'assistant)