;;; assistant.el --- guiding users through Emacs setup ;; Copyright (C) 2004 Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: util ;; This file is part of GNU Emacs. ;; GNU Emacs is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 2, or (at your option) ;; any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs; see the file COPYING. If not, write to the ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, ;; Boston, MA 02111-1307, USA. ;;; Commentary: ;;; Code: (eval-when-compile (require 'cl)) (defvar assistant-readers '(("variable" assistant-variable-reader) ("validate" assistant-sexp-reader) ("result" assistant-list-reader) ("next" assistant-list-reader) ("text" assistant-text-reader))) (defface assistant-field-face '((t (:bold t))) "Face used for editable fields." :group 'gnus-article-emphasis) ;;; Internal variables (defvar assistant-data nil) (defvar assistant-current-node nil) (defvar assistant-previous-node nil) (defvar assistant-widgets nil) (defun assistant-parse-buffer () (let (results command value) (goto-char (point-min)) (while (search-forward "@" nil t) (if (not (looking-at "[^ \t\n]+")) (error "Dangling @") (setq command (downcase (match-string 0))) (goto-char (match-end 0))) (setq value (if (looking-at "[ \t]*\n") (let (start) (forward-line 1) (setq start (point)) (unless (re-search-forward (concat "^@end " command) nil t) (error "No @end %s found" command)) (beginning-of-line) (prog1 (buffer-substring start (point)) (forward-line 1))) (skip-chars-forward " \t") (prog1 (buffer-substring (point) (line-end-position)) (forward-line 1)))) (push (list command (assistant-reader command value)) results)) (assistant-segment (nreverse results)))) (defun assistant-text-reader (text) (with-temp-buffer (insert text) (goto-char (point-min)) (let ((start (point)) (sections nil)) (while (re-search-forward "@\\([^{]+\\){\\([^}]+\\)}" nil t) (push (buffer-substring start (match-beginning 0)) sections) (push (list (match-string 1) (match-string 2)) sections) (setq start (point))) (push (buffer-substring start (point-max)) sections) (nreverse sections)))) ;; Segment the raw assistant data into a list of nodes. (defun assistant-segment (list) (let ((ast nil) (node nil) (title (pop list))) (dolist (elem list) (when (and (equal (car elem) "node") node) (push (list "save" nil) node) (push (nreverse node) ast) (setq node nil)) (push elem node)) (when node (push (list "save" nil) node) (push (nreverse node) ast)) (cons title (nreverse ast)))) (defun assistant-reader (command value) (let ((formatter (cadr (assoc command assistant-readers)))) (if (not formatter) value (funcall formatter value)))) (defun assistant-list-reader (value) (car (read-from-string (concat "(" value ")")))) (defun assistant-variable-reader (value) (let ((section (car (read-from-string (concat "(" value ")"))))) (append section (list 'default)))) (defun assistant-sexp-reader (value) (if (zerop (length value)) nil (car (read-from-string value)))) (defun assistant-buffer-name (title) (format "*Assistant %s*" title)) (defun assistant-get (ast command) (cadr (assoc command ast))) (defun assistant-set (ast command value) (let ((elem (assoc command ast))) (when elem (setcar (cdr elem) value)))) (defun assistant-get-list (ast command) (let ((result nil)) (dolist (elem ast) (when (equal (car elem) command) (push elem result))) (nreverse result))) ;;;###autoload (defun assistant (file) "Assist setting up Emacs based on FILE." (interactive "fAssistant file name: ") (let ((ast (with-temp-buffer (insert-file-contents file) (assistant-parse-buffer)))) (pop-to-buffer (assistant-buffer-name (assistant-get ast "title"))) (assistant-render ast))) (defun assistant-render (ast) (let ((first-node (assistant-get (nth 1 ast) "node"))) (set (make-local-variable 'assistant-data) ast) (set (make-local-variable 'assistant-current-node) first-node) (set (make-local-variable 'assistant-previous-node) nil) (assistant-render-node first-node))) (defun assistant-find-node (node-name) (let ((ast (cdr assistant-data))) (while (and ast (not (string= node-name (assistant-get (car ast) "node")))) (pop ast)) (car ast))) (defun assistant-previous-node-text (node) (format "[ << Go back to %s ] " node)) (defun assistant-next-node-text (node) (if node (format "[ Proceed to %s >> ]" node) "[ Finish ]")) (defun assistant-set-defaults (node) (dolist (variable (assistant-get-list node "variable")) (setq variable (cadr variable)) (when (eq (nth 3 variable) 'default) (setcar (nthcdr 3 variable) (eval (nth 2 variable)))))) (defun assistant-get-variable (node variable) (let ((variables (assistant-get-list node "variable")) (result nil)) (while (and (setq elem (pop variables)) (not result)) (setq elem (cadr elem)) (when (eq (intern variable) (car elem)) (setq result (format "%s" (nth 3 elem))))) result)) (defun assistant-set-variable (node variable value) (let ((variables (assistant-get-list node "variable"))) (while (setq elem (pop variables)) (setq elem (cadr elem)) (when (eq (intern variable) (car elem)) (setcar (nthcdr 3 elem) value))))) (defun assistant-render-text (text node) (dolist (elem text) (if (stringp elem) (insert elem) (push (widget-create 'editable-field :value-face 'assistant-field-face :assistant-variable (cadr elem) (assistant-get-variable node (cadr elem))) assistant-widgets)))) (defun assistant-render-node (node-name) (let ((node (assistant-find-node node-name))) (set (make-local-variable 'assistant-widgets) nil) (assistant-set-defaults node) (setq assistant-current-node node-name) (erase-buffer) (insert (cadar assistant-data) "\n\n") (insert node-name "\n\n") (assistant-render-text (assistant-get node "text") node) (insert "\n\n") (when assistant-previous-node (assistant-node-button 'previous assistant-previous-node)) (assistant-node-button 'next (assistant-find-next-node)) (insert "\n"))) (defun assistant-node-button (type node) (let ((text (if (eq type 'next) (assistant-next-node-text node) (assistant-previous-node-text node)))) (widget-create 'push-button :assistant-node node :assistant-type type :notify (lambda (widget &rest ignore) (let* ((node (widget-get widget :assistant-node)) (type (widget-get widget :assistant-type))) (when (eq type 'next) (assistant-get-widget-values) (assistant-validate)) (if (null node) (assistant-finish) (assistant-render-node node)))) text) (use-local-map widget-keymap))) (defun assistant-validate-types (node) (dolist (variable (assistant-get-list node "variable")) (setq variable (cadr variable)) (let ((type (nth 1 variable)) (value (nth 3 variable))) (when (cond ((eq type :number) (string-match "[^0-9]" value)) (t nil)) (error "%s is not of type %s: %s" (car variable) type value))))) (defun assistant-get-widget-values () (let ((node (assistant-find-node assistant-current-node))) (dolist (widget assistant-widgets) (assistant-set-variable node (widget-get widget :assistant-variable) (widget-value widget))))) (defun assistant-validate () (let* ((node (assistant-find-node assistant-current-node)) (validation (assistant-get node "validate")) result) (assistant-validate-types node) (when validation (when (setq result (assistant-eval validation node)) (unless (y-or-n-p (format "Error: %s. Continue? " result)) (error "%s" result)))) (assistant-set node "save" t))) (defun assistant-find-next-node () (let* ((node (assistant-find-node assistant-current-node)) (nexts (assistant-get-list node "next")) next elem) (while (and (setq elem (pop nexts)) (not next)) (when (assistant-eval (car elem) node) (setq next (cadr elem)))) next)) (defun assistant-eval (form node) (let ((bindings nil)) (dolist (variable (assistant-get-list node "variable")) (setq variable (cadr variable)) (push (list (car variable) (nth 3 variable)) bindings)) (eval `(let ,bindings ,form)))) (defun assistant-finish () (let ((results nil) result) (dolist (node (cdr assistant-data)) (when (assistant-get node "save") (setq result (assistant-get node "result")) (push (list (car result) (assistant-eval (cadr result) node)) results))) (message "Results: %s" (nreverse results)))) (provide 'assistant) ;;; arch-tag: 0404bfa2-9226-4611-8d3f-335c2416175b ;;; assistant.el ends here