* Template file processor
@ 2005-12-04 9:56 Neil Jerram
0 siblings, 0 replies; 3+ messages in thread
From: Neil Jerram @ 2005-12-04 9:56 UTC (permalink / raw)
Following the recent thread about Bruce's AutoGen, I thought it might
be of interest to post my template processor implementation, so here
it is.
It's pretty simple - for example it doesn't report errors precisely as
Bruce's does - but it does have a nice way of precisely controlling
the environment in which the Scheme code in the template file is
evaluated, and it gets the job done in all the cases I've tried so far.
It's LGPL, so please feel free to use and modify, and let me know if
you have any comments.
Regards,
Neil
;;;; (ossau template) -- template file processor
;;; Copyright (C) 2005 Neil Jerram
;;;
;; This library is free software; you can redistribute it and/or
;; modify it under the terms of the GNU Lesser General Public
;; License as published by the Free Software Foundation; either
;; version 2.1 of the License, or (at your option) any later version.
;;
;; This library 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
;; Lesser General Public License for more details.
;;
;; You should have received a copy of the GNU Lesser General Public
;; License along with this library; if not, write to the Free Software
;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
(define-module (ossau template)
#:use-module (ice-9 rdelim)
#:use-module (ice-9 regex)
#:export (template->code)
#:export-syntax (process-template))
;*****************************************************************************;
;* A template file is a file of content, such as HTML, that is complete *;
;* except for places where the content needs to be filled in *;
;* programmatically. In the case of the template processor code here, the *;
;* code to fill in the dynamic content is written in Scheme and appears *;
;* inline in the template file. *;
;* *;
;* Areas of Scheme code in the template file are delimited by $. For *;
;* example: *;
;* *;
;* <I>This page was processed by Guile $(display (version))$</I> *;
;* *;
;* here (display (version)) is interpreted and processed as Scheme code; *;
;* the rest is normal (HTML) content. *;
;* *;
;* If either normal content or Scheme code needs to include a $ character, *;
;* it can do so by doubling the $, as in: Price $$10.20. *;
;* *;
;* Fragments of Scheme code do not have to be individually balanced. For *;
;* example: *;
;* *;
;* $(for-each (lambda (x)$ *;
;* <LI>The square of $(display x)$ is $(display (* x x))$</LI> *;
;* $ ) (iota 11))$ *;
;* *;
;* A shorthand is provided for cases where a fragment only wants to display *;
;* a variable. This is $~FORMAT VARNAME$, for example $~A x$. ~FORMAT is a *;
;* format specifier understood by (ice-9 format), and VARNAME is the name of *;
;* the variable to display. *;
;* *;
;* It may sometimes help to know the exact algorithm in order to write a *;
;* piece of template file code correctly. It is as follows. *;
;* *;
;* 1. Convert the template file - even the normal content - into a big *;
;* Scheme code string by: *;
;* *;
;* - converting each fragment of normal content to `(display FRAGMENT)' *;
;* *;
;* - converting each `~FORMAT VARNAME' fragment to *;
;* `(format #t ~FORMAT VARNAME)' *;
;* *;
;* - copying other Scheme code fragments as written. *;
;* *;
;* 2. Read and evaluate this string in an environment as specified by the *;
;* arguments to process-template. *;
;* *;
;*****************************************************************************;
;*****************************************************************************;
;* template->code *;
;* *;
;* Reads a template file and returns the Scheme code that should be read and *;
;* evaluated to generate the implied output. *;
;*****************************************************************************;
(define (template->code template)
;***************************************************************************;
;* Utility procedure: convert any occurrences of "$$" in STRING to just *;
;* "$". *;
;***************************************************************************;
(define (unescape-$$ string)
(cond ((string-match "\\$\\$" string)
=>
(lambda (match-data)
(string-append (substring string 0 (match:start match-data 0))
"$"
(unescape-$$ (substring string
(+ (match:start match-data
0)
1))))))
(else string)))
;***************************************************************************;
;* Utility procedure: given a string read from the template file, after *;
;* splitting between scheme and non-scheme parts, return the Scheme code *;
;* corresponding to the template string. *;
;***************************************************************************;
(define (make-code-string template-string in-scheme)
(if in-scheme
;*********************************************************************;
;* Template string should be interpreted as Scheme code. If it *;
;* begins with "~", it is a shorthand for a format expression; *;
;* otherwise, it is straight Scheme code and doesn't need any *;
;* further tweaking. *;
;*********************************************************************;
(cond ((string-match "^~[^ ]+ " template-string)
=>
(lambda (match-data)
(let ((beg (match:start match-data 0))
(end (match:end match-data 0)))
(format #f
"(format #t ~S ~A)"
(substring template-string beg (- end 1))
(substring template-string end)))))
(else template-string))
;*********************************************************************;
;* Template string is normal file content (i.e. outside Scheme *;
;* code). The corresponding Scheme code should display it. *;
;*********************************************************************;
(format #f "(display ~S)" template-string)))
;***************************************************************************;
;* Main procedure code. *;
;***************************************************************************;
(with-input-from-file template
(lambda ()
;***********************************************************************;
;* Loop reading lines from the template file. *;
;***********************************************************************;
(let loop ((template-line (read-line (current-input-port) 'concat))
(in-scheme #f)
(strings '()))
(if (eof-object? template-line)
;*****************************************************************;
;* EOF: return the concatenated Scheme code string. *;
;*****************************************************************;
; (let ((code
(string-append "(begin "
(apply string-append
(reverse strings))
")")
; ))
; (with-output-to-file "template-debug.scm"
; (lambda ()
; (display code)))
; code)
;*****************************************************************;
;* Not yet EOF: normal processing. First check for single "$"; *;
;* these mark the boundaries between Scheme code and normal *;
;* (non-Scheme) file content. *;
;*****************************************************************;
(cond ((string-match "(^|[^$])(\\$)($|[^$])" template-line)
=>
;**********************************************************;
;* Found a single "$", so process the part of the line *;
;* before the "$", then toggle the in-scheme flag and *;
;* loop to process the rest of the line. *;
;**********************************************************;
(lambda (match-data)
(let (($pos (match:start match-data 2)))
(loop (let ((rest (substring template-line (+ $pos 1))))
(if (<= (string-length rest) 1)
(read-line (current-input-port) 'concat)
rest))
(not in-scheme)
(cons (make-code-string (unescape-$$
(substring template-line
0
$pos))
in-scheme)
strings)))))
;***********************************************************;
;* No "$" in this line, so process whole line and loop to *;
;* read the next line. *;
;***********************************************************;
(else
(loop (read-line (current-input-port) 'concat)
in-scheme
(cons (make-code-string (unescape-$$ template-line)
in-scheme)
strings)))))))))
;*****************************************************************************;
;* process-template *;
;* *;
;* Processes a template file, with the generated output going to the current *;
;* output port. Returns unspecified. *;
;* *;
;* Args are: template - Name of template file. *;
;* vars - Variables to define for the Scheme code in the *;
;* template file, in the same form as a set of let *;
;* bindings, i.e. *;
;* ((variable1 value1) *;
;* (variable2 value2) *;
;* ...) *;
;* modules - List of modules that the Scheme code in the *;
;* template file uses. *;
;* *;
;*****************************************************************************;
(define-macro (process-template template vars . modules)
`(let ((module (make-module 31
(map resolve-interface
',modules))))
,@(map (lambda (vardef)
`(module-define! module
',(if (pair? vardef) (car vardef) vardef)
,(if (pair? vardef) (cadr vardef) vardef)))
vars)
(eval (with-input-from-string (template->code ,template) read)
module)))
_______________________________________________
Guile-user mailing list
Guile-user@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-user
^ permalink raw reply [flat|nested] 3+ messages in thread
* RE: Template file processor
@ 2005-12-04 20:51 Bruce Korb
2005-12-06 19:57 ` Neil Jerram
0 siblings, 1 reply; 3+ messages in thread
From: Bruce Korb @ 2005-12-04 20:51 UTC (permalink / raw)
Cc: Guile Users
Neil wrote:
> Following the recent thread about Bruce's AutoGen, I thought it might
> be of interest to post my template processor implementation, so here
> it is.
Hi Neil, et al.,
That is useful and handy and a convenient representation for generating
a text file that has computed fragments. To be clear, though, that is not
exactly the prime motivation behind my program. :) It is rather the
separation of a template that describes the output file from the data
used to drive the instantiation. e.g. in my "trivial example", these
data:
> autogen definitions list;
> list = { list_element = alpha;
> list_info = "some alpha stuff"; };
> list = { list_info = "more beta stuff";
> list_element = beta; };
> list = { list_element = omega;
> list_info = "final omega stuff"; };
are used with a template to produce these two output files,
a "list.h":
> typedef enum {
> IDX_ALPHA,
> IDX_BETA,
> IDX_OMEGA } list_enum;
>
> extern const char* az_name_list[ 3 ];
and a "list.c":
> #include "list.h"
> const char* az_name_list[] = {
> "some alpha stuff",
> "more beta stuff",
> "final omega stuff" };
C.F.: http://www.gnu.org/software/autogen/manual/html_node/autogen_3.html#SEC3
Cheers - Bruce
_______________________________________________
Guile-user mailing list
Guile-user@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-user
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: Template file processor
2005-12-04 20:51 Template file processor Bruce Korb
@ 2005-12-06 19:57 ` Neil Jerram
0 siblings, 0 replies; 3+ messages in thread
From: Neil Jerram @ 2005-12-06 19:57 UTC (permalink / raw)
Cc: Guile Users
Bruce Korb <bkorb@gnu.org> writes:
> That is useful and handy and a convenient representation for generating
> a text file that has computed fragments. To be clear, though, that is not
> exactly the prime motivation behind my program. :) It is rather the
> separation of a template that describes the output file from the data
> used to drive the instantiation. e.g. in my "trivial example", these
> data: [...example snipped...]
But this is quite straightforward to do with my template processor
also.
The template for list.h would be this:
================================
typedef enum {
$(for-each (lambda (item-data) $
IDX_$~a (string-upcase (symbol->string (car item-data)))$,
$ ) list-data) $
} list_enum;
extern const char* az_name_list[ $~a (length list-data)$ ];
================================
and the template for list.c this:
================================
#include "list.h"
const char* az_name_list[] = {
$(for-each (lambda (item-data) $
$~s (cdr item-data)$,
$ ) list-data) $
};
================================
and then the code to define the data and generate the output would be
this:
================================
(define list-data
'((alpha . "some alpha stuff")
(beta . "more beta stuff")
(omega . "final omega stuff")))
(use-modules (ossau template))
(with-output-to-file "list.h"
(lambda ()
(process-template "list.h.template"
(list-data)
(guile))))
(with-output-to-file "list.c"
(lambda ()
(process-template "list.c.template"
(list-data)
(guile))))
================================
So what's my point? Well nothing very much, except to say that from
my point of view (as someone who prefers to code in Scheme) you are
making life hard for yourself with AutoGen by writing so much of it in
C. On the other hand I appreciate that AutoGen is a far more mature
system than the above and that you have constraints on what you can do
with it because of your existing users.
Regards,
Neil
_______________________________________________
Guile-user mailing list
Guile-user@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-user
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2005-12-06 19:57 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2005-12-04 20:51 Template file processor Bruce Korb
2005-12-06 19:57 ` Neil Jerram
-- strict thread matches above, loose matches on Subject: below --
2005-12-04 9:56 Neil Jerram
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).