From 7532d61008dbbc819d9fb6169b9a6e442d0c59d1 Mon Sep 17 00:00:00 2001 From: gered Date: Mon, 3 Mar 2014 09:38:52 -0500 Subject: [PATCH 01/13] update to jtwig 2.1.1 --- project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.clj b/project.clj index 698b5e9..9173030 100644 --- a/project.clj +++ b/project.clj @@ -6,4 +6,4 @@ :repositories [["sonatype" {:url "http://oss.sonatype.org/content/repositories/releases" :snapshots false}]] :dependencies [[org.clojure/clojure "1.5.1"] - [com.lyncode/jtwig-core "2.1.0"]]) + [com.lyncode/jtwig-core "2.1.1"]]) From 61284471215ef40a399dfed7de6aaaf95858979e Mon Sep 17 00:00:00 2001 From: gered Date: Tue, 4 Mar 2014 08:40:26 -0500 Subject: [PATCH 02/13] move code to manage twig template functions into separate namespace --- src/clj_jtwig/core.clj | 50 +++--------------------------------- src/clj_jtwig/functions.clj | 48 ++++++++++++++++++++++++++++++++++ test/clj_jtwig/core_test.clj | 5 ++-- 3 files changed, 54 insertions(+), 49 deletions(-) create mode 100644 src/clj_jtwig/functions.clj diff --git a/src/clj_jtwig/core.clj b/src/clj_jtwig/core.clj index 1c8893e..fe29a83 100644 --- a/src/clj_jtwig/core.clj +++ b/src/clj_jtwig/core.clj @@ -1,13 +1,10 @@ (ns clj-jtwig.core "wrapper functions for working with JTwig from clojure" - (:require [clojure.walk :refer [stringify-keys]] - [clj-jtwig.convert :refer [java->clojure clojure->java]]) (:import (com.lyncode.jtwig JtwigTemplate JtwigContext JtwigModelMap) - (com.lyncode.jtwig.functions.exceptions FunctionNotFoundException) - (com.lyncode.jtwig.functions.repository DefaultFunctionRepository) - (com.lyncode.jtwig.functions JtwigFunction) (com.lyncode.jtwig.tree.api Content) - (java.io File FileNotFoundException ByteArrayOutputStream))) + (java.io File FileNotFoundException ByteArrayOutputStream)) + (:require [clojure.walk :refer [stringify-keys]]) + (:use [clj-jtwig.functions])) ; global options (defonce options (atom {; true/false to enable/disable compiled template caching when using templates from @@ -131,47 +128,6 @@ [] (reset! compiled-templates {})) -(defn- create-function-repository [] - (new DefaultFunctionRepository (make-array JtwigFunction 0))) - -; we'll be reusing the same function repository object for all contexts created when rendering templates. -; any custom functions added will be added to this instance -(defonce functions (atom (create-function-repository))) - -(defn reset-functions! - "removes any added custom template function handlers" - [] - (reset! functions (create-function-repository))) - -(defn function-exists? [name] - (try - (.retrieve @functions name) - true - (catch FunctionNotFoundException ex - false))) - -(defn add-function! - "adds a new template function using the name specified. templates can call the function by the - name specified and passing in the same number of arguments accepted by f. the return value of - f is returned to the template. - prefer to use the 'deftwigfn' macro when possible." - [name f] - (if (function-exists? name) - (throw (new Exception (str "JTwig template function \"" name "\" already defined."))) - (let [handler (reify JtwigFunction - (execute [_ arguments] - (clojure->java (apply f (map java->clojure arguments)))))] - (.add @functions handler name (make-array String 0)) - (.retrieve @functions name)))) - -(defmacro deftwigfn - "adds 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 - template." - [fn-name args & body] - `(do - (add-function! ~fn-name (fn ~args ~@body)))) - (defn- get-resource-path [filename] (-> (Thread/currentThread) (.getContextClassLoader) diff --git a/src/clj_jtwig/functions.clj b/src/clj_jtwig/functions.clj new file mode 100644 index 0000000..27c7818 --- /dev/null +++ b/src/clj_jtwig/functions.clj @@ -0,0 +1,48 @@ +(ns clj-jtwig.functions + "standard functions added to jtwig contexts by default. these are in addition to the + functions added by default in all jtwig function repository objects" + (:import (com.lyncode.jtwig.functions JtwigFunction) + (com.lyncode.jtwig.functions.repository DefaultFunctionRepository) + (com.lyncode.jtwig.functions.exceptions FunctionNotFoundException)) + (:require [clj-jtwig.convert :refer [java->clojure clojure->java]])) + +(defn- create-function-repository [] + (new DefaultFunctionRepository (make-array JtwigFunction 0))) + +; we'll be reusing the same function repository object for all contexts created when rendering templates. +; any custom functions added will be added to this instance +(defonce functions (atom (create-function-repository))) + +(defn reset-functions! + "removes any added custom template function handlers" + [] + (reset! functions (create-function-repository))) + +(defn function-exists? [name] + (try + (.retrieve @functions name) + true + (catch FunctionNotFoundException ex + false))) + +(defn add-function! + "adds a new template function using the name specified. templates can call the function by the + name specified and passing in the same number of arguments accepted by f. the return value of + f is returned to the template. + prefer to use the 'deftwigfn' macro when possible." + [name f] + (if (function-exists? name) + (throw (new Exception (str "JTwig template function \"" name "\" already defined."))) + (let [handler (reify JtwigFunction + (execute [_ arguments] + (clojure->java (apply f (map java->clojure arguments)))))] + (.add @functions handler name (make-array String 0)) + (.retrieve @functions name)))) + +(defmacro deftwigfn + "adds 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 + template." + [fn-name args & body] + `(do + (add-function! ~fn-name (fn ~args ~@body)))) diff --git a/test/clj_jtwig/core_test.clj b/test/clj_jtwig/core_test.clj index 92c45c2..8399e3f 100644 --- a/test/clj_jtwig/core_test.clj +++ b/test/clj_jtwig/core_test.clj @@ -2,7 +2,8 @@ (:import (java.io FileNotFoundException) (clojure.lang ArityException)) (:require [clojure.test :refer :all] - [clj-jtwig.core :refer :all])) + [clj-jtwig.core :refer :all] + [clj-jtwig.functions :refer :all])) ; 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 @@ -20,7 +21,7 @@ (-> x (class) (.getName) - (.startsWith "clj_jtwig.core$add_function")))) + (.startsWith "clj_jtwig.functions$add_function")))) (deftest string-template (testing "Evaluating templates in string vars" From 1e46b1f207652f431e4c68a3f05bc5b473138e50 Mon Sep 17 00:00:00 2001 From: gered Date: Tue, 4 Mar 2014 10:53:23 -0500 Subject: [PATCH 03/13] allow redefining template functions. wrap exceptions in FunctionException --- src/clj_jtwig/functions.clj | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/clj_jtwig/functions.clj b/src/clj_jtwig/functions.clj index 27c7818..e4794ff 100644 --- a/src/clj_jtwig/functions.clj +++ b/src/clj_jtwig/functions.clj @@ -3,7 +3,7 @@ functions added by default in all jtwig function repository objects" (:import (com.lyncode.jtwig.functions JtwigFunction) (com.lyncode.jtwig.functions.repository DefaultFunctionRepository) - (com.lyncode.jtwig.functions.exceptions FunctionNotFoundException)) + (com.lyncode.jtwig.functions.exceptions FunctionNotFoundException FunctionException)) (:require [clj-jtwig.convert :refer [java->clojure clojure->java]])) (defn- create-function-repository [] @@ -31,13 +31,15 @@ f is returned to the template. prefer to use the 'deftwigfn' macro when possible." [name f] - (if (function-exists? name) - (throw (new Exception (str "JTwig template function \"" name "\" already defined."))) - (let [handler (reify JtwigFunction - (execute [_ arguments] - (clojure->java (apply f (map java->clojure arguments)))))] - (.add @functions handler name (make-array String 0)) - (.retrieve @functions name)))) + (let [handler (reify JtwigFunction + (execute [_ arguments] + (try + (clojure->java (apply f (map java->clojure arguments))) + (catch Exception ex + (println "exception!") + (throw (new FunctionException (.getMessage ex) ex))))))] + (.add @functions handler name (make-array String 0)) + (.retrieve @functions name))) (defmacro deftwigfn "adds a new template function. templates can call it by by the name specified and passing in the From f879a59b72eef1474bda7fb906070ace31521afc Mon Sep 17 00:00:00 2001 From: gered Date: Tue, 4 Mar 2014 10:54:14 -0500 Subject: [PATCH 04/13] get rid of debug println --- src/clj_jtwig/functions.clj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/clj_jtwig/functions.clj b/src/clj_jtwig/functions.clj index e4794ff..ccc78d4 100644 --- a/src/clj_jtwig/functions.clj +++ b/src/clj_jtwig/functions.clj @@ -36,8 +36,7 @@ (try (clojure->java (apply f (map java->clojure arguments))) (catch Exception ex - (println "exception!") - (throw (new FunctionException (.getMessage ex) ex))))))] + (throw (new FunctionException ex))))))] (.add @functions handler name (make-array String 0)) (.retrieve @functions name))) From bd6b5962cb981a0593277d3b286d50fa441134c4 Mon Sep 17 00:00:00 2001 From: gered Date: Tue, 4 Mar 2014 12:10:43 -0500 Subject: [PATCH 05/13] add support for template function aliases --- src/clj_jtwig/functions.clj | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/clj_jtwig/functions.clj b/src/clj_jtwig/functions.clj index ccc78d4..42d9ac0 100644 --- a/src/clj_jtwig/functions.clj +++ b/src/clj_jtwig/functions.clj @@ -25,25 +25,41 @@ (catch FunctionNotFoundException ex false))) +(defn- make-aliases-array [aliases] + (let [n (count aliases) + array (make-array String n)] + (doseq [index (range n)] + (aset array index (nth aliases index))) + array)) + (defn add-function! "adds a new template function using the name specified. templates can call the function by the - name specified and passing in the same number of arguments accepted by f. the return value of - f is returned to the template. + name specified (or one of the aliases specified) and passing in the same number of arguments + accepted by f. the return value of f is returned to the template. if this function has no aliases + then nil can be specified for the aliases arg. prefer to use the 'deftwigfn' macro when possible." - [name f] + [name aliases f] (let [handler (reify JtwigFunction (execute [_ arguments] (try (clojure->java (apply f (map java->clojure arguments))) (catch Exception ex (throw (new FunctionException ex))))))] - (.add @functions handler name (make-array String 0)) + (.add @functions handler name (make-aliases-array aliases)) (.retrieve @functions name))) (defmacro deftwigfn "adds 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 - template." + template. functions defined this way have no aliases and can only be called by the name given." [fn-name args & body] `(do - (add-function! ~fn-name (fn ~args ~@body)))) + (add-function! ~fn-name nil (fn ~args ~@body)))) + +(defmacro defaliasedtwigfn + "adds 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 + the last form in body is returned to the template." + [fn-name args aliases & body] + `(do + (add-function! ~fn-name ~aliases (fn ~args ~@body)))) \ No newline at end of file From 880dd02e91294bb3ad979370638590cc52116e5f Mon Sep 17 00:00:00 2001 From: gered Date: Tue, 4 Mar 2014 12:11:53 -0500 Subject: [PATCH 06/13] add standard functions (those not already provided by jtwig) --- src/clj_jtwig/functions.clj | 82 ++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/src/clj_jtwig/functions.clj b/src/clj_jtwig/functions.clj index 42d9ac0..9ae4923 100644 --- a/src/clj_jtwig/functions.clj +++ b/src/clj_jtwig/functions.clj @@ -62,4 +62,84 @@ the last form in body is returned to the template." [fn-name args aliases & body] `(do - (add-function! ~fn-name ~aliases (fn ~args ~@body)))) \ No newline at end of file + (add-function! ~fn-name ~aliases (fn ~args ~@body)))) + +;; ============================================================================ +;; Standard functions +;; ============================================================================ + +(deftwigfn "blankIfNull" [x] + (if (nil? x) "" x)) + +(deftwigfn "butlast" [sequence] + ; matching behaviour of jtwig's first/last implementation + (if (map? sequence) + (-> sequence vals butlast) + (butlast sequence))) + +(deftwigfn "dump" [x] + (with-out-str (clojure.pprint/pprint x))) + +(deftwigfn "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 (first optional-not-found)) + (nth values index)))) + +(deftwigfn "max" [& numbers] + (if (coll? (first numbers)) + (apply max (first numbers)) + (apply max numbers))) + +(deftwigfn "min" [& numbers] + (if (coll? (first numbers)) + (apply min (first numbers)) + (apply min numbers))) + +(deftwigfn "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)))) + +(deftwigfn "range" [low high & [step]] + (range low high (or step 1))) + +(deftwigfn "rest" [sequence] + ; matching behaviour of jtwig's first/last implementation + (if (map? sequence) + (-> sequence vals rest) + (rest sequence))) + +(deftwigfn "second" [sequence] + ; matching behaviour of jtwig's first/last implementation + (if (map? sequence) + (-> sequence vals second) + (second sequence))) + +(deftwigfn "sort" [sequence] + (sort < sequence)) + +(deftwigfn "sortDescending" [sequence] + (sort > sequence)) + +(deftwigfn "sortBy" [coll k] + (sort-by #(get % k) coll)) + +(deftwigfn "sortDescendingBy" [coll k] + (sort-by #(get % k) #(compare %2 %1) coll)) \ No newline at end of file From 8041c4cae3f0fa6446451b8e133af9e680d2c1a5 Mon Sep 17 00:00:00 2001 From: gered Date: Tue, 4 Mar 2014 12:14:10 -0500 Subject: [PATCH 07/13] pprint isn't really a 'core' function i guess, so we should have a :use tests failed to run, complaining about pprint being undefined until this was added anyway --- src/clj_jtwig/functions.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/clj_jtwig/functions.clj b/src/clj_jtwig/functions.clj index 9ae4923..67454bd 100644 --- a/src/clj_jtwig/functions.clj +++ b/src/clj_jtwig/functions.clj @@ -4,7 +4,8 @@ (:import (com.lyncode.jtwig.functions JtwigFunction) (com.lyncode.jtwig.functions.repository DefaultFunctionRepository) (com.lyncode.jtwig.functions.exceptions FunctionNotFoundException FunctionException)) - (:require [clj-jtwig.convert :refer [java->clojure clojure->java]])) + (:require [clj-jtwig.convert :refer [java->clojure clojure->java]]) + (:use [clojure.pprint])) (defn- create-function-repository [] (new DefaultFunctionRepository (make-array JtwigFunction 0))) From f71272a8a52b8dce5cef9c85b5d69ce6f08fd58f Mon Sep 17 00:00:00 2001 From: gered Date: Tue, 4 Mar 2014 12:42:38 -0500 Subject: [PATCH 08/13] typo --- src/clj_jtwig/functions.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clj_jtwig/functions.clj b/src/clj_jtwig/functions.clj index 67454bd..bc231bb 100644 --- a/src/clj_jtwig/functions.clj +++ b/src/clj_jtwig/functions.clj @@ -26,7 +26,7 @@ (catch FunctionNotFoundException ex false))) -(defn- make-aliases-array [aliases] +(defn- make-aliased-array [aliases] (let [n (count aliases) array (make-array String n)] (doseq [index (range n)] @@ -46,7 +46,7 @@ (clojure->java (apply f (map java->clojure arguments))) (catch Exception ex (throw (new FunctionException ex))))))] - (.add @functions handler name (make-aliases-array aliases)) + (.add @functions handler name (make-aliased-array aliases)) (.retrieve @functions name))) (defmacro deftwigfn From a7e6b70ea46e0ddbd4b66d96dc0014b0c0456d8c Mon Sep 17 00:00:00 2001 From: gered Date: Tue, 4 Mar 2014 13:25:58 -0500 Subject: [PATCH 09/13] standard function now defined in a map which can be easily reloaded --- src/clj_jtwig/functions.clj | 122 ++++++--------------------- src/clj_jtwig/standard_functions.clj | 103 ++++++++++++++++++++++ 2 files changed, 129 insertions(+), 96 deletions(-) create mode 100644 src/clj_jtwig/standard_functions.clj diff --git a/src/clj_jtwig/functions.clj b/src/clj_jtwig/functions.clj index bc231bb..a2ef13f 100644 --- a/src/clj_jtwig/functions.clj +++ b/src/clj_jtwig/functions.clj @@ -5,17 +5,39 @@ (com.lyncode.jtwig.functions.repository DefaultFunctionRepository) (com.lyncode.jtwig.functions.exceptions FunctionNotFoundException FunctionException)) (:require [clj-jtwig.convert :refer [java->clojure clojure->java]]) - (:use [clojure.pprint])) + (:use [clj-jtwig.standard-functions])) + +(defn- make-function-handler [f] + (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] + (let [n (count aliases) + array (make-array String n)] + (doseq [index (range n)] + (aset array index (nth aliases index))) + array)) (defn- create-function-repository [] - (new DefaultFunctionRepository (make-array JtwigFunction 0))) + (let [repository (new DefaultFunctionRepository (make-array JtwigFunction 0))] + ; always add our standard functions to new repository objects + (doseq [[name {:keys [aliases fn]}] standard-functions] + (.add repository + (make-function-handler fn) + name + (make-aliased-array aliases))) + repository)) ; we'll be reusing the same function repository object for all contexts created when rendering templates. ; any custom functions added will be added to this instance (defonce functions (atom (create-function-repository))) (defn reset-functions! - "removes any added custom template function handlers" + "removes any added custom template function handlers. use this with care!" [] (reset! functions (create-function-repository))) @@ -26,13 +48,6 @@ (catch FunctionNotFoundException ex false))) -(defn- make-aliased-array [aliases] - (let [n (count aliases) - array (make-array String n)] - (doseq [index (range n)] - (aset array index (nth aliases index))) - array)) - (defn add-function! "adds a new template function using the name specified. templates can call the function by the name specified (or one of the aliases specified) and passing in the same number of arguments @@ -40,12 +55,7 @@ then nil can be specified for the aliases arg. prefer to use the 'deftwigfn' macro when possible." [name aliases f] - (let [handler (reify JtwigFunction - (execute [_ arguments] - (try - (clojure->java (apply f (map java->clojure arguments))) - (catch Exception ex - (throw (new FunctionException ex))))))] + (let [handler (make-function-handler f)] (.add @functions handler name (make-aliased-array aliases)) (.retrieve @functions name))) @@ -64,83 +74,3 @@ [fn-name args aliases & body] `(do (add-function! ~fn-name ~aliases (fn ~args ~@body)))) - -;; ============================================================================ -;; Standard functions -;; ============================================================================ - -(deftwigfn "blankIfNull" [x] - (if (nil? x) "" x)) - -(deftwigfn "butlast" [sequence] - ; matching behaviour of jtwig's first/last implementation - (if (map? sequence) - (-> sequence vals butlast) - (butlast sequence))) - -(deftwigfn "dump" [x] - (with-out-str (clojure.pprint/pprint x))) - -(deftwigfn "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 (first optional-not-found)) - (nth values index)))) - -(deftwigfn "max" [& numbers] - (if (coll? (first numbers)) - (apply max (first numbers)) - (apply max numbers))) - -(deftwigfn "min" [& numbers] - (if (coll? (first numbers)) - (apply min (first numbers)) - (apply min numbers))) - -(deftwigfn "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)))) - -(deftwigfn "range" [low high & [step]] - (range low high (or step 1))) - -(deftwigfn "rest" [sequence] - ; matching behaviour of jtwig's first/last implementation - (if (map? sequence) - (-> sequence vals rest) - (rest sequence))) - -(deftwigfn "second" [sequence] - ; matching behaviour of jtwig's first/last implementation - (if (map? sequence) - (-> sequence vals second) - (second sequence))) - -(deftwigfn "sort" [sequence] - (sort < sequence)) - -(deftwigfn "sortDescending" [sequence] - (sort > sequence)) - -(deftwigfn "sortBy" [coll k] - (sort-by #(get % k) coll)) - -(deftwigfn "sortDescendingBy" [coll k] - (sort-by #(get % k) #(compare %2 %1) coll)) \ No newline at end of file diff --git a/src/clj_jtwig/standard_functions.clj b/src/clj_jtwig/standard_functions.clj new file mode 100644 index 0000000..1d591d9 --- /dev/null +++ b/src/clj_jtwig/standard_functions.clj @@ -0,0 +1,103 @@ +(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." + (:use [clojure.pprint])) + +; 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 + {"blankIfNull" + {:fn (fn [x] + (if (nil? x) "" x))} + + "butlast" + {:fn (fn [sequence] + ; matching behaviour of jtwig's first/last implementation + (if (map? sequence) + (-> sequence vals butlast) + (butlast sequence)))} + + "dump" + {:fn (fn [x] + (with-out-str + (clojure.pprint/pprint x)))} + + "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))))} + + "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)))} + + "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)))} + + "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))} + + "sortDescending" + {:fn (fn [sequence] + (sort > sequence))} + + "sortBy" + {:fn (fn [coll k] + (sort-by #(get % k) coll))} + + "sortDescendingBy" + {:fn (fn [coll k] + (sort-by #(get % k) #(compare %2 %1) coll))}}) From 559936b839d22547f6ce9b80cac1313a40d111f4 Mon Sep 17 00:00:00 2001 From: gered Date: Tue, 4 Mar 2014 13:59:23 -0500 Subject: [PATCH 10/13] add no-alias arg overload for add-function!. update some doc comments --- src/clj_jtwig/functions.clj | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/clj_jtwig/functions.clj b/src/clj_jtwig/functions.clj index a2ef13f..56562eb 100644 --- a/src/clj_jtwig/functions.clj +++ b/src/clj_jtwig/functions.clj @@ -49,26 +49,26 @@ false))) (defn add-function! - "adds a new template function using the name specified. templates can call the function by the + "adds a new template function under the name specified. templates can call the function by the name specified (or one of the aliases specified) and passing in the same number of arguments - accepted by f. the return value of f is returned to the template. if this function has no aliases - then nil can be specified for the aliases arg. - prefer to use the 'deftwigfn' macro when possible." - [name aliases f] - (let [handler (make-function-handler f)] - (.add @functions handler name (make-aliased-array aliases)) - (.retrieve @functions name))) + accepted by f. the return value of f is returned to the template." + ([name f] + (add-function! name nil f)) + ([name aliases f] + (let [handler (make-function-handler f)] + (.add @functions handler name (make-aliased-array aliases)) + (.retrieve @functions name)))) (defmacro deftwigfn - "adds 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 template. functions defined this way have no aliases and can only be called by the name given." [fn-name args & body] `(do - (add-function! ~fn-name nil (fn ~args ~@body)))) + (add-function! ~fn-name (fn ~args ~@body)))) (defmacro defaliasedtwigfn - "adds 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 the last form in body is returned to the template." [fn-name args aliases & body] From 70bc766fa774dd1be9fc38a4c3f56ad7246b6235 Mon Sep 17 00:00:00 2001 From: gered Date: Tue, 4 Mar 2014 14:00:13 -0500 Subject: [PATCH 11/13] move template function tests into a separate namespace. add std fn tests --- test/clj_jtwig/core_test.clj | 227 +-------------------------- test/clj_jtwig/functions_test.clj | 245 ++++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+), 226 deletions(-) create mode 100644 test/clj_jtwig/functions_test.clj diff --git a/test/clj_jtwig/core_test.clj b/test/clj_jtwig/core_test.clj index 8399e3f..3fe7afb 100644 --- a/test/clj_jtwig/core_test.clj +++ b/test/clj_jtwig/core_test.clj @@ -1,6 +1,5 @@ (ns clj-jtwig.core-test - (:import (java.io FileNotFoundException) - (clojure.lang ArityException)) + (:import (java.io FileNotFoundException)) (:require [clojure.test :refer :all] [clj-jtwig.core :refer :all] [clj-jtwig.functions :refer :all])) @@ -15,14 +14,6 @@ ; Some of the variable passing and return / iteration verification tests might be a bit ; overkill, but better safe than sorry. :) -; TODO: is there a better way to test that something is an instance of some object generated by reify? -(defn valid-function-handler? [x] - (and (not (nil? x)) - (-> x - (class) - (.getName) - (.startsWith "clj_jtwig.functions$add_function")))) - (deftest string-template (testing "Evaluating templates in string vars" (is (= (render "Hello {{ name }}!" @@ -126,219 +117,3 @@ {:name "Bob"} {:skip-model-map-stringify? true})) "passing a model-map where the keys are keywords and try skipping auto stringifying the keys")))) - -(deftest template-functions - (testing "Adding custom template functions" - (do - (reset-functions!) - - (is (valid-function-handler? - (deftwigfn "add" [a b] - (+ a b)))) - - (is (true? (function-exists? "add"))) - (is (false? (function-exists? "foobar"))) - - (is (thrown? - Exception - (deftwigfn "add" [a b] - (+ a b)))) - - (is (= (render "{{add(1, 2)}}" nil) - "3") - "calling a custom function") - (is (= (render "{{add(a, b)}}" {:a 1 :b 2}) - "3") - "calling a custom function, passing in variables from the model-map as arguments") - (is (= (render "{{x|add(1)}}" {:x 1}) - "2") - "calling a custom function using the 'filter' syntax") - - (reset-functions!))) - - (testing "Fixed and variable number of template function arguments" - (do - (reset-functions!) - - (is (valid-function-handler? - (deftwigfn "add2" [a b] - (+ a b)))) - (is (true? (function-exists? "add2"))) - (is (valid-function-handler? - (deftwigfn "addAll" [& numbers] - (apply + numbers)))) - (is (true? (function-exists? "addAll"))) - - (is (= (render "{{add2(1, 2)}}" nil) - "3") - "fixed number of arguments (correct amount)") - (is (thrown? - ArityException - (render "{{add2(1)}}" nil))) - (is (= (render "{{addAll(1, 2, 3, 4, 5)}}" nil) - "15") - "variable number of arguments (non-zero)") - (is (= (render "{{addAll}}" nil) - "null") - "variable number of arguments (zero)") - - (reset-functions!))) - - (testing "Passing different data structures to template functions" - (do - (reset-functions!) - - (is (valid-function-handler? - (deftwigfn "identity" [x] - x))) - (is (true? (function-exists? "identity"))) - (is (valid-function-handler? - (deftwigfn "typename" [x] - (.getName (type x))))) - (is (true? (function-exists? "typename"))) - - ; verify that the clojure function recognizes the correct types when the variable is passed via the model-map - (is (= (render "{{typename(x)}}" {:x 42}) - "java.lang.Long") - "integer typename via model-map") - (is (= (render "{{typename(x)}}" {:x 3.14}) - "java.lang.Double") - "float typename via model-map") - (is (= (render "{{typename(x)}}" {:x "foobar"}) - "java.lang.String") - "string typename via model-map") - (is (= (render "{{typename(x)}}" {:x \a}) - "java.lang.Character") - "char typename via model-map") - (is (= (render "{{typename(x)}}" {:x true}) - "java.lang.Boolean") - "boolean typename via model-map") - (is (= (render "{{typename(x)}}" {:x '(1 2 3 4 5)}) - "clojure.lang.LazySeq") - "list typename via model-map") - (is (= (render "{{typename(x)}}" {:x [1 2 3 4 5]}) - "clojure.lang.LazySeq") - "vector typename via model-map") - (is (= (render "{{typename(x)}}" {:x {:a 1 :b "foo" :c nil}}) - "clojure.lang.PersistentArrayMap") - "map typename via model-map") - (is (= (render "{{typename(x)}}" {:x #{1 2 3 4 5}}) - "clojure.lang.LazySeq") - "set typename via model-map") - - ; verify that the clojure function recognizes the correct types when the variable is passed via a constant - ; value embedded in the template - (is (= (render "{{typename(42)}}" nil) - "java.lang.Integer") - "integer typename via constant value embedded in the template") - (is (= (render "{{typename(3.14)}}" nil) - "java.lang.Double") - "float typename via constant value embedded in the template") - (is (= (render "{{typename('foobar')}}" nil) - "java.lang.String") - "string typename via constant value embedded in the template") - (is (= (render "{{typename('a')}}" nil) - "java.lang.Character") - "char typename via constant value embedded in the template") - (is (= (render "{{typename(true)}}" nil) - "java.lang.Boolean") - "boolean typename via constant value embedded in the template") - (is (= (render "{{typename([1, 2, 3, 4, 5])}}" nil) - "clojure.lang.LazySeq") - "list typename via constant value embedded in the template") - (is (= (render "{{typename(1..5)}}" nil) - "clojure.lang.LazySeq") - "vector typename via constant value embedded in the template") - (is (= (render "{{typename({a: 1, b: 'foo', c: null})}}" nil) - "clojure.lang.PersistentArrayMap") - "map typename via constant value embedded in the template") - - ; simple passing / returning... not doing anything exciting with the arguments - ; using a constant value embedded inside the template - (is (= (render "{{identity(x)}}" {:x 42}) - "42") - "integer via model-map") - (is (= (render "{{identity(x)}}" {:x 3.14}) - "3.14") - "float via model-map") - (is (= (render "{{identity(x)}}" {:x "foobar"}) - "foobar") - "string via model-map") - (is (= (render "{{identity(x)}}" {:x \a}) - "a") - "char via model-map") - (is (= (render "{{identity(x)}}" {:x true}) - "true") - "boolean via model-map") - (is (= (render "{{identity(x)}}" {:x '(1 2 3 4 5)}) - "[1, 2, 3, 4, 5]") - "list via model-map") - (is (= (render "{{identity(x)}}" {:x [1 2 3 4 5]}) - "[1, 2, 3, 4, 5]") - "vector via model-map") - ; TODO: order of iteration through a map is undefined, the string being tested may not always be the same (wrt. order) - (is (= (render "{{identity(x)}}" {:x {:a 1 :b "foo" :c nil}}) - "{b=foo, c=null, a=1}") - "map via model-map") - (is (= (render "{{identity(x)}}" {:x #{1 2 3 4 5}}) - "[1, 2, 3, 4, 5]") - "set via model-map") - - ; simple passing / returning... not doing anything exciting with the arguments - ; using a constant value embedded inside the template - (is (= (render "{{identity(42)}}" nil) - "42") - "integer via constant value embedded in the template") - (is (= (render "{{identity(3.14)}}" nil) - "3.14") - "float via constant value embedded in the template") - (is (= (render "{{identity('foobar')}}" nil) - "foobar") - "string via constant value embedded in the template") - (is (= (render "{{identity('a')}}" nil) - "a") - "char via constant value embedded in the template") - (is (= (render "{{identity(true)}}" nil) - "true") - "boolean via constant value embedded in the template") - (is (= (render "{{identity([1, 2, 3, 4, 5])}}" nil) - "[1, 2, 3, 4, 5]") - "enumerated list via constant value embedded in the template") - (is (= (render "{{identity(1..5)}}" nil) - "[1, 2, 3, 4, 5]") - "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) - (is (= (render "{{identity({a: 1, b: 'foo', c: null})}}" nil) - "{b=foo, c=null, a=1}") - "map via constant value embedded in the template") - - ; iterating over passed sequence/collection type arguments passed to a custom function from a variable - ; inside the model-map and being returned - (is (= (render "{% for i in identity(x) %}{{i}} {% endfor %}" {:x '(1 2 3 4 5)}) - "1 2 3 4 5 ") - "list (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]}) - "1 2 3 4 5 ") - "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) - (is (= (render "{% for k, v in identity(x) %}{{k}}: {{v}} {% endfor %}" {:x {:a 1 :b "foo" :c nil}}) - "b: foo c: null a: 1 ") - "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}}) - "1 2 3 4 5 ") - "set (iterating over a model-map var passed to a function and returned from it)") - - ; iterating over passed sequence/collection type arguments passed to a custom function from a constant - ; value embedded in the template and being returned - (is (= (render "{% for i in identity([1, 2, 3, 4, 5]) %}{{i}} {% endfor %}" nil) - "1 2 3 4 5 ") - "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) - "1 2 3 4 5 ") - "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) - (is (= (render "{% for k, v in identity({a: 1, b: 'foo', c: null}) %}{{k}}: {{v}} {% endfor %}" nil) - "b: foo c: null a: 1 ") - "map (iterating over a model-map var passed to a function and returned from it)") - - (reset-functions!)))) \ No newline at end of file diff --git a/test/clj_jtwig/functions_test.clj b/test/clj_jtwig/functions_test.clj new file mode 100644 index 0000000..0591942 --- /dev/null +++ b/test/clj_jtwig/functions_test.clj @@ -0,0 +1,245 @@ +(ns clj-jtwig.functions-test + (:require [clojure.test :refer :all] + [clj-jtwig.core :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] + (and (not (nil? x)) + (-> x + (class) + (.getName) + (.startsWith "clj_jtwig.functions$make_function_handler")))) + +(deftest template-functions + (testing "Adding custom template functions" + (do + (reset-functions!) + + (is (valid-function-handler? + (deftwigfn "add" [a b] + (+ a b)))) + + (is (true? (function-exists? "add"))) + (is (false? (function-exists? "foobar"))) + + (is (valid-function-handler? + (deftwigfn "add" [a b] + (+ a b)))) + + (is (= (render "{{add(1, 2)}}" nil) + "3") + "calling a custom function") + (is (= (render "{{add(a, b)}}" {:a 1 :b 2}) + "3") + "calling a custom function, passing in variables from the model-map as arguments") + (is (= (render "{{x|add(1)}}" {:x 1}) + "2") + "calling a custom function using the 'filter' syntax") + + (reset-functions!))) + + (testing "Fixed and variable number of template function arguments" + (do + (reset-functions!) + + (is (valid-function-handler? + (deftwigfn "add2" [a b] + (+ a b)))) + (is (true? (function-exists? "add2"))) + (is (valid-function-handler? + (deftwigfn "addAll" [& numbers] + (apply + numbers)))) + (is (true? (function-exists? "addAll"))) + + (is (= (render "{{add2(1, 2)}}" nil) + "3") + "fixed number of arguments (correct amount)") + (is (thrown-with-msg? + Exception + #"clojure\.lang\.ArityException: Wrong number of args" + (render "{{add2(1)}}" nil))) + (is (= (render "{{addAll(1, 2, 3, 4, 5)}}" nil) + "15") + "variable number of arguments (non-zero)") + (is (= (render "{{addAll}}" nil) + "null") + "variable number of arguments (zero)") + + (reset-functions!))) + + (testing "Passing different data structures to template functions" + (do + (reset-functions!) + + (is (valid-function-handler? + (deftwigfn "identity" [x] + x))) + (is (true? (function-exists? "identity"))) + (is (valid-function-handler? + (deftwigfn "typename" [x] + (.getName (type x))))) + (is (true? (function-exists? "typename"))) + + ; verify that the clojure function recognizes the correct types when the variable is passed via the model-map + (is (= (render "{{typename(x)}}" {:x 42}) + "java.lang.Long") + "integer typename via model-map") + (is (= (render "{{typename(x)}}" {:x 3.14}) + "java.lang.Double") + "float typename via model-map") + (is (= (render "{{typename(x)}}" {:x "foobar"}) + "java.lang.String") + "string typename via model-map") + (is (= (render "{{typename(x)}}" {:x \a}) + "java.lang.Character") + "char typename via model-map") + (is (= (render "{{typename(x)}}" {:x true}) + "java.lang.Boolean") + "boolean typename via model-map") + (is (= (render "{{typename(x)}}" {:x '(1 2 3 4 5)}) + "clojure.lang.LazySeq") + "list typename via model-map") + (is (= (render "{{typename(x)}}" {:x [1 2 3 4 5]}) + "clojure.lang.LazySeq") + "vector typename via model-map") + (is (= (render "{{typename(x)}}" {:x {:a 1 :b "foo" :c nil}}) + "clojure.lang.PersistentArrayMap") + "map typename via model-map") + (is (= (render "{{typename(x)}}" {:x #{1 2 3 4 5}}) + "clojure.lang.LazySeq") + "set typename via model-map") + + ; verify that the clojure function recognizes the correct types when the variable is passed via a constant + ; value embedded in the template + (is (= (render "{{typename(42)}}" nil) + "java.lang.Integer") + "integer typename via constant value embedded in the template") + (is (= (render "{{typename(3.14)}}" nil) + "java.lang.Double") + "float typename via constant value embedded in the template") + (is (= (render "{{typename('foobar')}}" nil) + "java.lang.String") + "string typename via constant value embedded in the template") + (is (= (render "{{typename('a')}}" nil) + "java.lang.Character") + "char typename via constant value embedded in the template") + (is (= (render "{{typename(true)}}" nil) + "java.lang.Boolean") + "boolean typename via constant value embedded in the template") + (is (= (render "{{typename([1, 2, 3, 4, 5])}}" nil) + "clojure.lang.LazySeq") + "list typename via constant value embedded in the template") + (is (= (render "{{typename(1..5)}}" nil) + "clojure.lang.LazySeq") + "vector typename via constant value embedded in the template") + (is (= (render "{{typename({a: 1, b: 'foo', c: null})}}" nil) + "clojure.lang.PersistentArrayMap") + "map typename via constant value embedded in the template") + + ; simple passing / returning... not doing anything exciting with the arguments + ; using a constant value embedded inside the template + (is (= (render "{{identity(x)}}" {:x 42}) + "42") + "integer via model-map") + (is (= (render "{{identity(x)}}" {:x 3.14}) + "3.14") + "float via model-map") + (is (= (render "{{identity(x)}}" {:x "foobar"}) + "foobar") + "string via model-map") + (is (= (render "{{identity(x)}}" {:x \a}) + "a") + "char via model-map") + (is (= (render "{{identity(x)}}" {:x true}) + "true") + "boolean via model-map") + (is (= (render "{{identity(x)}}" {:x '(1 2 3 4 5)}) + "[1, 2, 3, 4, 5]") + "list via model-map") + (is (= (render "{{identity(x)}}" {:x [1 2 3 4 5]}) + "[1, 2, 3, 4, 5]") + "vector via model-map") + ; TODO: order of iteration through a map is undefined, the string being tested may not always be the same (wrt. order) + (is (= (render "{{identity(x)}}" {:x {:a 1 :b "foo" :c nil}}) + "{b=foo, c=null, a=1}") + "map via model-map") + (is (= (render "{{identity(x)}}" {:x #{1 2 3 4 5}}) + "[1, 2, 3, 4, 5]") + "set via model-map") + + ; simple passing / returning... not doing anything exciting with the arguments + ; using a constant value embedded inside the template + (is (= (render "{{identity(42)}}" nil) + "42") + "integer via constant value embedded in the template") + (is (= (render "{{identity(3.14)}}" nil) + "3.14") + "float via constant value embedded in the template") + (is (= (render "{{identity('foobar')}}" nil) + "foobar") + "string via constant value embedded in the template") + (is (= (render "{{identity('a')}}" nil) + "a") + "char via constant value embedded in the template") + (is (= (render "{{identity(true)}}" nil) + "true") + "boolean via constant value embedded in the template") + (is (= (render "{{identity([1, 2, 3, 4, 5])}}" nil) + "[1, 2, 3, 4, 5]") + "enumerated list via constant value embedded in the template") + (is (= (render "{{identity(1..5)}}" nil) + "[1, 2, 3, 4, 5]") + "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) + (is (= (render "{{identity({a: 1, b: 'foo', c: null})}}" nil) + "{b=foo, c=null, a=1}") + "map via constant value embedded in the template") + + ; iterating over passed sequence/collection type arguments passed to a custom function from a variable + ; inside the model-map and being returned + (is (= (render "{% for i in identity(x) %}{{i}} {% endfor %}" {:x '(1 2 3 4 5)}) + "1 2 3 4 5 ") + "list (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]}) + "1 2 3 4 5 ") + "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) + (is (= (render "{% for k, v in identity(x) %}{{k}}: {{v}} {% endfor %}" {:x {:a 1 :b "foo" :c nil}}) + "b: foo c: null a: 1 ") + "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}}) + "1 2 3 4 5 ") + "set (iterating over a model-map var passed to a function and returned from it)") + + ; iterating over passed sequence/collection type arguments passed to a custom function from a constant + ; value embedded in the template and being returned + (is (= (render "{% for i in identity([1, 2, 3, 4, 5]) %}{{i}} {% endfor %}" nil) + "1 2 3 4 5 ") + "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) + "1 2 3 4 5 ") + "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) + (is (= (render "{% for k, v in identity({a: 1, b: 'foo', c: null}) %}{{k}}: {{v}} {% endfor %}" nil) + "b: foo c: null a: 1 ") + "map (iterating over a model-map var passed to a function and returned from it)") + + (reset-functions!)))) + +(deftest standard-functions + (testing "Standard functions were added properly" + (is (true? (function-exists? "blankIfNull"))) + (is (true? (function-exists? "butlast"))) + (is (true? (function-exists? "dump"))) + (is (true? (function-exists? "nth"))) + (is (true? (function-exists? "max"))) + (is (true? (function-exists? "min"))) + (is (true? (function-exists? "random"))) + (is (true? (function-exists? "range"))) + (is (true? (function-exists? "rest"))) + (is (true? (function-exists? "second"))) + (is (true? (function-exists? "sort"))) + (is (true? (function-exists? "sortDescending"))) + (is (true? (function-exists? "sortBy"))) + (is (true? (function-exists? "sortDescendingBy"))))) \ No newline at end of file From c3d1f15af4767984e05f76fb1c6585b37e3e8480 Mon Sep 17 00:00:00 2001 From: gered Date: Tue, 4 Mar 2014 14:27:45 -0500 Subject: [PATCH 12/13] add more standard function tests --- test/clj_jtwig/functions_test.clj | 76 ++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/test/clj_jtwig/functions_test.clj b/test/clj_jtwig/functions_test.clj index 0591942..1aa1db8 100644 --- a/test/clj_jtwig/functions_test.clj +++ b/test/clj_jtwig/functions_test.clj @@ -242,4 +242,78 @@ (is (true? (function-exists? "sort"))) (is (true? (function-exists? "sortDescending"))) (is (true? (function-exists? "sortBy"))) - (is (true? (function-exists? "sortDescendingBy"))))) \ No newline at end of file + (is (true? (function-exists? "sortDescendingBy")))) + + (testing "blankIfNull" + (is (= (render "{{ a|blankIfNull }}" nil) + "")) + (is (= (render "{{ a|blankIfNull }}" {:a nil}) + "")) + (is (= (render "{{ a|blankIfNull }}" {:a "foo"}) + "foo"))) + + (testing "butlast" + (is (= (render "{{ [1, 2, 3, 4, 5]|butlast }}" nil) + "[1, 2, 3, 4]"))) + + (testing "dump" + (is (= (render "{{ a|dump }}" {:a [{:foo "bar"} [1, 2, 3] "hello"]}) + "({\"foo\" \"bar\"} (1 2 3) \"hello\")\n"))) + + (testing "nth" + (is (= (render "{{ [1, 2, 3, 4, 5]|nth(2) }}" nil) + "3")) + (is (thrown-with-msg? + Exception + #"java.lang.IndexOutOfBoundsException" + (render "{{ [1, 2, 3, 4, 5]|nth(6) }}" nil))) + (is (= (render "{{ [1, 2, 3, 4, 5]|nth(6, \"not found\") }}" nil) + "not found"))) + + (testing "max" + (is (= (render "{{ [2, 1, 5, 3, 4]|max }}" nil) + "5")) + (is (= (render "{{ max(2, 1, 5, 3, 4) }}" nil) + "5"))) + + (testing "min" + (is (= (render "{{ [2, 1, 5, 3, 4]|min }}" nil) + "1")) + (is (= (render "{{ min(2, 1, 5, 3, 4) }}" nil) + "1"))) + + (testing "random" + (is (some #{(render "{{ ['apple', 'orange', 'citrus']|random }}" nil)} + ["apple" "orange" "citrus"])) + (is (some #{(render "{{ \"ABC\"|random }}" nil)} + ["A" "B" "C"]))) + + (testing "range" + (is (= (render "{{ range(1, 5) }}" nil) + "[1, 2, 3, 4]")) + (is (= (render "{{ range(1, 5, 2) }}" nil) + "[1, 3]"))) + + (testing "rest" + (is (= (render "{{ [1, 2, 3, 4, 5]|rest }}" nil) + "[2, 3, 4, 5]"))) + + (testing "second" + (is (= (render "{{ [1, 2, 3, 4, 5]|second }}" nil) + "2"))) + + (testing "sort" + (is (= (render "{{ [2, 1, 5, 3, 4]|sort }}" nil) + "[1, 2, 3, 4, 5]"))) + + (testing "sortDescending" + (is (= (render "{{ [2, 1, 5, 3, 4]|sortDescending }}" nil) + "[5, 4, 3, 2, 1]"))) + + (testing "sortBy" + (is (= (render "{{ [{a: 2}, {a: 1}, {a: 5}, {a: 3}, {a: 4}]|sortBy(\"a\") }}" nil) + "[{a=1}, {a=2}, {a=3}, {a=4}, {a=5}]"))) + + (testing "sortDescendingBy" + (is (= (render "{{ [{a: 2}, {a: 1}, {a: 5}, {a: 3}, {a: 4}]|sortDescendingBy(\"a\") }}" nil) + "[{a=5}, {a=4}, {a=3}, {a=2}, {a=1}]")))) From 1595d540417b6597ccd53a867428494c3511cb5c Mon Sep 17 00:00:00 2001 From: gered Date: Tue, 4 Mar 2014 14:28:39 -0500 Subject: [PATCH 13/13] update version --- project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.clj b/project.clj index 9173030..43514b6 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject clj-jtwig "0.1.0-SNAPSHOT" +(defproject clj-jtwig "0.2" :description "Clojure wrapper for JTwig" :url "https://github.com/gered/clj-jtwig" :license {:name "Apache License, Version 2.0"