3 ;;; Filling in skeletons
5 ;;; (c) 1998 Mark Wooding
8 ;;;----- Licensing notice ---------------------------------------------------
10 ;;; This program is free software; you can redistribute it and/or modify
11 ;;; it under the terms of the GNU General Public License as published by
12 ;;; the Free Software Foundation; either version 2 of the License, or
13 ;;; (at your option) any later version.
15 ;;; This program is distributed in the hope that it will be useful,
16 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;;; GNU General Public License for more details.
20 ;;; You should have received a copy of the GNU General Public License
21 ;;; along with this program; if not, write to the Free Software Foundation,
22 ;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 ;;;----- Variables (largely tweakable) --------------------------------------
26 (defvar skel-directory-list '(".skel" "")
27 "*List of directory names which contain skeleton files.")
29 (defvar skel-skeleton-path '("/etc/skel" "@skeldir@" "~/skel" "~/src/skel")
30 "*List of directories to search for skeleton data anyway.")
32 (defvar skel-skelrc '(".skelrc" "skelrc")
33 "*File containing skeleton substitution data, looked up using the standard
34 skeleton search mathod.")
36 (defvar skel-alist '()
37 "Alist of values to substitute into a skeleton. It is filled in by skelrc
38 files and user interaction.
40 The alist's keys are symbols, interned from the placeholder strings in the
41 skeleton file. The values are forms to be evaluated. In the simplest
42 case, the form is a string added as a result of user interaction; however,
43 it could just as easily be a function call or something similarly
46 ;;;----- Finding skeleton files ---------------------------------------------
48 (defun skel-do-join (acc jfun ll s)
49 "Recursive guts of skel-join."
52 (skel-do-join (skel-do-join acc jfun (cdr ll)
53 (funcall jfun s (car (car ll))))
54 jfun (cons (cdr (car ll)) (cdr ll)) s)
58 (defun skel-join (jfun base &rest ll)
59 "Return a list built from joining elements from the given lists in order,
60 left to right. JFUN is a function of two arguments which can join items
61 together. BASE is the initial item."
62 (nreverse (skel-do-join nil jfun ll base)))
64 (defun skel-do-parents (dir acc)
65 "Tail recursive guts of skel-parents"
66 (setq acc (cons dir acc))
67 (setq dir (substring dir 0 (string-match "/[^/]*/?$" dir)))
70 (skel-do-parents dir acc)))
72 (defun skel-parents (dir)
73 "Returns a list of DIR, DIR's parent directory, etc., all the way up to the
75 (setq dir (expand-file-name dir))
76 (nreverse (skel-do-parents dir nil)))
78 (defun skel-do-find (l all acc)
81 (if (and (file-readable-p n) (file-regular-p n))
85 (cons (abbreviate-file-name n) acc))
86 (abbreviate-file-name n))
87 (skel-do-find (cdr l) all acc)))
90 (defun skel-find-skeleton (name &optional all acc)
91 "Searches for skeleton files. NAME is the name of the file to find, or a
92 list of possible names.
94 If ALL is nil, or omitted, return only the first matching filename
95 encountered. Otherwise, return a list of all matching names, most
96 `global' first. ACC is a base list to which the matching filenames are
99 ;; --- Build one big list of all the possible names ---
101 (let ((l (skel-join #'(lambda (x y) (if (string= y "")
103 (expand-file-name y x)))
105 (append (skel-parents default-directory)
108 (if (consp name) name (cons name nil)))))
110 ;; --- Now filter out any which aren't interesting ---
112 (skel-do-find l all acc)))
114 ;;;----- Processing file skeletons ------------------------------------------
116 (defun skel-include (file)
117 "Includes the skeleton rc FILE."
118 (let ((rc (skel-find-skeleton file t)))
120 (load (car rc) nil t t)
121 (setq rc (cdr rc)))))
123 (defun skel-lookup (name)
125 "Reads the value of symbol NAME in skel-alist. If there is no currrent
126 value, the user is prompted for one."
128 ;; --- Resolve NAME into a symbol ---
131 (setq name (intern name)))
133 ;; --- Look up the value ---
135 ;; Add it to the list if we've not seen it before. Protect ourselves
136 ;; against functions which do regexp matching.
138 (let ((pair (assq name skel-alist))
141 (setq value (eval (cdr pair)))
142 (setq value (read-string (format "Value for %s: " name)))
143 (setq skel-alist (cons (cons name value) skel-alist)))
146 (defun skel-do-fill-in ()
147 "Does the actual donkey-work of filling in a file. For each fill-in area
148 in the current buffer, the function looks to see if the item in question
149 has been entered into ALIST: if so, it is replaced automatically;
150 otherwise the user is promted to enter a string to substitute into the
151 buffer at this point."
152 (if (re-search-forward "\\[\\[\\([^]]*\\)\\]\\]" nil t)
154 (replace-match (save-match-data
155 (skel-lookup (match-string 1)))
157 (goto-char (match-beginning 0))
160 ;;;----- Creating new files from skeletons ----------------------------------
162 (defun skel-do-create-file (name switch &optional skel)
164 "Does the main work of creating a file based on a skeleton. The SWITCH
165 argument is called to display the buffer."
167 ;; --- Some local variables ---
169 ;; This is a little bit of a hack, but do I look like someone who cares?
173 ;; --- Find out if the file's there already ---
175 (if (file-exists-p name)
177 (format "File %s already exists. Overwrite? " name))
180 ;; --- Fiddle with the filename ---
182 (cond ((stringp skel) (let ((extind (string-match "\.[^.]*$" skel)))
183 (setq ext (and extind (substring skel extind)))))
185 (setq ext (read-string "Extension: "))
186 (or (string= ext "") (setq ext (concat "." ext)))))
187 (t (let ((extind (string-match "\.[^.]*$" name)))
188 (setq ext (and extind (substring name extind))))))
189 (setq skel (concat "skeleton" (or ext "")))
191 ;; --- Find the skeleton filename ---
193 (setq skel (or (skel-find-skeleton skel)
194 (skel-find-skeleton "skeleton")
195 (error "Couldn't find skeleton file %s" skel)))
197 ;; --- Visit the file and destroy its contents ---
199 (funcall switch (find-file-noselect name))
200 (kill-region (point-min) (point-max))
201 (insert-file-contents skel)
203 ;; --- Mangle the skeleton data in the file ---
205 (make-local-variable 'skel-alist)
206 (setq skel-alist '())
208 ;; --- Read the default values to insert ---
211 (skel-find-skeleton skel-skelrc t)
214 (if (listp skel-skelrc)
215 (mapcar #'(lambda (x) (concat x ext)) skel-skelrc)
216 (concat skel-skelrc ext))
219 (load (car rc) nil t t)
222 ;; --- Now do substitution ---
227 ;;;----- User commands ------------------------------------------------------
229 (defun skel-create-file (name &optional skel)
230 "Creates a new file called NAME and visits it. If SKEL is non-`nil', it is
231 the name of a skeleton file to insert and substitute. Otherwise the
232 skeleton file's name is derived from NAME by taking NAME's extension and
233 appending it to `skel'."
234 (interactive "FSkeleton create file: \nP")
235 (skel-do-create-file name 'switch-to-buffer skel))
237 (defun skel-create-file-other-window (name &optional skel)
238 "Like skel-create-file, but in another window."
239 (interactive "FSkeleton create file in other window: \nP")
240 (skel-do-create-file name 'switch-to-buffer-other-window skel))
242 (defun skel-create-file-other-frame (name &optional skel)
243 "Like skel-create-file, but in another frame."
244 (interactive "FSkeleton create file in other frame: \nP")
245 (skel-do-create-file name 'switch-to-buffer-other-frame skel))
247 ;;;----- Is that all there is? ----------------------------------------------