[comp.emacs] template.el

lord+@andrew.cmu.edu (Tom Lord) (03/25/88)

I have found the following code useful.  To use it, load it into your
emacs and bind TEMPLATE-COMMAND to some key (I use \M-+).
TEMPLATE-COMMAND prompts you for the name of a template file stored in
the directory named in the variable TEMPLATE-DIR.  That file is
inserted at the point.

Then, supposing you've loaded the template TEMPLATE-DIR/TEMPL, the
function DO-SUBS is invoked.  DO-SUBS looks for
TEMPLATE-DIR/TEMPL.subs and if that file is found, reads the contents
of that file and interprets the value (which should be a list) as a
list of instructions for filling out the template.  The odd numbered
(if you start counting at 1) elements of the list are `keys'.  On the
other hand, the odd numbered (if you start counting at 0) elements are
replacement instructions.  All occurences of each key are replaced by
some string determined by the replacement instruction.  The type of
the replacement instruction determines the replacement.  If it is a
string, then that string is used a prompt and you are asked for the
replacement string.  If the type is anything else, it is EVAL'ed and
the result (which had better be a string) replaces the key.

This code is very small, and doesn't bother doing any error checking.
Hey! what do you want for nothing?

Ok, here's an example template that I use whenever I write a new
program:

/*@ cc @I @F @L -o @R
 **
 **LIBS: -ldtree
 */

char useline[]={"@PROGRAM@ [-x]"};

char * flagsexpl[] = {
	"@PROGRAM@ - ?? 1 line description for -x output",
	"",
	"-x		Display these explanations",
	0,
};

#if	!defined(lint) && !defined(NOSCCS)
	static char	sccsid[] ={"%W% - %E%"};
#endif

/* the (((comments))) below were added just for this post.
 * I usually don't stick n+1 lines of comment in all my 
 * programs
 */
/* created by:
 ** Thomas Lord, busily hacking on @DATE@
 ** the Information Technology Center
 ** Carnegie<no-dash>Mellon - the Un-university
 **	(((note...you won't get that joke until you've read some 
 **	   of the PR dept.'s rules for referring to CM.)))
 ** 	breeding ground for crooked squares
 **	(((note...you won't get THAT joke until you see CM's logo)))
 **	(((Incidently, it was the PR dept. the pushed for the new logo)))
 ** 4910 Forbes Avenue
 ** Pittsburgh PA, 15213
 ** USA
 ** Phone: +1 412 268 5790
 **	(((268 spells CMU on your phone)))
 **	(((I think that was the PR dept.'s idea)))
 **	(((5790 a symmetric pattern.)))
 **	(((That's how I remember it anyway...never had to worry about 
 **	   remembering the area code)))
 ** Site: andrew.cmu.edu
 ** Email: lord+@andrew.cmu.edu
 **	(((Don't ask about the plus.  The explanation sounds really silly.
 **	   If your mailer can't hack the +, send mail to 
 **	   toom.lard@andrew.cmu.edu (no +) and i'll probably get it.)))
 ** Favourite Pizza: Larry and Carol's 687-1189
 ** 	(((This probably violates some rule about advertising on the net)))
 **	(((oh well, too bad)))
 **	(((Besides, since I became a vegetarian I don't 
 **	   eat there much anymore)))
 ** Favourite Beer: Orval
 ** (((Oh yeah, if you happen to work for CM's PR dept., 
 **   nothing personal, ok?  It's just a joke.)))
 */


#include	<stdio.h>

static char		?flag;
extern char	*  leafnm();
char		*  errlabel;
static int	   arginterp();

main(argc,argv)
	char **argv;
{
	arginterp(argc,argv);
	/* This is the tricky bit */
	
	exit(0);
}

static int
arginterp(argc,argv)
	char **argv;
{
	register char *p;
	register c;

	errlabel = leafnm(*argv);
	for (argv++; --argc && **argv == '-'; argv++) {
		p = &argv[0][1];
		while (c = *p++) switch (c) {
		default:
			usage("-%c: invalid flag", c);
			/* no return */
			
		case 'x':
			explflags();
			exit(1);
			
		case '?':
			?flag++;
			continue;
			
		case '?':
			if (!--argc)
				errexit("-?: missing argument");
			?flgarg = *++argv;
			continue;
		}
	}
	return argc;
}


Note that the keys @PROGRAM@ and @DATE@ need to be fixed up.  Also,
incindently, note that there are question marks (which I just I-search
for) where I need to fix up the template, but DO-SUBS isn't powerful
enough to do the job.

Associated with that template (called "main") is a subs file (called 
"main.subs").  It reads:

(
	"@PROGRAM@"	"Program? "
	"@DATE@"	(current-time-string)
)


Note that you don't need to supply a .subs file.  If none is found, 
template-command is just an insert file from a fixed directory (which
is useful, I think).


Well, this post is, so far, much longer than the actual code which is just:

(defvar template-dir "~/include/templates/"
  "*A directory containing oft-used templates.")

(defun template-command ()
  "Nicely prompt for a template file and insert it."
  (interactive)
  (let* ((name (read-file-name "template: " template-dir "default" t))
	 (sub (concat name ".subs")))
    (insert-file name)
    (if (file-readable-p sub)
	(do-subs sub))))

(defun do-subs (fname)
  "Execute a list of substitution commands."
  (interactive "fSubstitution File: ")
  (let ((buffer (get-buffer-create "*Subs buffer*"))
	subs)
    (save-excursion
      (set-buffer buffer)
      (erase-buffer)
      (insert-file fname)
      (goto-char 0)
      (setq subs (read buffer)))
    (save-excursion
      (goto-char 0)
      (while subs
	(let ((key (car subs))
	      (val (car (cdr subs))))
	  (if (stringp val)
	      (setq val (read-string val))
	    (setq val (eval val)))
	  (replace-string key val)
	  (goto-char 0)
	  (setq subs (cdr (cdr subs))))))))