com.github.jlangch.venice.kira.venice Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of venice Show documentation
Show all versions of venice Show documentation
Venice, a sandboxed Lisp implemented in Java.
The newest version!
;;;; __ __ _
;;;; \ \ / /__ _ __ (_) ___ ___
;;;; \ \/ / _ \ '_ \| |/ __/ _ \
;;;; \ / __/ | | | | (_| __/
;;;; \/ \___|_| |_|_|\___\___|
;;;;
;;;;
;;;; Copyright 2017-2024 Venice
;;;;
;;;; Licensed under the Apache License, Version 2.0 (the "License");
;;;; you may not use this file except in compliance with the License.
;;;; You may obtain a copy of the License at
;;;;
;;;; http://www.apache.org/licenses/LICENSE-2.0
;;;;
;;;; Unless required by applicable law or agreed to in writing, software
;;;; distributed under the License is distributed on an "AS IS" BASIS,
;;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;;;; See the License for the specific language governing permissions and
;;;; limitations under the License.
;;;; Kira - a simple templating library
;;;;
;;;; Thanks to James Reeves and his clojure 'comb' project
;;;; (https://github.com/weavejester/comb). Venice's Kira
;;;; templating library builds on the ideas of 'comb'.
;;;; Note
;;;;
;;;; Kira depends heavily on parsing templates using Java regex. Java
;;;; regex can cause StackOverflowError exceptions on very large templates.
;;;; StackOverflowError
;;;; at java.util.regex.Pattern$CharProperty$1.isSatisfiedBy(...)
;;;; To circumvent this the Java VMs stack size can be increased by
;;;; the VM argument -Xss4096k
;;;; So far this just has been obeserved once while producing the Venice
;;;; cheatsheet a 180 page PDF document that is generated from a pretty
;;;; complex 625KB Kira XHTML template.
(ns kira)
;; -----------------------------------------------------------------------------
;; Framework functions
;; -----------------------------------------------------------------------------
(def kira/delimiters ["<%" "%>"])
(defn- kira/read-source [source]
(if (string? source)
source
(io/slurp source)))
(defn kira/parser-regex [delimiters]
;; \A beginning of the input
;; \z end of the input
;; \Q nothing, but quotes all characters until \E
;; \E nothing, but ends quoting started by \Q
;; (?s) enables dotall mode (dot to match newline characters, too)
;; (?:X) X, as a non-capturing group
(regex/pattern
(str "(?s)\\A"
"(?:" "(.*?)"
"\\Q" (first delimiters) "\\E" "(.*?)" "\\Q" (last delimiters) "\\E"
")?"
"(.*)\\z")))
(def kira/parser-regex-memo (memoize kira/parser-regex))
(defn kira/emit-string [s]
(when (not-empty? s)
(print "(print" (pr-str s) ")")))
(defn kira/emit-expr [expr]
(if (str/starts-with? expr "=")
(print (str "(print " (str/trim (str/rest expr)) ")"))
(print expr)))
(defn kira/parse-string
([source]
(kira/parse-string source kira/delimiters))
([source delimiters]
(let [regex (kira/parser-regex delimiters)]
(with-out-str
(print "(do ")
(loop [src source]
(let [[_ before expr after] (regex/matches regex src)]
(if expr
(do (kira/emit-string before)
(kira/emit-expr expr)
(recur after))
(do (kira/emit-string after)
(print ")")))))))))
(defn kira/compile-fn [args src delimiters]
(eval
`(fn ~args
(with-out-str
~(-> (kira/read-source src)
(kira/parse-string delimiters)
read-string)))))
;; -----------------------------------------------------------------------------
;; Public functions
;; -----------------------------------------------------------------------------
(defn
^{ :arglists '("(kira/escape-xml val)" "(kira/escape-xml val f)" )
:doc """
Returns an XML escaped string. If the passed data is not of
type string it will be converted first to a string using the
'str' function.
An optional function f transforms the value before being converted
to a string and XML escaped.
"""
:examples '(
"""
(do
(ns test)
(load-module :kira)
(println (kira/eval "<%= (kira/escape-xml formula) %> "
{ :formula "x > 100" }))
(defn format [t] (time/format t "yyyy-MM-dd"))
(println (kira/eval "<%= (kira/escape-xml date test/format) %> "
{ :date (time/local-date 2000 8 1) })))
""" )
:see-also '("kira/escape-html") }
kira/escape-xml
([s] (kira/escape-xml s identity))
([s f] (str/escape-xml (str (f s)))))
(defn
^{ :arglists '("(kira/escape-html val)" "(kira/escape-html val f)" )
:doc """
Returns a HTML escaped string. If the passed data is not of
type string it will be converted first to a string using the
'str' function.
An optional function f transforms the value before being converted
to a string and HTML escaped.
"""
:examples '(
"""
(do
(ns test)
(load-module :kira)
(println (kira/eval "<%= (kira/escape-html formula) %>"
{ :formula "x > 100" }))
(defn format [t] (time/format t "yyyy-MM-dd"))
(println (kira/eval "<%= (kira/escape-html date test/format) %>"
{ :date (time/local-date 2000 8 1) })))
""" )
:see-also '("kira/escape-xml") }
kira/escape-html
([s] (kira/escape-html s identity))
([s f] (str/escape-html (str (f s)))))
(defmacro
^{ :arglists '("(kira/fn args source)" "(kira/fn args source delimiters)" )
:doc """
Compile a template into a function that takes the supplied arguments.
The template source may be a string, or an I/O source such as a File,
Reader or InputStream.
"""
:examples '(
"""
(do
(load-module :kira)
(def hello (kira/fn [name] "Hello <%= name %>"))
(println (hello "Alice"))
(println (hello "Bob")))
""" )
:see-also '("kira/eval" "kira/escape-xml" "kira/escape-html") }
kira/fn
([args source]
`(kira/compile-fn '~args ~source ~kira/delimiters))
([args source delimiters]
`(kira/compile-fn '~args ~source ~delimiters)))
(defn
^{ :arglists '(
"(kira/eval source)"
"(kira/eval source bindings)"
"(kira/eval source delimiters bindings)" )
:doc """
Evaluate a template using the supplied bindings. The template source
may be a string, or an I/O source such as a File, Reader or
InputStream.
"""
:examples '(
"""
(do
(ns test)
(load-module :kira)
(println (kira/eval "Hello <%= name %>" { :name "Alice" }))
(println (kira/eval "1 + 2 = <%= (+ 1 2) %>"))
(println (kira/eval "2 + 3 = <% (print (+ 2 3)) %>"))
(println (kira/eval "${=x}$ + ${=y}$ = ${= (+ x y) }$"
["${" "}$"]
{:x 4 :y 5}))
(println (kira/eval "margin: <%= (if large 100 10) %>"
{ :large false }))
(println (kira/eval "fruits: <% (doseq [f fruits] %><%= f %> <% ) %>"
{ :fruits '("apple" "peach") }))
(println (kira/eval "fruits: <% (doseq [f fruits] %><%= f %> <% ) %>"
{ :fruits '("apple" "peach") }))
(println (kira/eval "when: <% (when large %>is large<% ) %>"
{ :large true }))
(println (kira/eval "if: <% (if large (do %>100<% ) (do %>1<% )) %>"
{ :large true }))
(println (kira/eval "<%= (kira/escape-html formula) %>"
{ :formula "12 < 15" })))
""" )
:see-also '("kira/fn" "kira/escape-xml" "kira/escape-html") }
kira/eval
([source]
(kira/eval source kira/delimiters {}))
([source bindings]
(kira/eval source kira/delimiters bindings))
([source delimiters bindings]
(let [keys (map (comp symbol name) (keys bindings))
func (kira/compile-fn [{:keys (into [] keys)}] source delimiters)]
(func bindings))))