All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.jlangch.venice.kira.venice Maven / Gradle / Ivy

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))))




© 2015 - 2024 Weber Informatics LLC | Privacy Policy