Compare commits
19 commits
Author | SHA1 | Date | |
---|---|---|---|
Gered | ba78333c28 | ||
Gered | 5a644a92d2 | ||
Gered | 159918019a | ||
Gered | 1fbfaae934 | ||
Gered | 3234d97ce0 | ||
Gered | 77a10a38d7 | ||
Gered | 68c2beb863 | ||
Gered | ee08d7e94d | ||
Gered | b1a605e758 | ||
Gered | 97281d6648 | ||
Gered | e1a08429bb | ||
Gered | 0aeae17824 | ||
Gered | 941c0ad2e2 | ||
Gered | 3ab6c96c93 | ||
Gered | eba27bc60d | ||
Gered | f1558b72a1 | ||
Gered | d3eb6e844c | ||
Gered | 0891fa5675 | ||
Gered | fde89f46b9 |
|
@ -1,10 +1,12 @@
|
||||||
(defproject clj-jtwig "0.5.1"
|
(defproject clj-jtwig "0.5.1"
|
||||||
:description "Clojure wrapper for JTwig"
|
:description "Clojure wrapper for Jtwig"
|
||||||
:url "https://github.com/gered/clj-jtwig"
|
:url "https://github.com/gered/clj-jtwig"
|
||||||
:license {:name "Apache License, Version 2.0"
|
:license {:name "Apache License, Version 2.0"
|
||||||
:url "http://www.apache.org/licenses/LICENSE-2.0"}
|
:url "http://www.apache.org/licenses/LICENSE-2.0"}
|
||||||
:repositories [["sonatype" {:url "http://oss.sonatype.org/content/repositories/releases"
|
:repositories [["sonatype" {:url "http://oss.sonatype.org/content/repositories/releases"
|
||||||
:snapshots false}]]
|
:snapshots false}]]
|
||||||
:dependencies [[org.clojure/clojure "1.6.0"]
|
:dependencies [[org.clojure/clojure "1.6.0"]
|
||||||
[com.lyncode/jtwig-core "2.1.7"]
|
[com.lyncode/jtwig-core "3.0.0-SNAPSHOT"]
|
||||||
[org.apache.commons/commons-lang3 "3.1"]])
|
[org.apache.commons/commons-lang3 "3.1"]]
|
||||||
|
:source-paths ["src/clojure"]
|
||||||
|
:java-source-paths ["src/java"])
|
||||||
|
|
|
@ -1,203 +0,0 @@
|
||||||
(ns clj-jtwig.standard-functions
|
|
||||||
"standard function definitions. these are functions that are not yet included in JTwig's standard function
|
|
||||||
library and are just here to fill in the gaps for now."
|
|
||||||
(:import (org.apache.commons.lang3.text WordUtils)
|
|
||||||
(org.apache.commons.lang3 StringUtils))
|
|
||||||
(:use [clojure.pprint]
|
|
||||||
[clj-jtwig.options]))
|
|
||||||
|
|
||||||
(defn- possible-keyword-string [x]
|
|
||||||
(if (and (:auto-convert-map-keywords @options)
|
|
||||||
(string? x))
|
|
||||||
(keyword x)
|
|
||||||
x))
|
|
||||||
|
|
||||||
; we are using a separate map to hold the standard functions instead of using deftwigfn, etc. because doing it this
|
|
||||||
; way makes it easy to re-add all these functions when/if the JTwig function repository object needs to be
|
|
||||||
; recreated (e.g. during unit tests).
|
|
||||||
|
|
||||||
; the keys are function names. each value is a map containing :fn which is the actual function, and an optional
|
|
||||||
; :aliases, which should be a vector of strings containing one or more possible aliases for this function.
|
|
||||||
|
|
||||||
(defonce standard-functions
|
|
||||||
{"blank_if_null"
|
|
||||||
{:fn (fn [x]
|
|
||||||
(if (nil? x) "" x))
|
|
||||||
:aliases ["nonull"]}
|
|
||||||
|
|
||||||
"butlast"
|
|
||||||
{:fn (fn [sequence]
|
|
||||||
; matching behaviour of jtwig's first/last implementation
|
|
||||||
(if (map? sequence)
|
|
||||||
(-> sequence vals butlast)
|
|
||||||
(butlast sequence)))}
|
|
||||||
|
|
||||||
"center"
|
|
||||||
{:fn (fn [s size & [padding-string]]
|
|
||||||
(StringUtils/center s size (or padding-string " ")))}
|
|
||||||
|
|
||||||
"contains"
|
|
||||||
{:fn (fn [coll value]
|
|
||||||
(cond
|
|
||||||
(map? coll) (contains? coll (possible-keyword-string value))
|
|
||||||
(string? coll) (.contains coll value)
|
|
||||||
; explicit use of '=' to allow testing for falsey values
|
|
||||||
(coll? coll) (not (nil? (some #(= value %) coll)))
|
|
||||||
:else (throw (new Exception (str "'contains' passed invalid collection type: " (type coll))))))}
|
|
||||||
|
|
||||||
"dump"
|
|
||||||
{:fn (fn [x]
|
|
||||||
(with-out-str
|
|
||||||
(clojure.pprint/pprint x)))}
|
|
||||||
|
|
||||||
"dump_table"
|
|
||||||
{:fn (fn [x]
|
|
||||||
(with-out-str
|
|
||||||
(clojure.pprint/print-table x)))}
|
|
||||||
|
|
||||||
"index_of"
|
|
||||||
{:fn (fn [coll value]
|
|
||||||
(cond
|
|
||||||
(instance? java.util.List coll) (.indexOf coll value)
|
|
||||||
(string? coll) (.indexOf coll (if (char? value) (int value) value))
|
|
||||||
:else (throw (new Exception (str "'index_of' passed invalid collection type: " (type coll))))))}
|
|
||||||
|
|
||||||
"last_index_of"
|
|
||||||
{:fn (fn [coll value]
|
|
||||||
(cond
|
|
||||||
(instance? java.util.List coll) (.lastIndexOf coll value)
|
|
||||||
(string? coll) (.lastIndexOf coll (if (char? value) (int value) value))
|
|
||||||
:else (throw (new Exception (str "'last_index_of' passed invalid collection type: " (type coll))))))}
|
|
||||||
|
|
||||||
"max"
|
|
||||||
{:fn (fn [& numbers]
|
|
||||||
(if (coll? (first numbers))
|
|
||||||
(apply max (first numbers))
|
|
||||||
(apply max numbers)))}
|
|
||||||
|
|
||||||
"min"
|
|
||||||
{:fn (fn [& numbers]
|
|
||||||
(if (coll? (first numbers))
|
|
||||||
(apply min (first numbers))
|
|
||||||
(apply min numbers)))}
|
|
||||||
|
|
||||||
"normalize_space"
|
|
||||||
{:fn (fn [s]
|
|
||||||
(StringUtils/normalizeSpace s))}
|
|
||||||
|
|
||||||
"nth"
|
|
||||||
{:fn (fn [sequence index & optional-not-found]
|
|
||||||
(let [values (if (map? sequence) ; map instance check to match behaviour of jtwig's first/last implementation
|
|
||||||
(-> sequence vals)
|
|
||||||
sequence)]
|
|
||||||
(if optional-not-found
|
|
||||||
(nth values index (first optional-not-found))
|
|
||||||
(nth values index))))}
|
|
||||||
|
|
||||||
"pad_left"
|
|
||||||
{:fn (fn [s size & [padding-string]]
|
|
||||||
(StringUtils/leftPad s size (or padding-string " ")))}
|
|
||||||
|
|
||||||
"pad_right"
|
|
||||||
{:fn (fn [s size & [padding-string]]
|
|
||||||
(StringUtils/rightPad s size (or padding-string " ")))}
|
|
||||||
|
|
||||||
"random"
|
|
||||||
{:fn (fn [& values]
|
|
||||||
(let [first-value (first values)]
|
|
||||||
(cond
|
|
||||||
(and (= (count values) 1)
|
|
||||||
(coll? first-value))
|
|
||||||
(rand-nth first-value)
|
|
||||||
|
|
||||||
(> (count values) 1)
|
|
||||||
(rand-nth values)
|
|
||||||
|
|
||||||
(string? first-value)
|
|
||||||
(rand-nth (seq first-value))
|
|
||||||
|
|
||||||
(number? first-value)
|
|
||||||
(rand-int first-value)
|
|
||||||
|
|
||||||
:else
|
|
||||||
(rand))))}
|
|
||||||
|
|
||||||
"range"
|
|
||||||
{:fn (fn [low high & [step]]
|
|
||||||
(range low high (or step 1)))}
|
|
||||||
|
|
||||||
"repeat"
|
|
||||||
{:fn (fn [s n]
|
|
||||||
(StringUtils/repeat s n))}
|
|
||||||
|
|
||||||
"rest"
|
|
||||||
{:fn (fn [sequence]
|
|
||||||
; matching behaviour of jtwig's first/last implementation
|
|
||||||
(if (map? sequence)
|
|
||||||
(-> sequence vals rest)
|
|
||||||
(rest sequence)))}
|
|
||||||
|
|
||||||
"second"
|
|
||||||
{:fn (fn [sequence]
|
|
||||||
; matching behaviour of jtwig's first/last implementation
|
|
||||||
(if (map? sequence)
|
|
||||||
(-> sequence vals second)
|
|
||||||
(second sequence)))}
|
|
||||||
|
|
||||||
"sort"
|
|
||||||
{:fn (fn [sequence]
|
|
||||||
(sort < sequence))}
|
|
||||||
|
|
||||||
"sort_descending"
|
|
||||||
{:fn (fn [sequence]
|
|
||||||
(sort > sequence))
|
|
||||||
:aliases ["sort_desc"]}
|
|
||||||
|
|
||||||
"sort_by"
|
|
||||||
{:fn (fn [coll k]
|
|
||||||
(let [sort-key (possible-keyword-string k)]
|
|
||||||
(sort-by #(get % sort-key) coll)))}
|
|
||||||
|
|
||||||
"sort_descending_by"
|
|
||||||
{:fn (fn [coll k]
|
|
||||||
(let [sort-key (possible-keyword-string k)]
|
|
||||||
(sort-by #(get % sort-key) #(compare %2 %1) coll)))
|
|
||||||
:aliases ["sort_desc_by"]}
|
|
||||||
|
|
||||||
"to_double"
|
|
||||||
{:fn (fn [x]
|
|
||||||
(Double/parseDouble x))}
|
|
||||||
|
|
||||||
"to_float"
|
|
||||||
{:fn (fn [x]
|
|
||||||
(Float/parseFloat x))}
|
|
||||||
|
|
||||||
"to_int"
|
|
||||||
{:fn (fn [x]
|
|
||||||
(Integer/parseInt x))}
|
|
||||||
|
|
||||||
"to_keyword"
|
|
||||||
{:fn (fn [x]
|
|
||||||
(keyword x))}
|
|
||||||
|
|
||||||
"to_long"
|
|
||||||
{:fn (fn [x]
|
|
||||||
(Long/parseLong x))}
|
|
||||||
|
|
||||||
"to_string"
|
|
||||||
{:fn (fn [x]
|
|
||||||
(cond
|
|
||||||
(keyword? x) (name x)
|
|
||||||
(instance? clojure.lang.LazySeq x) (str (seq x))
|
|
||||||
(coll? x) (str x)
|
|
||||||
:else (.toString x)))}
|
|
||||||
|
|
||||||
"wrap"
|
|
||||||
{:fn (fn [s length & [wrap-long-words? new-line-string]]
|
|
||||||
(WordUtils/wrap
|
|
||||||
s
|
|
||||||
length
|
|
||||||
new-line-string
|
|
||||||
(if (nil? wrap-long-words?)
|
|
||||||
false
|
|
||||||
wrap-long-words?)))}})
|
|
|
@ -1,8 +1,12 @@
|
||||||
(ns clj-jtwig.core
|
(ns clj-jtwig.core
|
||||||
"wrapper functions for working with JTwig from clojure"
|
"wrapper functions for working with Jtwig from clojure"
|
||||||
(:import (com.lyncode.jtwig JtwigTemplate JtwigContext JtwigModelMap)
|
(:import (com.lyncode.jtwig JtwigTemplate JtwigContext JtwigModelMap)
|
||||||
(com.lyncode.jtwig.resource ClasspathJtwigResource)
|
(com.lyncode.jtwig.content.api Renderable)
|
||||||
(com.lyncode.jtwig.tree.api Content)
|
(com.lyncode.jtwig.configuration JtwigConfiguration)
|
||||||
|
(com.lyncode.jtwig.parser.config TagSymbols)
|
||||||
|
(com.lyncode.jtwig.render RenderContext)
|
||||||
|
(com.lyncode.jtwig.render.config RenderConfiguration)
|
||||||
|
(com.lyncode.jtwig.resource ClasspathJtwigResource StringJtwigResource FileJtwigResource)
|
||||||
(java.io File FileNotFoundException ByteArrayOutputStream)
|
(java.io File FileNotFoundException ByteArrayOutputStream)
|
||||||
(java.net URL))
|
(java.net URL))
|
||||||
(:require [clojure.walk :refer [stringify-keys]])
|
(:require [clojure.walk :refer [stringify-keys]])
|
||||||
|
@ -10,6 +14,8 @@
|
||||||
[clj-jtwig.utils]
|
[clj-jtwig.utils]
|
||||||
[clj-jtwig.options]))
|
[clj-jtwig.options]))
|
||||||
|
|
||||||
|
(defonce configuration (JtwigConfiguration.))
|
||||||
|
|
||||||
(declare flush-template-cache!)
|
(declare flush-template-cache!)
|
||||||
|
|
||||||
(defn set-options!
|
(defn set-options!
|
||||||
|
@ -19,32 +25,46 @@
|
||||||
see clj-jtwig.options for the option keys you can specify here."
|
see clj-jtwig.options for the option keys you can specify here."
|
||||||
[& opts]
|
[& opts]
|
||||||
(doseq [[k v] (apply hash-map opts)]
|
(doseq [[k v] (apply hash-map opts)]
|
||||||
(if (= :cache-compiled-templates k)
|
(cond
|
||||||
|
(= k :cache-compiled-templates)
|
||||||
; always clear the cache when toggling. this will help ensure that any possiblity of weird behaviour from
|
; always clear the cache when toggling. this will help ensure that any possiblity of weird behaviour from
|
||||||
; leftover stuff being stuck in the cache pre-toggle-on/off won't happen
|
; leftover stuff being stuck in the cache pre-toggle-on/off won't happen
|
||||||
(flush-template-cache!))
|
(flush-template-cache!)
|
||||||
|
|
||||||
|
(= k :strict-mode)
|
||||||
|
(-> configuration .render (.strictMode v))
|
||||||
|
|
||||||
|
(= k :tag-symbols)
|
||||||
|
(-> configuration
|
||||||
|
.parse
|
||||||
|
(.withSymbols (condp = v
|
||||||
|
:default TagSymbols/DEFAULT
|
||||||
|
:js TagSymbols/JAVASCRIPT))))
|
||||||
|
|
||||||
(swap! options assoc k v)))
|
(swap! options assoc k v)))
|
||||||
|
|
||||||
; cache of compiled templates. key is the file path. value is a map with :last-modified which is the source file's
|
; cache of compiled templates. key is the file path. value is a map with :last-modified which is the source file's
|
||||||
; last modification timestamp and :template which is a com.lyncode.jtwig.tree.api.Content object which has been
|
; last modification timestamp and :template which is a com.lyncode.jtwig.content.api.Renderable object which has been
|
||||||
; compiled already and can be rendered by calling it's 'render' method
|
; compiled already and can be rendered by calling it's 'render' method
|
||||||
(defonce compiled-templates (atom {}))
|
(defonce compiled-templates (atom {}))
|
||||||
|
|
||||||
(defn- compile-template-string [^String contents]
|
(defn- compile-template-string [^String contents]
|
||||||
(->> contents
|
(-> contents
|
||||||
(new JtwigTemplate)
|
(StringJtwigResource.)
|
||||||
(.compile)))
|
(JtwigTemplate. configuration)
|
||||||
|
(.compile)))
|
||||||
|
|
||||||
(defn- compile-template-file [^File file]
|
(defn- compile-template-file [^File file]
|
||||||
(if (inside-jar? file)
|
(if (inside-jar? file)
|
||||||
(->> (.getPath file)
|
(-> (.getPath file)
|
||||||
(get-jar-resource-filename)
|
(get-jar-resource-filename)
|
||||||
(new ClasspathJtwigResource)
|
(ClasspathJtwigResource.)
|
||||||
(new JtwigTemplate)
|
(JtwigTemplate. configuration)
|
||||||
(.compile))
|
(.compile))
|
||||||
(->> file
|
(-> file
|
||||||
(new JtwigTemplate)
|
(FileJtwigResource.)
|
||||||
(.compile))))
|
(JtwigTemplate. configuration)
|
||||||
|
(.compile))))
|
||||||
|
|
||||||
(defn- newer? [^File file other-timestamp]
|
(defn- newer? [^File file other-timestamp]
|
||||||
(let [file-last-modified (get-file-last-modified file)]
|
(let [file-last-modified (get-file-last-modified file)]
|
||||||
|
@ -119,33 +139,32 @@
|
||||||
(new JtwigContext model-map-obj @functions)))
|
(new JtwigContext model-map-obj @functions)))
|
||||||
|
|
||||||
(defn- render-compiled-template
|
(defn- render-compiled-template
|
||||||
[^Content compiled-template model-map]
|
[^Renderable renderable model-map]
|
||||||
(let [context (make-context model-map)]
|
(with-open [stream (new ByteArrayOutputStream)]
|
||||||
; technically we don't have to use with-open with a ByteArrayOutputStream but if we later
|
(let [context (make-context model-map)
|
||||||
; decide to use another OutputStream implementation, this is already all set up :)
|
render-context (RenderContext/create (.render configuration) context stream)]
|
||||||
(with-open [stream (new ByteArrayOutputStream)]
|
(.render renderable render-context)
|
||||||
(.render compiled-template stream context)
|
|
||||||
(.toString stream))))
|
(.toString stream))))
|
||||||
|
|
||||||
(defn render
|
(defn render
|
||||||
"renders a template contained in the provided string, using the values in model-map
|
"renders a template contained in the provided string, using the values in model-map
|
||||||
as the model for the template. templates rendered using this function are always
|
as the model for the template. templates rendered using this function are always
|
||||||
parsed, compiled and rendered. the compiled results are never cached."
|
parsed, compiled and rendered. the compiled results are never cached."
|
||||||
[s model-map]
|
[^String s & [model-map]]
|
||||||
(let [compiled-template (compile-template-string s)]
|
(let [renderable (compile-template-string s)]
|
||||||
(render-compiled-template compiled-template model-map)))
|
(render-compiled-template renderable model-map)))
|
||||||
|
|
||||||
(defn render-file
|
(defn render-file
|
||||||
"renders a template from a file, using the values in model-map as the model for the template"
|
"renders a template from a file, using the values in model-map as the model for the template"
|
||||||
[^String filename model-map]
|
[^String filename & [model-map]]
|
||||||
(let [file (new File filename)
|
(let [file (new File filename)
|
||||||
compiled-template (compile-template! file)]
|
renderable (compile-template! file)]
|
||||||
(render-compiled-template compiled-template model-map)))
|
(render-compiled-template renderable model-map)))
|
||||||
|
|
||||||
(defn render-resource
|
(defn render-resource
|
||||||
"renders a template from a resource file, using the values in the model-map as the model for
|
"renders a template from a resource file, using the values in the model-map as the model for
|
||||||
the template."
|
the template."
|
||||||
[^String filename model-map]
|
[^String filename & [model-map]]
|
||||||
(if-let [resource-filename (get-resource-path filename)]
|
(if-let [resource-filename (get-resource-path filename)]
|
||||||
(render-file (.getPath resource-filename) model-map)
|
(render-file (.getPath resource-filename) model-map)
|
||||||
(throw (new FileNotFoundException (str "Template file \"" filename "\" not found.")))))
|
(throw (new FileNotFoundException (str "Template file \"" filename "\" not found.")))))
|
34
src/clojure/clj_jtwig/function_utils.clj
Normal file
34
src/clojure/clj_jtwig/function_utils.clj
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
(ns clj-jtwig.function-utils
|
||||||
|
"utility macros for creating Jtwig function handlers. intended for internal clj-jtwig use only."
|
||||||
|
(:import (com.lyncode.jtwig.functions.exceptions FunctionException)
|
||||||
|
(com.lyncode.jtwig.functions.annotations JtwigFunction Parameter)
|
||||||
|
(clj_jtwig TemplateFunction))
|
||||||
|
(:require [clj-jtwig.convert :refer [java->clojure clojure->java]]))
|
||||||
|
|
||||||
|
(defmacro make-function-handler [name aliases f]
|
||||||
|
(let [arguments (with-meta
|
||||||
|
(gensym "arguments#")
|
||||||
|
`{Parameter {}})]
|
||||||
|
`(reify TemplateFunction
|
||||||
|
(~(with-meta
|
||||||
|
'execute
|
||||||
|
`{JtwigFunction {:name ~name :aliases ~aliases}})
|
||||||
|
[_ ~arguments]
|
||||||
|
(try
|
||||||
|
(clojure->java (apply ~f (map java->clojure ~arguments)))
|
||||||
|
(catch Exception ex#
|
||||||
|
(throw (new FunctionException ex#))))))))
|
||||||
|
|
||||||
|
(defmacro deflibrary [name & function-handlers]
|
||||||
|
`(def ~(symbol name)
|
||||||
|
[~@function-handlers]))
|
||||||
|
|
||||||
|
(defmacro library-function
|
||||||
|
[fn-name args & body]
|
||||||
|
`(let [f# (fn ~args ~@body)]
|
||||||
|
(make-function-handler ~fn-name [] f#)))
|
||||||
|
|
||||||
|
(defmacro library-aliased-function
|
||||||
|
[fn-name aliases args & body]
|
||||||
|
`(let [f# (fn ~args ~@body)]
|
||||||
|
(make-function-handler ~fn-name ~aliases f#)))
|
|
@ -1,37 +1,27 @@
|
||||||
(ns clj-jtwig.functions
|
(ns clj-jtwig.functions
|
||||||
"custom template function/filter support functions."
|
"custom template function/filter support functions."
|
||||||
(:import (com.lyncode.jtwig.functions JtwigFunction)
|
(:import (com.lyncode.jtwig.functions.repository FunctionResolver)
|
||||||
(com.lyncode.jtwig.functions.repository DefaultFunctionRepository)
|
(com.lyncode.jtwig.functions.exceptions FunctionNotFoundException FunctionException)
|
||||||
(com.lyncode.jtwig.functions.exceptions FunctionNotFoundException FunctionException))
|
(com.lyncode.jtwig.functions.annotations JtwigFunction)
|
||||||
(:require [clj-jtwig.convert :refer [java->clojure clojure->java]])
|
(com.lyncode.jtwig.functions.parameters GivenParameters)
|
||||||
|
(clj_jtwig TemplateFunction))
|
||||||
|
(:require [clj-jtwig.convert :refer [java->clojure clojure->java]]
|
||||||
|
[clj-jtwig.function-utils :refer [make-function-handler]])
|
||||||
(:use [clj-jtwig.standard-functions]
|
(:use [clj-jtwig.standard-functions]
|
||||||
[clj-jtwig.web.web-functions]))
|
[clj-jtwig.web.web-functions]))
|
||||||
|
|
||||||
(defn- make-function-handler [f]
|
(def ^:private object-array-type (Class/forName "[Ljava.lang.Object;"))
|
||||||
(reify JtwigFunction
|
|
||||||
(execute [_ arguments]
|
|
||||||
(try
|
|
||||||
(clojure->java (apply f (map java->clojure arguments)))
|
|
||||||
(catch Exception ex
|
|
||||||
(throw (new FunctionException ex)))))))
|
|
||||||
|
|
||||||
(defn- make-aliased-array [aliases]
|
(def ^:private function-parameters (doto (GivenParameters.)
|
||||||
(let [n (count aliases)
|
(.add (to-array [object-array-type]))))
|
||||||
array (make-array String n)]
|
|
||||||
(doseq [index (range n)]
|
|
||||||
(aset array index (nth aliases index)))
|
|
||||||
array))
|
|
||||||
|
|
||||||
(defn- add-function-library! [repository functions]
|
(defn- add-function-library! [repository functions]
|
||||||
(doseq [[name {:keys [aliases fn]}] functions]
|
(doseq [fn-obj functions]
|
||||||
(.add repository
|
(.store repository fn-obj))
|
||||||
(make-function-handler fn)
|
|
||||||
name
|
|
||||||
(make-aliased-array aliases)))
|
|
||||||
repository)
|
repository)
|
||||||
|
|
||||||
(defn- create-function-repository []
|
(defn- create-function-repository []
|
||||||
(doto (new DefaultFunctionRepository (make-array JtwigFunction 0))
|
(doto (new FunctionResolver)
|
||||||
(add-function-library! standard-functions)
|
(add-function-library! standard-functions)
|
||||||
(add-function-library! web-functions)))
|
(add-function-library! web-functions)))
|
||||||
|
|
||||||
|
@ -44,36 +34,34 @@
|
||||||
[]
|
[]
|
||||||
(reset! functions (create-function-repository)))
|
(reset! functions (create-function-repository)))
|
||||||
|
|
||||||
(defn function-exists? [^String name]
|
; intended for internal-use only. mainly exists for use in unit tests
|
||||||
|
(defn get-function [^String name]
|
||||||
(try
|
(try
|
||||||
(.retrieve @functions name)
|
(.get @functions name function-parameters)
|
||||||
true
|
(catch FunctionNotFoundException ex)))
|
||||||
(catch FunctionNotFoundException ex
|
|
||||||
false)))
|
|
||||||
|
|
||||||
(defn add-function!
|
; intended for internal-use only. mainly exists for use in unit tests
|
||||||
"adds a new template function under the name specified. templates can call the function by the
|
(defn function-exists? [^String name]
|
||||||
name specified (or one of the aliases specified) and passing in the same number of arguments
|
(not (nil? (get-function name))))
|
||||||
accepted by f. the return value of f is returned to the template."
|
|
||||||
([^String name f]
|
|
||||||
(add-function! name nil f))
|
|
||||||
([^String name aliases f]
|
|
||||||
(let [handler (make-function-handler f)]
|
|
||||||
(.add @functions handler name (make-aliased-array aliases))
|
|
||||||
(.retrieve @functions name))))
|
|
||||||
|
|
||||||
(defmacro deftwigfn
|
(defmacro deftwigfn
|
||||||
"defines a new template function. templates can call it by by the name specified and passing in the
|
"defines a new template function. templates can call it by by the name specified and passing in the
|
||||||
same number of arguments as in args. the return value of the last form in body is returned to the
|
same number of arguments as in args. the return value of the last form in body is returned to the
|
||||||
template. functions defined this way have no aliases and can only be called by the name given."
|
template. functions defined this way have no aliases and can only be called by the name given."
|
||||||
[fn-name args & body]
|
[fn-name args & body]
|
||||||
`(do
|
`(let [f# (fn ~args ~@body)]
|
||||||
(add-function! ~fn-name (fn ~args ~@body))))
|
(.store
|
||||||
|
@functions
|
||||||
|
(make-function-handler ~fn-name [] f#))
|
||||||
|
(get-function ~fn-name)))
|
||||||
|
|
||||||
(defmacro defaliasedtwigfn
|
(defmacro defaliasedtwigfn
|
||||||
"defines a new template function. templates can call it by by the name specified (or one of the
|
"defines a new template function. templates can call it by by the name specified (or one of the
|
||||||
aliases specified) and passing in the same number of arguments as in args. the return value of
|
aliases specified) and passing in the same number of arguments as in args. the return value of
|
||||||
the last form in body is returned to the template."
|
the last form in body is returned to the template."
|
||||||
[fn-name args aliases & body]
|
[fn-name args aliases & body]
|
||||||
`(do
|
`(let [f# (fn ~args ~@body)]
|
||||||
(add-function! ~fn-name ~aliases (fn ~args ~@body))))
|
(.store
|
||||||
|
@functions
|
||||||
|
(make-function-handler ~fn-name ~aliases f#))
|
||||||
|
(get-function ~fn-name)))
|
|
@ -28,7 +28,7 @@
|
||||||
; part of the filename, and if so use that file instead.
|
; part of the filename, and if so use that file instead.
|
||||||
; note that enabling this option does obviously incur a slight file I/O performance penalty
|
; note that enabling this option does obviously incur a slight file I/O performance penalty
|
||||||
; whenever these functions are used
|
; whenever these functions are used
|
||||||
:check-for-minified-web-resources true
|
:check-for-minified-web-resources false
|
||||||
|
|
||||||
; automatically convert keyword keys in maps to/from strings as necessary when being passed
|
; automatically convert keyword keys in maps to/from strings as necessary when being passed
|
||||||
; in model-maps, when passed to Jtwig functions and when returned as values from Jtwig
|
; in model-maps, when passed to Jtwig functions and when returned as values from Jtwig
|
||||||
|
@ -36,4 +36,18 @@
|
||||||
; having to do any manual conversions yourself and to keep your Clojure code as idiomatic
|
; having to do any manual conversions yourself and to keep your Clojure code as idiomatic
|
||||||
; as possible. Jtwig model-maps at the very least do require all the keys to be strings
|
; as possible. Jtwig model-maps at the very least do require all the keys to be strings
|
||||||
; (not keywords) to ensure that model-map value resolution works as expected.
|
; (not keywords) to ensure that model-map value resolution works as expected.
|
||||||
:auto-convert-map-keywords true}))
|
:auto-convert-map-keywords true
|
||||||
|
|
||||||
|
; the root path (relative to the classpath) where web resources such as js, css, images are
|
||||||
|
; located. typically in your project structure this path will be located under the
|
||||||
|
; "resources" directory.
|
||||||
|
:web-resource-path-root "public"
|
||||||
|
|
||||||
|
; when enabled, exceptions will be thrown when attempting to use variables that do not exist
|
||||||
|
:strict-mode false
|
||||||
|
|
||||||
|
; allows for changing the tag symbols used from normal Twig-style to a style more friendly
|
||||||
|
; with certain Javascript template engines (e.g. Angular).
|
||||||
|
; :default => tag: {% %}, output: {{ }}, comment: {# #}
|
||||||
|
; :js => tag: <# #>, output: <@ @>, comment: <$ $>
|
||||||
|
:tag-symbols :default}))
|
164
src/clojure/clj_jtwig/standard_functions.clj
Normal file
164
src/clojure/clj_jtwig/standard_functions.clj
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
(ns clj-jtwig.standard-functions
|
||||||
|
"standard function definitions. these are functions that are not yet included in Jtwig's standard function
|
||||||
|
library and are just here to fill in the gaps for now."
|
||||||
|
(:import (org.apache.commons.lang3.text WordUtils)
|
||||||
|
(org.apache.commons.lang3 StringUtils))
|
||||||
|
(:use [clojure.pprint]
|
||||||
|
[clj-jtwig.function-utils]
|
||||||
|
[clj-jtwig.options]))
|
||||||
|
|
||||||
|
(defn- possible-keyword-string [x]
|
||||||
|
(if (and (:auto-convert-map-keywords @options)
|
||||||
|
(string? x))
|
||||||
|
(keyword x)
|
||||||
|
x))
|
||||||
|
|
||||||
|
(deflibrary standard-functions
|
||||||
|
(library-aliased-function "blank_if_null" ["nonull"] [x]
|
||||||
|
(if (nil? x) "" x))
|
||||||
|
|
||||||
|
(library-function "butlast" [sequence]
|
||||||
|
; matching behaviour of jtwig's first/last implementation
|
||||||
|
(if (map? sequence)
|
||||||
|
(-> sequence vals butlast)
|
||||||
|
(butlast sequence)))
|
||||||
|
|
||||||
|
(library-function "center" [s size & [padding-string]]
|
||||||
|
(StringUtils/center s size (or padding-string " ")))
|
||||||
|
|
||||||
|
(library-function "contains" [coll value]
|
||||||
|
(cond
|
||||||
|
(map? coll) (contains? coll (possible-keyword-string value))
|
||||||
|
(string? coll) (.contains coll value)
|
||||||
|
; explicit use of '=' to allow testing for falsey values
|
||||||
|
(coll? coll) (not (nil? (some #(= value %) coll)))
|
||||||
|
:else (throw (new Exception (str "'contains' passed invalid collection type: " (type coll))))))
|
||||||
|
|
||||||
|
(library-function "dump" [x]
|
||||||
|
(with-out-str
|
||||||
|
(clojure.pprint/pprint x)))
|
||||||
|
|
||||||
|
(library-function "dump_table" [x]
|
||||||
|
(with-out-str
|
||||||
|
(clojure.pprint/print-table x)))
|
||||||
|
|
||||||
|
(library-function "index_of" [coll value]
|
||||||
|
(cond
|
||||||
|
(instance? java.util.List coll) (.indexOf coll value)
|
||||||
|
(string? coll) (.indexOf coll (if (char? value) (int value) value))
|
||||||
|
:else (throw (new Exception (str "'index_of' passed invalid collection type: " (type coll))))))
|
||||||
|
|
||||||
|
(library-function "last_index_of" [coll value]
|
||||||
|
(cond
|
||||||
|
(instance? java.util.List coll) (.lastIndexOf coll value)
|
||||||
|
(string? coll) (.lastIndexOf coll (if (char? value) (int value) value))
|
||||||
|
:else (throw (new Exception (str "'last_index_of' passed invalid collection type: " (type coll))))))
|
||||||
|
|
||||||
|
(library-function "max" [& numbers]
|
||||||
|
(if (coll? (first numbers))
|
||||||
|
(apply max (first numbers))
|
||||||
|
(apply max numbers)))
|
||||||
|
|
||||||
|
(library-function "min" [& numbers]
|
||||||
|
(if (coll? (first numbers))
|
||||||
|
(apply min (first numbers))
|
||||||
|
(apply min numbers)))
|
||||||
|
|
||||||
|
(library-function "normalize_space" [s]
|
||||||
|
(StringUtils/normalizeSpace s))
|
||||||
|
|
||||||
|
(library-function "nth" [sequence index & [optional-not-found]]
|
||||||
|
(let [values (if (map? sequence) ; map instance check to match behaviour of jtwig's first/last implementation
|
||||||
|
(-> sequence vals)
|
||||||
|
sequence)]
|
||||||
|
(if optional-not-found
|
||||||
|
(nth values index optional-not-found)
|
||||||
|
(nth values index))))
|
||||||
|
|
||||||
|
(library-function "pad_left" [s size & [padding-string]]
|
||||||
|
(StringUtils/leftPad s size (or padding-string " ")))
|
||||||
|
|
||||||
|
(library-function "pad_right" [s size & [padding-string]]
|
||||||
|
(StringUtils/rightPad s size (or padding-string " ")))
|
||||||
|
|
||||||
|
(library-function "random" [& values]
|
||||||
|
(let [first-value (first values)]
|
||||||
|
(cond
|
||||||
|
(and (= (count values) 1)
|
||||||
|
(coll? first-value))
|
||||||
|
(rand-nth first-value)
|
||||||
|
|
||||||
|
(> (count values) 1)
|
||||||
|
(rand-nth values)
|
||||||
|
|
||||||
|
(string? first-value)
|
||||||
|
(rand-nth (seq first-value))
|
||||||
|
|
||||||
|
(number? first-value)
|
||||||
|
(rand-int first-value)
|
||||||
|
|
||||||
|
:else
|
||||||
|
(rand))))
|
||||||
|
|
||||||
|
(library-function "range" [low high & [step]]
|
||||||
|
(range low high (or step 1)))
|
||||||
|
|
||||||
|
(library-function "repeat" [s n]
|
||||||
|
(StringUtils/repeat s n))
|
||||||
|
|
||||||
|
(library-function "rest" [sequence]
|
||||||
|
; matching behaviour of jtwig's first/last implementation
|
||||||
|
(if (map? sequence)
|
||||||
|
(-> sequence vals rest)
|
||||||
|
(rest sequence)))
|
||||||
|
|
||||||
|
(library-function "second" [sequence]
|
||||||
|
; matching behaviour of jtwig's first/last implementation
|
||||||
|
(if (map? sequence)
|
||||||
|
(-> sequence vals second)
|
||||||
|
(second sequence)))
|
||||||
|
|
||||||
|
(library-function "sort" [sequence]
|
||||||
|
(sort < sequence))
|
||||||
|
|
||||||
|
(library-aliased-function "sort_descending" ["sort_desc"] [sequence]
|
||||||
|
(sort > sequence))
|
||||||
|
|
||||||
|
(library-function "sort_by" [coll k]
|
||||||
|
(let [sort-key (possible-keyword-string k)]
|
||||||
|
(sort-by #(get % sort-key) coll)))
|
||||||
|
|
||||||
|
(library-aliased-function "sort_descending_by" ["sort_desc_by"] [coll k]
|
||||||
|
(let [sort-key (possible-keyword-string k)]
|
||||||
|
(sort-by #(get % sort-key) #(compare %2 %1) coll)))
|
||||||
|
|
||||||
|
(library-function "to_double" [x]
|
||||||
|
(Double/parseDouble x))
|
||||||
|
|
||||||
|
(library-function "to_float" [x]
|
||||||
|
(Float/parseFloat x))
|
||||||
|
|
||||||
|
(library-function "to_int" [x]
|
||||||
|
(Integer/parseInt x))
|
||||||
|
|
||||||
|
(library-function "to_keyword" [x]
|
||||||
|
(keyword x))
|
||||||
|
|
||||||
|
(library-function "to_long" [x]
|
||||||
|
(Long/parseLong x))
|
||||||
|
|
||||||
|
(library-function "to_string" [x]
|
||||||
|
(cond
|
||||||
|
(keyword? x) (name x)
|
||||||
|
(instance? clojure.lang.LazySeq x) (str (seq x))
|
||||||
|
(coll? x) (str x)
|
||||||
|
:else (.toString x)))
|
||||||
|
|
||||||
|
(library-function "wrap" [s length & [wrap-long-words? new-line-string]]
|
||||||
|
(WordUtils/wrap
|
||||||
|
s
|
||||||
|
length
|
||||||
|
new-line-string
|
||||||
|
(if (nil? wrap-long-words?)
|
||||||
|
false
|
||||||
|
wrap-long-words?))))
|
|
@ -5,13 +5,10 @@
|
||||||
(:import (java.net URI))
|
(:import (java.net URI))
|
||||||
(:require [clj-jtwig.web.middleware :refer [*servlet-context-path*]]
|
(:require [clj-jtwig.web.middleware :refer [*servlet-context-path*]]
|
||||||
[clojure.string :as str])
|
[clojure.string :as str])
|
||||||
(:use [clj-jtwig.utils]
|
(:use [clj-jtwig.function-utils]
|
||||||
|
[clj-jtwig.utils]
|
||||||
[clj-jtwig.options]))
|
[clj-jtwig.options]))
|
||||||
|
|
||||||
;; TODO: while 'public' is the default with Compojure, applications can override with something else ...
|
|
||||||
;; should make this customizable (some option added to clj-jtwig.options likely ...)
|
|
||||||
(def root-resource-path "public")
|
|
||||||
|
|
||||||
(defn- get-context-url [url]
|
(defn- get-context-url [url]
|
||||||
(str *servlet-context-path* url))
|
(str *servlet-context-path* url))
|
||||||
|
|
||||||
|
@ -22,8 +19,7 @@
|
||||||
|
|
||||||
(defn- get-resource-modification-timestamp [^String resource-url]
|
(defn- get-resource-modification-timestamp [^String resource-url]
|
||||||
(if (relative-url? resource-url)
|
(if (relative-url? resource-url)
|
||||||
|
(->> (str (:web-resource-path-root @options) resource-url)
|
||||||
(->> (str root-resource-path resource-url)
|
|
||||||
(get-context-url)
|
(get-context-url)
|
||||||
(get-resource-modification-date))))
|
(get-resource-modification-date))))
|
||||||
|
|
||||||
|
@ -51,27 +47,22 @@
|
||||||
(minified-url? url))
|
(minified-url? url))
|
||||||
url
|
url
|
||||||
(let [minified-url (make-minified-url url)]
|
(let [minified-url (make-minified-url url)]
|
||||||
(if (get-resource-path (str root-resource-path minified-url))
|
(if (get-resource-path (str (:web-resource-path-root @options) minified-url))
|
||||||
minified-url
|
minified-url
|
||||||
url))))
|
url))))
|
||||||
|
|
||||||
; defined using the same type of map structure as in clj-jtwig.standard-functions
|
(deflibrary web-functions
|
||||||
|
(library-function "path" [url]
|
||||||
|
(get-context-url url))
|
||||||
|
|
||||||
(defonce web-functions
|
(library-function "stylesheet" [url & [media]]
|
||||||
{"path"
|
(let [fmt (if media
|
||||||
{:fn (fn [url]
|
"<link href=\"%s\" rel=\"stylesheet\" type=\"text/css\" media=\"%s\" />"
|
||||||
(get-context-url url))}
|
"<link href=\"%s\" rel=\"stylesheet\" type=\"text/css\" />")
|
||||||
|
resource-path (get-minified-resource-url url)]
|
||||||
|
(format fmt (get-url-string resource-path) media)))
|
||||||
|
|
||||||
"stylesheet"
|
(library-function "javascript" [url]
|
||||||
{:fn (fn [url & [media]]
|
(let [fmt "<script type=\"text/javascript\" src=\"%s\"></script>"
|
||||||
(let [fmt (if media
|
resource-path (get-minified-resource-url url)]
|
||||||
"<link href=\"%s\" rel=\"stylesheet\" type=\"text/css\" media=\"%s\" />"
|
(format fmt (get-url-string resource-path)))))
|
||||||
"<link href=\"%s\" rel=\"stylesheet\" type=\"text/css\" />")
|
|
||||||
resource-path (get-minified-resource-url url)]
|
|
||||||
(format fmt (get-url-string resource-path) media)))}
|
|
||||||
|
|
||||||
"javascript"
|
|
||||||
{:fn (fn [url]
|
|
||||||
(let [fmt "<script type=\"text/javascript\" src=\"%s\"></script>"
|
|
||||||
resource-path (get-minified-resource-url url)]
|
|
||||||
(format fmt (get-url-string resource-path))))}})
|
|
10
src/java/clj_jtwig/TemplateFunction.java
Normal file
10
src/java/clj_jtwig/TemplateFunction.java
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package clj_jtwig;
|
||||||
|
|
||||||
|
import com.lyncode.jtwig.functions.exceptions.FunctionException;
|
||||||
|
|
||||||
|
// this is defined in Java only because Clojure defprotocol/definterface don't allow
|
||||||
|
// including method definitions with vararg parameters
|
||||||
|
|
||||||
|
public interface TemplateFunction {
|
||||||
|
public abstract Object execute (Object... arguments) throws FunctionException;
|
||||||
|
}
|
|
@ -167,15 +167,15 @@
|
||||||
|
|
||||||
(set-options! :auto-convert-map-keywords true)
|
(set-options! :auto-convert-map-keywords true)
|
||||||
(is (= (render "{{keys_are_all_keywords(x)}}" {:x {:a "foo" :b "bar" :c "baz"}})
|
(is (= (render "{{keys_are_all_keywords(x)}}" {:x {:a "foo" :b "bar" :c "baz"}})
|
||||||
"true"))
|
"1"))
|
||||||
(is (= (render "{{keys_are_all_keywords(x)}}" {:x {"a" "foo" "b" "bar" "c" "baz"}})
|
(is (= (render "{{keys_are_all_keywords(x)}}" {:x {"a" "foo" "b" "bar" "c" "baz"}})
|
||||||
"true"))
|
"1"))
|
||||||
|
|
||||||
(set-options! :auto-convert-map-keywords false)
|
(set-options! :auto-convert-map-keywords false)
|
||||||
(is (= (render "{{keys_are_all_keywords(x)}}" {"x" {:a "foo" :b "bar" :c "baz"}})
|
(is (= (render "{{keys_are_all_keywords(x)}}" {"x" {:a "foo" :b "bar" :c "baz"}})
|
||||||
"true"))
|
"1"))
|
||||||
(is (= (render "{{keys_are_all_strings(x)}}" {"x" {"a" "foo" "b" "bar" "c" "baz"}})
|
(is (= (render "{{keys_are_all_strings(x)}}" {"x" {"a" "foo" "b" "bar" "c" "baz"}})
|
||||||
"true"))
|
"1"))
|
||||||
|
|
||||||
(set-options! :auto-convert-map-keywords true)
|
(set-options! :auto-convert-map-keywords true)
|
||||||
(reset-functions!))))
|
(reset-functions!))))
|
||||||
|
@ -195,16 +195,16 @@
|
||||||
(every? string? (keys x)))
|
(every? string? (keys x)))
|
||||||
|
|
||||||
(set-options! :auto-convert-map-keywords true)
|
(set-options! :auto-convert-map-keywords true)
|
||||||
(is (= (render "{{keys_are_all_keywords(get_map_with_keywords(null))}}" {})
|
(is (= (render "{{keys_are_all_keywords(get_map_with_keywords(null))}}")
|
||||||
"true"))
|
"1"))
|
||||||
(is (= (render "{{keys_are_all_keywords(get_map_with_strings(null))}}" {})
|
(is (= (render "{{keys_are_all_keywords(get_map_with_strings(null))}}")
|
||||||
"true"))
|
"1"))
|
||||||
|
|
||||||
(set-options! :auto-convert-map-keywords false)
|
(set-options! :auto-convert-map-keywords false)
|
||||||
(is (= (render "{{keys_are_all_keywords(get_map_with_keywords(null))}}" {})
|
(is (= (render "{{keys_are_all_keywords(get_map_with_keywords(null))}}")
|
||||||
"true"))
|
"1"))
|
||||||
(is (= (render "{{keys_are_all_strings(get_map_with_strings(null))}}" {})
|
(is (= (render "{{keys_are_all_strings(get_map_with_strings(null))}}")
|
||||||
"true"))
|
"1"))
|
||||||
|
|
||||||
(set-options! :auto-convert-map-keywords true)
|
(set-options! :auto-convert-map-keywords true)
|
||||||
(reset-functions!))))
|
(reset-functions!))))
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
(ns clj-jtwig.core-test
|
(ns clj-jtwig.core-test
|
||||||
(:import (java.io FileNotFoundException))
|
(:import (java.io FileNotFoundException)
|
||||||
|
(com.lyncode.jtwig.exception CalculateException RenderException))
|
||||||
(:require [clojure.test :refer :all]
|
(:require [clojure.test :refer :all]
|
||||||
[clj-jtwig.core :refer :all]
|
[clj-jtwig.core :refer :all]
|
||||||
[clj-jtwig.functions :refer :all]))
|
[clj-jtwig.functions :refer :all]))
|
||||||
|
|
||||||
; The purpose of these tests is to establish that our wrapper around JTwig works. That is,
|
; The purpose of these tests is to establish that our wrapper around Jtwig works. That is,
|
||||||
; we will be focusing on stuff like making sure that passing Clojure data structures
|
; we will be focusing on stuff like making sure that passing Clojure data structures
|
||||||
; (e.g. maps, vectors, lists) over to JTwig works fine.
|
; (e.g. maps, vectors, lists) over to Jtwig works fine.
|
||||||
; JTwig includes its own test suite which tests actual template parsing and evaluation
|
; Jtwig includes its own test suite which tests actual template parsing and evaluation
|
||||||
; functionality, so there's no point in duplicating that kind of testing here once we
|
; functionality, so there's no point in duplicating that kind of testing here once we
|
||||||
; establish that the above mentioned stuff works fine.
|
; establish that the above mentioned stuff works fine.
|
||||||
;
|
;
|
||||||
|
@ -20,9 +21,8 @@
|
||||||
{:name "Bob"})
|
{:name "Bob"})
|
||||||
"Hello Bob!")
|
"Hello Bob!")
|
||||||
"passing a model-map")
|
"passing a model-map")
|
||||||
(is (= (render "Hello {{ name }}!"
|
(is (= (render "Hello {{ name }}!")
|
||||||
nil)
|
"Hello !")
|
||||||
"Hello null!")
|
|
||||||
"not passing a model-map")
|
"not passing a model-map")
|
||||||
(is (= (render "Hello {{ name }}!"
|
(is (= (render "Hello {{ name }}!"
|
||||||
{"name" "Bob"})
|
{"name" "Bob"})
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
(set-options! :auto-convert-map-keywords true))))
|
(set-options! :auto-convert-map-keywords true))))
|
||||||
|
|
||||||
(deftest passing-model-map-data
|
(deftest passing-model-map-data
|
||||||
(testing "Passing Clojure data structures to JTwigContext's"
|
(testing "Passing Clojure data structures to JtwigContext's"
|
||||||
(is (= (render "float {{ x }}"
|
(is (= (render "float {{ x }}"
|
||||||
{:x 3.14})
|
{:x 3.14})
|
||||||
"float 3.14")
|
"float 3.14")
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
"passing an integer")
|
"passing an integer")
|
||||||
(is (= (render "null {{ x }}"
|
(is (= (render "null {{ x }}"
|
||||||
{:x nil})
|
{:x nil})
|
||||||
"null null")
|
"null ")
|
||||||
"passing a nil value")
|
"passing a nil value")
|
||||||
(is (= (render "char {{ x }}"
|
(is (= (render "char {{ x }}"
|
||||||
{:x \a})
|
{:x \a})
|
||||||
|
@ -108,9 +108,8 @@
|
||||||
(render-file invalid-filename
|
(render-file invalid-filename
|
||||||
{:name "Bob"}))
|
{:name "Bob"}))
|
||||||
"trying to render a file that doesn't exist")
|
"trying to render a file that doesn't exist")
|
||||||
(is (= (render-file test-filename
|
(is (= (render-file test-filename)
|
||||||
nil)
|
"Hello from a file!")
|
||||||
"Hello null from a file!")
|
|
||||||
"not passing a model-map")
|
"not passing a model-map")
|
||||||
(is (= (render-file test-filename
|
(is (= (render-file test-filename
|
||||||
{"name" "Bob"})
|
{"name" "Bob"})
|
||||||
|
@ -135,3 +134,39 @@
|
||||||
"passing a model-map where the keys are keywords and try skipping auto stringifying the keys")
|
"passing a model-map where the keys are keywords and try skipping auto stringifying the keys")
|
||||||
|
|
||||||
(set-options! :auto-convert-map-keywords true)))))
|
(set-options! :auto-convert-map-keywords true)))))
|
||||||
|
|
||||||
|
(deftest options
|
||||||
|
(testing "clj-jtwig options specific tests"
|
||||||
|
(do
|
||||||
|
(set-options! :strict-mode true)
|
||||||
|
|
||||||
|
(is (thrown-with-msg?
|
||||||
|
RenderException
|
||||||
|
#"com.lyncode.jtwig.exception.CalculateException"
|
||||||
|
(render "{{ foo }}"))
|
||||||
|
"trying to output a non-existant variable under strict-mode")
|
||||||
|
(is (= (render "{{ foo }}" {:foo "bar"})
|
||||||
|
"bar")
|
||||||
|
"trying to output an existing variable under strict-mode")
|
||||||
|
|
||||||
|
(set-options! :strict-mode false)
|
||||||
|
|
||||||
|
(is (= (render "{{ foo }}")
|
||||||
|
"")
|
||||||
|
"trying to output a non-existant variable under non-strict-mode")
|
||||||
|
(is (= (render "{{ foo }}" {:foo "bar"})
|
||||||
|
"bar")
|
||||||
|
"trying to output an existing variable under non-strict-mode"))
|
||||||
|
(do
|
||||||
|
(set-options! :tag-symbols :js)
|
||||||
|
(is (= (render "@> 1 <@" {:foo "bar"})
|
||||||
|
"1")
|
||||||
|
"js-style output symbols")
|
||||||
|
(is (= (render "<# if (foo) #>bar<# endif #>" {:foo true})
|
||||||
|
"bar")
|
||||||
|
"js-style tag symbols")
|
||||||
|
(is (= (render "<$ this is a comment $>")
|
||||||
|
"")
|
||||||
|
"js-style comment symbols")
|
||||||
|
|
||||||
|
(set-options! :tag-symbols :default))))
|
|
@ -1,15 +1,12 @@
|
||||||
(ns clj-jtwig.functions-test
|
(ns clj-jtwig.functions-test
|
||||||
|
(:import (com.lyncode.jtwig.functions.repository CallableFunction))
|
||||||
(:require [clojure.test :refer :all]
|
(:require [clojure.test :refer :all]
|
||||||
[clj-jtwig.core :refer :all]
|
[clj-jtwig.core :refer :all]
|
||||||
[clj-jtwig.functions :refer :all]))
|
[clj-jtwig.functions :refer :all]))
|
||||||
|
|
||||||
; TODO: is there a better way to test that something is an instance of some object generated by reify?
|
|
||||||
(defn valid-function-handler? [x]
|
(defn valid-function-handler? [x]
|
||||||
(and (not (nil? x))
|
(and (not (nil? x))
|
||||||
(-> x
|
(instance? CallableFunction x)))
|
||||||
(class)
|
|
||||||
(.getName)
|
|
||||||
(.startsWith "clj_jtwig.functions$make_function_handler"))))
|
|
||||||
|
|
||||||
(deftest template-functions
|
(deftest template-functions
|
||||||
(testing "Adding custom template functions"
|
(testing "Adding custom template functions"
|
||||||
|
@ -27,7 +24,7 @@
|
||||||
(deftwigfn "add" [a b]
|
(deftwigfn "add" [a b]
|
||||||
(+ a b))))
|
(+ a b))))
|
||||||
|
|
||||||
(is (= (render "{{add(1, 2)}}" nil)
|
(is (= (render "{{add(1, 2)}}")
|
||||||
"3")
|
"3")
|
||||||
"calling a custom function")
|
"calling a custom function")
|
||||||
(is (= (render "{{add(a, b)}}" {:a 1 :b 2})
|
(is (= (render "{{add(a, b)}}" {:a 1 :b 2})
|
||||||
|
@ -52,13 +49,13 @@
|
||||||
(is (true? (function-exists? "plus")))
|
(is (true? (function-exists? "plus")))
|
||||||
(is (true? (function-exists? "myAddFn")))
|
(is (true? (function-exists? "myAddFn")))
|
||||||
|
|
||||||
(is (= (render "{{add(1, 2)}}" nil)
|
(is (= (render "{{add(1, 2)}}")
|
||||||
"3")
|
"3")
|
||||||
"calling a custom function by name")
|
"calling a custom function by name")
|
||||||
(is (= (render "{{plus(1, 2)}}" nil)
|
(is (= (render "{{plus(1, 2)}}")
|
||||||
"3")
|
"3")
|
||||||
"calling a custom function by alias")
|
"calling a custom function by alias")
|
||||||
(is (= (render "{{myAddFn(1, 2)}}" nil)
|
(is (= (render "{{myAddFn(1, 2)}}")
|
||||||
"3")
|
"3")
|
||||||
"calling a custom function by another alias")
|
"calling a custom function by another alias")
|
||||||
|
|
||||||
|
@ -77,18 +74,17 @@
|
||||||
(apply + numbers))))
|
(apply + numbers))))
|
||||||
(is (true? (function-exists? "addAll")))
|
(is (true? (function-exists? "addAll")))
|
||||||
|
|
||||||
(is (= (render "{{add2(1, 2)}}" nil)
|
(is (= (render "{{add2(1, 2)}}")
|
||||||
"3")
|
"3")
|
||||||
"fixed number of arguments (correct amount)")
|
"fixed number of arguments (correct amount)")
|
||||||
(is (thrown-with-msg?
|
(is (thrown?
|
||||||
Exception
|
Exception
|
||||||
#"clojure\.lang\.ArityException: Wrong number of args"
|
(render "{{add2(1)}}")))
|
||||||
(render "{{add2(1)}}" nil)))
|
(is (= (render "{{addAll(1, 2, 3, 4, 5)}}")
|
||||||
(is (= (render "{{addAll(1, 2, 3, 4, 5)}}" nil)
|
|
||||||
"15")
|
"15")
|
||||||
"variable number of arguments (non-zero)")
|
"variable number of arguments (non-zero)")
|
||||||
(is (= (render "{{addAll}}" nil)
|
(is (= (render "{{addAll}}")
|
||||||
"null")
|
"")
|
||||||
"variable number of arguments (zero)")
|
"variable number of arguments (zero)")
|
||||||
|
|
||||||
(reset-functions!)))
|
(reset-functions!)))
|
||||||
|
@ -137,28 +133,28 @@
|
||||||
|
|
||||||
; verify that the clojure function recognizes the correct types when the variable is passed via a constant
|
; verify that the clojure function recognizes the correct types when the variable is passed via a constant
|
||||||
; value embedded in the template
|
; value embedded in the template
|
||||||
(is (= (render "{{typename(42)}}" nil)
|
(is (= (render "{{typename(42)}}")
|
||||||
"java.lang.Integer")
|
"java.lang.Integer")
|
||||||
"integer typename via constant value embedded in the template")
|
"integer typename via constant value embedded in the template")
|
||||||
(is (= (render "{{typename(3.14)}}" nil)
|
(is (= (render "{{typename(3.14)}}")
|
||||||
"java.lang.Double")
|
"java.lang.Double")
|
||||||
"float typename via constant value embedded in the template")
|
"float typename via constant value embedded in the template")
|
||||||
(is (= (render "{{typename('foobar')}}" nil)
|
(is (= (render "{{typename('foobar')}}")
|
||||||
"java.lang.String")
|
"java.lang.String")
|
||||||
"string typename via constant value embedded in the template")
|
"string typename via constant value embedded in the template")
|
||||||
(is (= (render "{{typename('a')}}" nil)
|
(is (= (render "{{typename('a')}}")
|
||||||
"java.lang.Character")
|
"java.lang.Character")
|
||||||
"char typename via constant value embedded in the template")
|
"char typename via constant value embedded in the template")
|
||||||
(is (= (render "{{typename(true)}}" nil)
|
(is (= (render "{{typename(true)}}")
|
||||||
"java.lang.Boolean")
|
"java.lang.Boolean")
|
||||||
"boolean typename via constant value embedded in the template")
|
"boolean typename via constant value embedded in the template")
|
||||||
(is (= (render "{{typename([1, 2, 3, 4, 5])}}" nil)
|
(is (= (render "{{typename([1, 2, 3, 4, 5])}}")
|
||||||
"clojure.lang.LazySeq")
|
"clojure.lang.LazySeq")
|
||||||
"list typename via constant value embedded in the template")
|
"list typename via constant value embedded in the template")
|
||||||
(is (= (render "{{typename(1..5)}}" nil)
|
(is (= (render "{{typename(1..5)}}")
|
||||||
"clojure.lang.LazySeq")
|
"clojure.lang.LazySeq")
|
||||||
"vector typename via constant value embedded in the template")
|
"vector typename via constant value embedded in the template")
|
||||||
(is (= (render "{{typename({a: 1, b: 'foo', c: null})}}" nil)
|
(is (= (render "{{typename({a: 1, b: 'foo', c: null})}}")
|
||||||
"clojure.lang.PersistentArrayMap")
|
"clojure.lang.PersistentArrayMap")
|
||||||
"map typename via constant value embedded in the template")
|
"map typename via constant value embedded in the template")
|
||||||
|
|
||||||
|
@ -177,7 +173,7 @@
|
||||||
"a")
|
"a")
|
||||||
"char via model-map")
|
"char via model-map")
|
||||||
(is (= (render "{{identity(x)}}" {:x true})
|
(is (= (render "{{identity(x)}}" {:x true})
|
||||||
"true")
|
"1")
|
||||||
"boolean via model-map")
|
"boolean via model-map")
|
||||||
(is (= (render "{{identity(x)}}" {:x '(1 2 3 4 5)})
|
(is (= (render "{{identity(x)}}" {:x '(1 2 3 4 5)})
|
||||||
"[1, 2, 3, 4, 5]")
|
"[1, 2, 3, 4, 5]")
|
||||||
|
@ -196,29 +192,29 @@
|
||||||
|
|
||||||
; simple passing / returning... not doing anything exciting with the arguments
|
; simple passing / returning... not doing anything exciting with the arguments
|
||||||
; using a constant value embedded inside the template
|
; using a constant value embedded inside the template
|
||||||
(is (= (render "{{identity(42)}}" nil)
|
(is (= (render "{{identity(42)}}")
|
||||||
"42")
|
"42")
|
||||||
"integer via constant value embedded in the template")
|
"integer via constant value embedded in the template")
|
||||||
(is (= (render "{{identity(3.14)}}" nil)
|
(is (= (render "{{identity(3.14)}}")
|
||||||
"3.14")
|
"3.14")
|
||||||
"float via constant value embedded in the template")
|
"float via constant value embedded in the template")
|
||||||
(is (= (render "{{identity('foobar')}}" nil)
|
(is (= (render "{{identity('foobar')}}")
|
||||||
"foobar")
|
"foobar")
|
||||||
"string via constant value embedded in the template")
|
"string via constant value embedded in the template")
|
||||||
(is (= (render "{{identity('a')}}" nil)
|
(is (= (render "{{identity('a')}}")
|
||||||
"a")
|
"a")
|
||||||
"char via constant value embedded in the template")
|
"char via constant value embedded in the template")
|
||||||
(is (= (render "{{identity(true)}}" nil)
|
(is (= (render "{{identity(true)}}")
|
||||||
"true")
|
"1")
|
||||||
"boolean via constant value embedded in the template")
|
"boolean via constant value embedded in the template")
|
||||||
(is (= (render "{{identity([1, 2, 3, 4, 5])}}" nil)
|
(is (= (render "{{identity([1, 2, 3, 4, 5])}}")
|
||||||
"[1, 2, 3, 4, 5]")
|
"[1, 2, 3, 4, 5]")
|
||||||
"enumerated list via constant value embedded in the template")
|
"enumerated list via constant value embedded in the template")
|
||||||
(is (= (render "{{identity(1..5)}}" nil)
|
(is (= (render "{{identity(1..5)}}")
|
||||||
"[1, 2, 3, 4, 5]")
|
"[1, 2, 3, 4, 5]")
|
||||||
"list by comprehension via constant value embedded in the template")
|
"list by comprehension via constant value embedded in the template")
|
||||||
; TODO: order of iteration through a map is undefined, the string being tested may not always be the same (wrt. order)
|
; TODO: order of iteration through a map is undefined, the string being tested may not always be the same (wrt. order)
|
||||||
(is (= (render "{{identity({a: 1, b: 'foo', c: null})}}" nil)
|
(is (= (render "{{identity({a: 1, b: 'foo', c: null})}}")
|
||||||
"{b=foo, c=null, a=1}")
|
"{b=foo, c=null, a=1}")
|
||||||
"map via constant value embedded in the template")
|
"map via constant value embedded in the template")
|
||||||
|
|
||||||
|
@ -232,7 +228,7 @@
|
||||||
"vector (iterating over a model-map var passed to a function and returned from it)")
|
"vector (iterating over a model-map var passed to a function and returned from it)")
|
||||||
; TODO: order of iteration through a map is undefined, the string being tested may not always be the same (wrt. order)
|
; TODO: order of iteration through a map is undefined, the string being tested may not always be the same (wrt. order)
|
||||||
(is (= (render "{% for k, v in identity(x) %}{{k}}: {{v}} {% endfor %}" {:x {:a 1 :b "foo" :c nil}})
|
(is (= (render "{% for k, v in identity(x) %}{{k}}: {{v}} {% endfor %}" {:x {:a 1 :b "foo" :c nil}})
|
||||||
"b: foo c: null a: 1 ")
|
"b: foo c: a: 1 ")
|
||||||
"map (iterating over a model-map var passed to a function and returned from it)")
|
"map (iterating over a model-map var passed to a function and returned from it)")
|
||||||
(is (= (render "{% for i in identity(x) %}{{i}} {% endfor %}" {:x #{1 2 3 4 5}})
|
(is (= (render "{% for i in identity(x) %}{{i}} {% endfor %}" {:x #{1 2 3 4 5}})
|
||||||
"1 4 3 2 5 ")
|
"1 4 3 2 5 ")
|
||||||
|
@ -240,15 +236,15 @@
|
||||||
|
|
||||||
; iterating over passed sequence/collection type arguments passed to a custom function from a constant
|
; iterating over passed sequence/collection type arguments passed to a custom function from a constant
|
||||||
; value embedded in the template and being returned
|
; value embedded in the template and being returned
|
||||||
(is (= (render "{% for i in identity([1, 2, 3, 4, 5]) %}{{i}} {% endfor %}" nil)
|
(is (= (render "{% for i in identity([1, 2, 3, 4, 5]) %}{{i}} {% endfor %}")
|
||||||
"1 2 3 4 5 ")
|
"1 2 3 4 5 ")
|
||||||
"enumerated list (iterating over a model-map var passed to a function and returned from it)")
|
"enumerated list (iterating over a model-map var passed to a function and returned from it)")
|
||||||
(is (= (render "{% for i in identity(1..5) %}{{i}} {% endfor %}" nil)
|
(is (= (render "{% for i in identity(1..5) %}{{i}} {% endfor %}")
|
||||||
"1 2 3 4 5 ")
|
"1 2 3 4 5 ")
|
||||||
"list by comprehension (iterating over a model-map var passed to a function and returned from it)")
|
"list by comprehension (iterating over a model-map var passed to a function and returned from it)")
|
||||||
; TODO: order of iteration through a map is undefined, the string being tested may not always be the same (wrt. order)
|
; TODO: order of iteration through a map is undefined, the string being tested may not always be the same (wrt. order)
|
||||||
(is (= (render "{% for k, v in identity({a: 1, b: 'foo', c: null}) %}{{k}}: {{v}} {% endfor %}" nil)
|
(is (= (render "{% for k, v in identity({a: 1, b: 'foo', c: null}) %}{{k}}: {{v}} {% endfor %}")
|
||||||
"b: foo c: null a: 1 ")
|
"b: foo c: a: 1 ")
|
||||||
"map (iterating over a model-map var passed to a function and returned from it)")
|
"map (iterating over a model-map var passed to a function and returned from it)")
|
||||||
|
|
||||||
(reset-functions!))))
|
(reset-functions!))))
|
||||||
|
@ -272,40 +268,40 @@
|
||||||
(is (true? (function-exists? "sort_descending_by"))))
|
(is (true? (function-exists? "sort_descending_by"))))
|
||||||
|
|
||||||
(testing "blank_if_null"
|
(testing "blank_if_null"
|
||||||
(is (= (render "{{ a|blank_if_null }}" nil)
|
(is (= (render "{{ a|blank_if_null }}")
|
||||||
""))
|
""))
|
||||||
(is (= (render "{{ a|blank_if_null }}" {:a nil})
|
(is (= (render "{{ a|blank_if_null }}" {:a nil})
|
||||||
""))
|
""))
|
||||||
(is (= (render "{{ a|blank_if_null }}" {:a "foo"})
|
(is (= (render "{{ a|blank_if_null }}" {:a "foo"})
|
||||||
"foo"))
|
"foo"))
|
||||||
(is (= (render "{{ a|nonull }}" nil)
|
(is (= (render "{{ a|nonull }}")
|
||||||
"")))
|
"")))
|
||||||
|
|
||||||
(testing "butlast"
|
(testing "butlast"
|
||||||
(is (= (render "{{ [1, 2, 3, 4, 5]|butlast }}" nil)
|
(is (= (render "{{ [1, 2, 3, 4, 5]|butlast }}")
|
||||||
"[1, 2, 3, 4]")))
|
"[1, 2, 3, 4]")))
|
||||||
|
|
||||||
(testing "center"
|
(testing "center"
|
||||||
(is (= (render "{{ center('bat', 5) }}" nil)
|
(is (= (render "{{ center('bat', 5) }}")
|
||||||
" bat "))
|
" bat "))
|
||||||
(is (= (render "{{ center('bat', 3) }}" nil)
|
(is (= (render "{{ center('bat', 3) }}")
|
||||||
"bat"))
|
"bat"))
|
||||||
(is (= (render "{{ center('bat', 5, 'x') }}" nil)
|
(is (= (render "{{ center('bat', 5, 'x') }}")
|
||||||
"xbatx")))
|
"xbatx")))
|
||||||
|
|
||||||
(testing "contains"
|
(testing "contains"
|
||||||
(is (= (render "{{ {a: 1, b: 2, c: 3}|contains(\"b\") }}" nil)
|
(is (= (render "{{ {a: 1, b: 2, c: 3}|contains(\"b\") }}")
|
||||||
"true"))
|
"1"))
|
||||||
(is (= (render "{{ {a: 1, b: 2, c: 3}|contains(\"d\") }}" nil)
|
(is (= (render "{{ {a: 1, b: 2, c: 3}|contains(\"d\") }}")
|
||||||
"false"))
|
"0"))
|
||||||
(is (= (render "{{ [1, 2, 3, 4]|contains(2) }}" nil)
|
(is (= (render "{{ [1, 2, 3, 4]|contains(2) }}")
|
||||||
"true"))
|
"1"))
|
||||||
(is (= (render "{{ [1, 2, 3, 4]|contains(5) }}" nil)
|
(is (= (render "{{ [1, 2, 3, 4]|contains(5) }}")
|
||||||
"false"))
|
"0"))
|
||||||
(is (= (render "{{ \"abcdef\"|contains(\"abc\") }}" nil)
|
(is (= (render "{{ \"abcdef\"|contains(\"abc\") }}")
|
||||||
"true"))
|
"1"))
|
||||||
(is (= (render "{{ \"abcdef\"|contains(\"xyz\") }}" nil)
|
(is (= (render "{{ \"abcdef\"|contains(\"xyz\") }}")
|
||||||
"false")))
|
"0")))
|
||||||
|
|
||||||
(testing "dump"
|
(testing "dump"
|
||||||
(is (= (render "{{ a|dump }}" {:a [{:foo "bar"} [1, 2, 3] "hello"]})
|
(is (= (render "{{ a|dump }}" {:a [{:foo "bar"} [1, 2, 3] "hello"]})
|
||||||
|
@ -316,115 +312,114 @@
|
||||||
"\n| :a | :b | :c |\n|----+----+-----|\n| 1 | 2 | 3 |\n| 7 | 5 | dog |\n")))
|
"\n| :a | :b | :c |\n|----+----+-----|\n| 1 | 2 | 3 |\n| 7 | 5 | dog |\n")))
|
||||||
|
|
||||||
(testing "index_of"
|
(testing "index_of"
|
||||||
(is (= (render "{{ [1, 2, 3, 2, 1]|index_of(2) }}" nil)
|
(is (= (render "{{ [1, 2, 3, 2, 1]|index_of(2) }}")
|
||||||
"1"))
|
"1"))
|
||||||
(is (= (render "{{ [1, 2, 3, 2, 1]|index_of(5) }}" nil)
|
(is (= (render "{{ [1, 2, 3, 2, 1]|index_of(5) }}")
|
||||||
"-1"))
|
"-1"))
|
||||||
(is (= (render "{{ \"abcdcba\"|index_of(\"b\") }}" nil)
|
(is (= (render "{{ \"abcdcba\"|index_of(\"b\") }}")
|
||||||
"1"))
|
"1"))
|
||||||
(is (= (render "{{ \"abcdcba\"|index_of(\"z\") }}" nil)
|
(is (= (render "{{ \"abcdcba\"|index_of(\"z\") }}")
|
||||||
"-1")))
|
"-1")))
|
||||||
|
|
||||||
(testing "last_index_of"
|
(testing "last_index_of"
|
||||||
(is (= (render "{{ [1, 2, 3, 2, 1]|last_index_of(2) }}" nil)
|
(is (= (render "{{ [1, 2, 3, 2, 1]|last_index_of(2) }}")
|
||||||
"3"))
|
"3"))
|
||||||
(is (= (render "{{ [1, 2, 3, 2, 1]|last_index_of(5) }}" nil)
|
(is (= (render "{{ [1, 2, 3, 2, 1]|last_index_of(5) }}")
|
||||||
"-1"))
|
"-1"))
|
||||||
(is (= (render "{{ \"abcdcba\"|last_index_of(\"b\") }}" nil)
|
(is (= (render "{{ \"abcdcba\"|last_index_of(\"b\") }}")
|
||||||
"5"))
|
"5"))
|
||||||
(is (= (render "{{ \"abcdcba\"|last_index_of(\"z\") }}" nil)
|
(is (= (render "{{ \"abcdcba\"|last_index_of(\"z\") }}")
|
||||||
"-1")))
|
"-1")))
|
||||||
|
|
||||||
(testing "max"
|
(testing "max"
|
||||||
(is (= (render "{{ [2, 1, 5, 3, 4]|max }}" nil)
|
(is (= (render "{{ [2, 1, 5, 3, 4]|max }}")
|
||||||
"5"))
|
"5"))
|
||||||
(is (= (render "{{ max(2, 1, 5, 3, 4) }}" nil)
|
(is (= (render "{{ max(2, 1, 5, 3, 4) }}")
|
||||||
"5")))
|
"5")))
|
||||||
|
|
||||||
(testing "min"
|
(testing "min"
|
||||||
(is (= (render "{{ [2, 1, 5, 3, 4]|min }}" nil)
|
(is (= (render "{{ [2, 1, 5, 3, 4]|min }}")
|
||||||
"1"))
|
"1"))
|
||||||
(is (= (render "{{ min(2, 1, 5, 3, 4) }}" nil)
|
(is (= (render "{{ min(2, 1, 5, 3, 4) }}")
|
||||||
"1")))
|
"1")))
|
||||||
|
|
||||||
(testing "normalize_space"
|
(testing "normalize_space"
|
||||||
(is (= (render "{{ normalize_space(' hello world ') }}" nil)
|
(is (= (render "{{ normalize_space(' hello world ') }}")
|
||||||
"hello world")))
|
"hello world")))
|
||||||
|
|
||||||
(testing "nth"
|
(testing "nth"
|
||||||
(is (= (render "{{ [1, 2, 3, 4, 5]|nth(2) }}" nil)
|
(is (= (render "{{ [1, 2, 3, 4, 5]|nth(2) }}")
|
||||||
"3"))
|
"3"))
|
||||||
(is (thrown-with-msg?
|
(is (thrown?
|
||||||
Exception
|
Exception
|
||||||
#"java.lang.IndexOutOfBoundsException"
|
(render "{{ [1, 2, 3, 4, 5]|nth(6) }}")))
|
||||||
(render "{{ [1, 2, 3, 4, 5]|nth(6) }}" nil)))
|
(is (= (render "{{ [1, 2, 3, 4, 5]|nth(6, \"not found\") }}")
|
||||||
(is (= (render "{{ [1, 2, 3, 4, 5]|nth(6, \"not found\") }}" nil)
|
|
||||||
"not found")))
|
"not found")))
|
||||||
|
|
||||||
(testing "pad_left"
|
(testing "pad_left"
|
||||||
(is (= (render "{{ pad_left('bat', 5) }}" nil)
|
(is (= (render "{{ pad_left('bat', 5) }}")
|
||||||
" bat"))
|
" bat"))
|
||||||
(is (= (render "{{ pad_left('bat', 3) }}" nil)
|
(is (= (render "{{ pad_left('bat', 3) }}")
|
||||||
"bat"))
|
"bat"))
|
||||||
(is (= (render "{{ pad_left('bat', 5, 'x') }}" nil)
|
(is (= (render "{{ pad_left('bat', 5, 'x') }}")
|
||||||
"xxbat")))
|
"xxbat")))
|
||||||
|
|
||||||
(testing "pad_right"
|
(testing "pad_right"
|
||||||
(is (= (render "{{ pad_right('bat', 5) }}" nil)
|
(is (= (render "{{ pad_right('bat', 5) }}")
|
||||||
"bat "))
|
"bat "))
|
||||||
(is (= (render "{{ pad_right('bat', 3) }}" nil)
|
(is (= (render "{{ pad_right('bat', 3) }}")
|
||||||
"bat"))
|
"bat"))
|
||||||
(is (= (render "{{ pad_right('bat', 5, 'x') }}" nil)
|
(is (= (render "{{ pad_right('bat', 5, 'x') }}")
|
||||||
"batxx")))
|
"batxx")))
|
||||||
|
|
||||||
(testing "random"
|
(testing "random"
|
||||||
(is (some #{(render "{{ ['apple', 'orange', 'citrus']|random }}" nil)}
|
(is (some #{(render "{{ ['apple', 'orange', 'citrus']|random }}")}
|
||||||
["apple" "orange" "citrus"]))
|
["apple" "orange" "citrus"]))
|
||||||
(is (some #{(render "{{ \"ABC\"|random }}" nil)}
|
(is (some #{(render "{{ \"ABC\"|random }}")}
|
||||||
["A" "B" "C"])))
|
["A" "B" "C"])))
|
||||||
|
|
||||||
(testing "range"
|
(testing "range"
|
||||||
(is (= (render "{{ range(1, 5) }}" nil)
|
(is (= (render "{{ range(1, 5) }}")
|
||||||
"[1, 2, 3, 4]"))
|
"[1, 2, 3, 4]"))
|
||||||
(is (= (render "{{ range(1, 5, 2) }}" nil)
|
(is (= (render "{{ range(1, 5, 2) }}")
|
||||||
"[1, 3]")))
|
"[1, 3]")))
|
||||||
|
|
||||||
(testing "repeat"
|
(testing "repeat"
|
||||||
(is (= (render "{{ repeat('x', 10) }}" nil)
|
(is (= (render "{{ repeat('x', 10) }}")
|
||||||
"xxxxxxxxxx"))
|
"xxxxxxxxxx"))
|
||||||
(is (= (render "{{ repeat('x', 0) }}" nil)
|
(is (= (render "{{ repeat('x', 0) }}")
|
||||||
"")))
|
"")))
|
||||||
|
|
||||||
(testing "rest"
|
(testing "rest"
|
||||||
(is (= (render "{{ [1, 2, 3, 4, 5]|rest }}" nil)
|
(is (= (render "{{ [1, 2, 3, 4, 5]|rest }}")
|
||||||
"[2, 3, 4, 5]")))
|
"[2, 3, 4, 5]")))
|
||||||
|
|
||||||
(testing "second"
|
(testing "second"
|
||||||
(is (= (render "{{ [1, 2, 3, 4, 5]|second }}" nil)
|
(is (= (render "{{ [1, 2, 3, 4, 5]|second }}")
|
||||||
"2")))
|
"2")))
|
||||||
|
|
||||||
(testing "sort"
|
(testing "sort"
|
||||||
(is (= (render "{{ [2, 1, 5, 3, 4]|sort }}" nil)
|
(is (= (render "{{ [2, 1, 5, 3, 4]|sort }}")
|
||||||
"[1, 2, 3, 4, 5]")))
|
"[1, 2, 3, 4, 5]")))
|
||||||
|
|
||||||
(testing "sort_descending"
|
(testing "sort_descending"
|
||||||
(is (= (render "{{ [2, 1, 5, 3, 4]|sort_descending }}" nil)
|
(is (= (render "{{ [2, 1, 5, 3, 4]|sort_descending }}")
|
||||||
"[5, 4, 3, 2, 1]")))
|
"[5, 4, 3, 2, 1]")))
|
||||||
|
|
||||||
(testing "sort_by"
|
(testing "sort_by"
|
||||||
(is (= (render "{{ [{a: 2}, {a: 1}, {a: 5}, {a: 3}, {a: 4}]|sort_by(\"a\") }}" nil)
|
(is (= (render "{{ [{a: 2}, {a: 1}, {a: 5}, {a: 3}, {a: 4}]|sort_by(\"a\") }}")
|
||||||
"[{a=1}, {a=2}, {a=3}, {a=4}, {a=5}]")))
|
"[{a=1}, {a=2}, {a=3}, {a=4}, {a=5}]")))
|
||||||
|
|
||||||
(testing "sort_descending_by"
|
(testing "sort_descending_by"
|
||||||
(is (= (render "{{ [{a: 2}, {a: 1}, {a: 5}, {a: 3}, {a: 4}]|sort_descending_by(\"a\") }}" nil)
|
(is (= (render "{{ [{a: 2}, {a: 1}, {a: 5}, {a: 3}, {a: 4}]|sort_descending_by(\"a\") }}")
|
||||||
"[{a=5}, {a=4}, {a=3}, {a=2}, {a=1}]")))
|
"[{a=5}, {a=4}, {a=3}, {a=2}, {a=1}]")))
|
||||||
|
|
||||||
(testing "wrap"
|
(testing "wrap"
|
||||||
(is (= (render "{{ wrap(\"Here is one line of text that is going to be wrapped after 20 columns.\", 20) }}" nil)
|
(is (= (render "{{ wrap(\"Here is one line of text that is going to be wrapped after 20 columns.\", 20) }}")
|
||||||
"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."))
|
"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."))
|
||||||
(is (= (render "{{ wrap(\"Here is one line of text that is going to be wrapped after 20 columns.\", 20, false, \"<br />\") }}" nil)
|
(is (= (render "{{ wrap(\"Here is one line of text that is going to be wrapped after 20 columns.\", 20, false, \"<br />\") }}")
|
||||||
"Here is one line of<br />text that is going<br />to be wrapped after<br />20 columns."))
|
"Here is one line of<br />text that is going<br />to be wrapped after<br />20 columns."))
|
||||||
(is (= (render "{{ wrap(\"Click here to jump to the commons website - http://commons.apache.org\", 20, false) }}" nil)
|
(is (= (render "{{ wrap(\"Click here to jump to the commons website - http://commons.apache.org\", 20, false) }}")
|
||||||
"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apache.org"))
|
"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apache.org"))
|
||||||
(is (= (render "{{ wrap(\"Click here to jump to the commons website - http://commons.apache.org\", 20, true) }}" nil)
|
(is (= (render "{{ wrap(\"Click here to jump to the commons website - http://commons.apache.org\", 20, true) }}")
|
||||||
"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apach\ne.org"))))
|
"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apach\ne.org"))))
|
||||||
|
|
Reference in a new issue