;;
;;
;; The Ersatz template library.
;;
;; Based on the Ocaml Jingoo library by Masaki WATANABE, which is in
;; turn based on the Python Jinja2 library.
;;
;; Copyright 2012-2014 Ivan Raikov and the Okinawa Institute of
;; Science and Technology.
;;
;; This program 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 3 of the
;; License, or (at your option) any later version.
;;
;; This program 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.
;;
;; A full copy of the GPL license can be found at
;; .
;;
(module ersatz-lib
(
from-string from-file
statements-from-string statements-from-file
eval-expr eval-statement eval-statements
template-std-env init-context
keep-lexer-table lexer-trace
template-environment? make-template-environment make-lexer-table
tmpl-env-autoescape tmpl-env-search-path tmpl-env-filters tmpl-env-lexer-table
template-context? make-template-context
tmpl-ctx-frame-stack tmpl-ctx-macro-table tmpl-ctx-filter-table tmpl-ctx-buffer
template-context-frame?
template-macro? make-template-macro
tmpl-mac-args tmpl-mac-defaults tmpl-mac-code
macro-code?
tvalue? Tnull Tint Tbool Tfloat Tstr Tobj Tlist Tset Tfun
sexpr->tvalue tvalue->sexpr
tstmt? TextStatement ExpandStatement IfStatement ForStatement IncludeStatement
ExtendsStatement ImportStatement FromImportStatement SetStatement BlockStatement
MacroStatement FilterStatement CallStatement WithStatement AutoEscapeStatement
texpr? IdentExpr LiteralExpr NotOpExpr NegativeOpExpr PlusOpExpr MinusOpExpr
TimesOpExpr PowerOpExpr DivOpExpr ModOpExpr AndOpExpr OrOpExpr NotEqOpExpr EqEqOpExpr
LtOpExpr GtOpExpr LtEqOpExpr GtEqOpExpr DotExpr ApplyExpr ListExpr SetExpr ObjExpr
TestOpExpr KeywordExpr AliasExpr InOpExpr
eq-eq list-same obj-same
op-default op-length op-reverse op-append op-cons op-first op-last op-slice
op-plus op-minus op-times op-power op-div op-mod
op-abs op-round op-range op-toint op-tofloat
op-or op-and
op-upper op-lower op-join op-substring
op-replace op-truncate
op-capitalize op-title op-escape-html op-urlize op-striptags op-trim op-pad
op-wordcount op-sort op-dictsort
op-list op-sublist op-group-by
)
(import scheme chicken)
(import (only srfi-1 every any drop fold find filter take last first)
(only irregex string->irregex sre->irregex irregex-search irregex-split irregex-replace/all irregex-match-num-submatches
irregex-match-start-index)
(only data-structures alist-ref compose ->string string-split conc sort intersperse identity )
(only posix current-directory)
(only files make-pathname)
(only extras fprintf sprintf pp)
(only utils read-all)
)
(require-extension datatype lalr lalr-driver uri-generic)
(require-library setup-api utf8 utf8-srfi-13 utf8-srfi-14 silex)
(import (only utf8 string-length substring)
(only utf8-srfi-13 string-null? string-every string-upcase
string-downcase string-titlecase string-concatenate string-trim-both string-pad
string-ci< string<)
(only utf8-srfi-14 char-set:lower-case char-set:upper-case char-set:whitespace
char-set char-set->string char-set-contains?)
(only silex lex-tables lexer-make-IS lexer-make-lexer )
(only setup-api run execute)
)
;;
;; template environment
;;
;; autoescape : if true or a procedure, template variables are auto escaped when output
;; search-path : search path list; if empty, search current directory only
;; filters : user-defined filters
;; lexer-table : lexical analyzer table to be used (allowing for customizable syntax)
(define-record-type template-environment
(make-template-environment autoescape search-path filters lexer-table )
template-environment?
(autoescape tmpl-env-autoescape)
(search-path tmpl-env-search-path)
(filters tmpl-env-filters)
(lexer-table tmpl-env-lexer-table)
)
;;
;; template context
;;
(define-record-type template-context
(make-template-context frame-stack macro-table filter-table buffer)
template-context?
(frame-stack tmpl-ctx-frame-stack)
(macro-table tmpl-ctx-macro-table)
(filter-table tmpl-ctx-filter-table)
(buffer tmpl-ctx-buffer))
(define (template-context-frame? lst)
(every (lambda (x) (and (string? (car x)) (tvalue? (cdr x)))) lst))
(define-record-type template-macro
(make-template-macro args defaults code)
template-macro?
(args tmpl-mac-args)
(defaults tmpl-mac-defaults)
(code tmpl-mac-code))
(define (list-of pred)
(lambda (lst) (every pred lst)))
(define-datatype tvalue tvalue?
(Tnull)
(Tint (i integer?))
(Tbool (b boolean?))
(Tfloat (n number?))
(Tstr (s string?))
(Tobj (x tvalue-alist?))
(Tlist (x tvalue-list?))
(Tset (x tvalue-list?))
(Tfun (p procedure?)))
(define (tvalue-stringp v)
(cases tvalue v
(Tstr (s) (Tbool #t))
(else (Tbool #f))))
(define (tvalue-intp v)
(cases tvalue v
(Tint (i) (Tbool #t))
(else (Tbool #f))))
(define (tvalue-floatp v)
(cases tvalue v
(Tfloat (i) (Tbool #t))
(else (Tbool #f))))
(define (tvalue-listp v)
(cases tvalue v
(Tlist (l) (Tbool #t))
(else (Tbool #f))))
(define (tvalue-setp v)
(cases tvalue v
(Tset (l) (Tbool #t))
(else (Tbool #f))))
(define (tvalue-objp v)
(cases tvalue v
(Tobj (l) (Tbool #t))
(else (Tbool #f))))
(define (tvalue-funp v)
(cases tvalue v
(Tfun (f) (Tbool #t))
(else (Tbool #f))))
(define tvalue-alist?
(list-of (lambda (x) (and (symbol? (car x)) (tvalue? (cdr x))))))
(define tvalue-list? (list-of tvalue?))
(define-record-printer (tvalue x out)
(cases tvalue x
(Tnull () (fprintf out ""))
(Tint (i) (fprintf out "~A" i))
(Tbool (b) (fprintf out "~A" (if b "true" "false")))
(Tfloat (n) (fprintf out "~A" n))
(Tstr (s) (fprintf out "~A" s))
(Tobj (x) (fprintf out ""))
(Tlist (x) (fprintf out ""))
(Tset (x) (fprintf out ""))
(Tfun (x) (fprintf out ""))
))
(define-record-printer (texpr x out)
(cases texpr x
(IdentExpr (s) (fprintf out "Ident(~A)" s))
(LiteralExpr (v) (fprintf out "Literal (~A)" v))
(NotOpExpr (e) (fprintf out "Not (~A)" e))
(NegativeOpExpr (e) (fprintf out "Neg (~A)" e))
(PlusOpExpr (e1 e2) (fprintf out "Plus (~A,~A)" e1 e2))
(MinusOpExpr (e1 e2) (fprintf out "Minus (~A,~A)" e1 e2))
(TimesOpExpr (e1 e2) (fprintf out "Times (~A,~A)" e1 e2))
(PowerOpExpr (e1 e2) (fprintf out "Power (~A,~A)" e1 e2))
(DivOpExpr (e1 e2) (fprintf out "Div (~A,~A)" e1 e2))
(ModOpExpr (e1 e2) (fprintf out "Mod (~A,~A)" e1 e2))
(AndOpExpr (e1 e2) (fprintf out "And (~A,~A)" e1 e2))
(OrOpExpr (e1 e2) (fprintf out "Or (~A,~A)" e1 e2))
(NotEqOpExpr (e1 e2) (fprintf out "Neq (~A,~A)" e1 e2))
(EqEqOpExpr (e1 e2) (fprintf out "Eq (~A,~A)" e1 e2))
(LtOpExpr (e1 e2) (fprintf out "Lt (~A,~A)" e1 e2))
(GtOpExpr (e1 e2) (fprintf out "PGt (~A,~A)" e1 e2))
(LtEqOpExpr (e1 e2) (fprintf out "LtEq (~A,~A)" e1 e2))
(GtEqOpExpr (e1 e2) (fprintf out "GtEq (~A,~A)" e1 e2))
(DotExpr (e1 e2) (fprintf out "Dot (~A,~A)" e1 e2))
(ApplyExpr (e a) (fprintf out "Apply (~A,~A)" e a))
(ListExpr (xs) (fprintf out "List (~A)" xs))
(SetExpr (xs) (fprintf out "Set (~A)" xs))
(ObjExpr (xs) (fprintf out "Obj (~A)" xs))
(TestOpExpr (e1 e2) (fprintf out "Test (~A,~A)" e1 e2))
(KeywordExpr (e1 e2) (fprintf out "Keyword (~A,~A)" e1 e2))
(AliasExpr (e1 e2) (fprintf out "Alias (~A,~A)" e1 e2))
(InOpExpr (e1 e2) (fprintf out "In (~A,~A)" e1 e2))
))
(define (type-string-of-tvalue v)
(cases tvalue v
(Tnull () "null")
(Tint (i) "int")
(Tbool (b) "bool")
(Tfloat (n) "float")
(Tstr (s) "string")
(Tobj (x) "obj")
(Tlist (x) "list")
(Tset (x) "set")
(Tfun (x) "function")
))
(define (sexpr->tvalue x)
(cond
((boolean? x) (Tbool x))
((integer? x) (Tint x))
((number? x) (Tfloat x))
((string? x) (Tstr x))
((char? x) (Tstr (->string x)))
((symbol? x) (Tstr (->string x)))
((procedure? x) (Tfun x))
((vector? x) (Tset (map sexpr->tvalue (vector->list x))))
((null? x) (Tlist '()))
((pair? x)
(cond
((and (pair? (car x)) (symbol? (car (car x))))
(Tobj (map (lambda (x) (cons (car x) (sexpr->tvalue (cdr x)))) x)))
(else (Tlist (map sexpr->tvalue x)))))
((tvalue? x) x)
(else (error 'sexpr->tvalue "cannot convert sexpr to tvalue" x))
))
(define (tvalue->sexpr x)
(cases tvalue x
(Tnull () '(tnull))
(Tint (i) i)
(Tbool (b) b)
(Tfloat (n) n)
(Tstr (s) s)
(Tobj (fs) (map (lambda (x) (cons (car x) (tvalue->sexpr (cdr x)))) fs))
(Tlist (vs) (map tvalue->sexpr vs))
(Tset (vs) (vector->list (map tvalue->sexpr vs)))
(Tfun (p) p)
))
;; Template function arguments
;;
;; Arguments of template functions are defined as "tvalue list".
;; The filtered target is the LAST argument of filter function.
;;
;; For example, consider the following expansion of "x" with filter
;; function "foo" (with no keyword arguments) {{x|foo(10,20)}}
;;
;; The filter function "foo" takes 3 arguments, and internally is
;; evaluated like this:
;;
;; (foo 10 20 x)
(define tfun-args? tvalue-list?)
;; Template function keyword arguments
;; Keyword arguments of function are defined as (string * tvalue) list.
(define tfun-kwargs? tvalue-alist?)
(define-datatype tstmt tstmt?
(TextStatement (s string?))
(ExpandStatement (e texpr?))
(IfStatement (cb (list-of template-cond-clause?))
(el template-ast?))
(ForStatement (e1 texpr?)
(e2 texpr?)
(a template-ast?))
(IncludeStatement (s string?) (wcontext boolean?))
(ExtendsStatement (s string?))
(ImportStatement (s string?)
(w (lambda (x) (or (not x) (symbol? x)))))
(FromImportStatement (s string?)
(w (list-of texpr?)))
(SetStatement (e1 texpr?)
(e2 texpr?))
(BlockStatement (e texpr?) (f (lambda (x) (or (not x) (texpr? x))))
(b template-ast?))
(MacroStatement (e texpr?)
(a (list-of texpr?))
(b template-ast?))
(FilterStatement (e texpr?)
(b template-ast?))
(CallStatement (e texpr?)
(a1 (list-of texpr?))
(a2 (list-of texpr?) )
(b template-ast?))
(WithStatement (es (list-of texpr?))
(b template-ast?))
(AutoEscapeStatement (e texpr?)
(b template-ast?))
)
(define macro-code? (list-of tstmt?))
(define-datatype texpr texpr?
(IdentExpr (s symbol?))
(LiteralExpr (v tvalue?))
(NotOpExpr (e texpr?))
(NegativeOpExpr (e texpr?))
(PlusOpExpr (e1 texpr?) (e2 texpr?))
(MinusOpExpr (e1 texpr?) (e2 texpr?))
(TimesOpExpr (e1 texpr?) (e2 texpr?))
(PowerOpExpr (e1 texpr?) (e2 texpr?))
(DivOpExpr (e1 texpr?) (e2 texpr?))
(ModOpExpr (e1 texpr?) (e2 texpr?))
(AndOpExpr (e1 texpr?) (e2 texpr?))
(OrOpExpr (e1 texpr?) (e2 texpr?))
(NotEqOpExpr (e1 texpr?) (e2 texpr?))
(EqEqOpExpr (e1 texpr?) (e2 texpr?))
(LtOpExpr (e1 texpr?) (e2 texpr?))
(GtOpExpr (e1 texpr?) (e2 texpr?))
(LtEqOpExpr (e1 texpr?) (e2 texpr?))
(GtEqOpExpr (e1 texpr?) (e2 texpr?))
(DotExpr (e1 texpr?) (e2 texpr?))
(ApplyExpr (e texpr?) (a (list-of texpr?)))
(ListExpr (xs (list-of texpr?)))
(SetExpr (xs (list-of texpr?)))
(ObjExpr (xs (list-of expression-pair?)))
(TestOpExpr (e1 texpr?) (e2 texpr?))
(KeywordExpr (e1 texpr?) (e2 texpr?))
(AliasExpr (e1 texpr?) (e2 texpr?))
(InOpExpr (e1 texpr?) (e2 texpr?))
)
(define (expression-pair? x) (and (texpr? (car x)) (texpr? (cdr x))))
(define template-ast? (list-of tstmt?))
(define (template-cond-clause? x)
(and (texpr? (car x)) (template-ast? (cdr x))))
(define (template-std-env #!key
(autoescape #t)
(search-path '())
(filters '())
(lexer-table default-ersatz-lexer-table)
)
(make-template-environment
autoescape search-path filters lexer-table)
)
(include "runtime.scm")
;(define default-ersatz-lexer-table)
(define top-frame
`(
;; built-in filters
(abs . ,(func-arg1 op-abs))
(capitalize . ,(func-arg1 op-capitalize))
(escape . ,(func-arg1 op-escape-html))
(e . ,(func-arg1 op-escape-html)) ;; alias for escape
(float . ,(func-arg1 op-tofloat))
(int . ,(func-arg1 op-toint))
(first . ,(func-arg1 op-first))
(last . ,(func-arg1 op-last))
(length . ,(func-arg1 op-length))
(list . ,(func-arg1 op-list))
(lower . ,(func-arg1 op-lower))
(safe . ,(func-arg1 op-safe))
(strlen . ,(func-arg1 op-strlen))
(striptags . ,(func-arg1 op-striptags))
(sort . ,(func-arg1 op-sort))
(dict . ,(func-arg1 op-dict))
(upper . ,(func-arg1 op-upper))
(reverse . ,(func-arg1 op-reverse))
(append . ,(func-arg2 op-append))
(cons . ,(func-arg2 op-cons))
(title . ,(func-arg1 op-title))
(trim . ,(func-arg1 op-trim))
(pad . ,(func-arg2 op-pad))
(urlize . ,(func-arg1 op-urlize))
(wordcount . ,(func-arg1 op-wordcount))
(attr . ,(func-arg2 op-attr))
(haskey . ,(func-arg2 op-haskey))
(batch . ,(func-arg2 op-batch))
(default . ,(func-arg2 op-default))
(d . ,(func-arg2 op-default)) ;; alias for default
(join . ,(func-arg2 op-join))
(split . ,(func-arg2 op-split))
(slice . ,(func-arg2 op-slice))
(truncate . ,(func-arg2 op-truncate))
(range . ,(func-arg2 op-range))
(round . ,(func-arg2 op-round))
(replace . ,(func-arg3 op-replace))
(substring . ,(func-arg3 op-substring))
(sublist . ,(func-arg3 op-sublist))
(groupBy . ,(func-arg2 op-group-by))
;; built-in tests
(divisibleby . ,(func-arg2 test-divisibleby))
(even . ,(func-arg1 test-even))
(iterable . ,(func-arg1 test-iterable))
(lower . ,(func-arg1 test-lower))
(number . ,(func-arg1 test-number))
(odd . ,(func-arg1 test-odd))
(sameas . ,(func-arg2 test-sameas))
(sequence . ,(func-arg1 test-sequence))
(string . ,(func-arg1 test-string))
(upper . ,(func-arg1 test-upper))
))
(define (init-context #!key
(env (template-std-env))
(models '())
(open-buffer open-output-string)
)
(let ((env-values
`((is_autoescape . ,(Tbool (and (tmpl-env-autoescape env) #t))))))
(make-template-context
(list (append env-values models)
(append (tmpl-env-filters env)
top-frame)) ;; frame-stack
'() ;; macro-table
'() ;; filter-table
(open-buffer) ;; buffer
)))
(include "make-ersatz-lexer.scm")
(include "parser.scm")
(include "eval.scm")
(define (from-file fn #!key
(env (template-std-env))
(models '())
(ctx #f))
(eval-statements (statements-from-file env fn)
env: env models: models ctx: ctx))
(define (from-string source #!key
(env (template-std-env))
(models '())
(ctx #f))
(eval-statements (statements-from-string env source)
env: env models: models ctx: ctx))
)