diff --git a/README.rest b/README.rest index 5db1d7e..17fb43a 100644 --- a/README.rest +++ b/README.rest @@ -81,6 +81,23 @@ handy convertions:: user=> (js/from-js (js/eval sc "o = {name: 'spongebob', friends: ['patrick', 'sandy']};")) {:friends ["patrick" "sandy"], :name "spongebob"} +exposing clojure functions to rhino:: + + user=> (require '[clj-rhino :as js]) + nil + + user=> (defn add [ctx scope this [a b]] (+ a b)) + #'user/add + + user=> (def scope (js/new-safe-scope)) + #'user/scope + + user=> (js/set! scope "add" (js/make-fn add)) + nil + + user=> (js/eval scope "add(1, 2)") + 3.0 + license? -------- diff --git a/src/clj_rhino.clj b/src/clj_rhino.clj index f2efa5c..8be255d 100644 --- a/src/clj_rhino.clj +++ b/src/clj_rhino.clj @@ -1,6 +1,7 @@ (ns clj-rhino (:refer-clojure :exclude (eval get get-in set!)) - (:import [org.mozilla.javascript Context UniqueTag NativeArray NativeObject])) + (:import [org.mozilla.javascript Context UniqueTag NativeArray NativeObject + BaseFunction])) (defprotocol RhinoConvertible (-to-rhino [object scope ctx] "convert a value to a rhino compatible type")) @@ -198,3 +199,13 @@ (.compileFunction ctx scope code (or filename "") (or line-number 1) sec-domain)))) + +(defn make-fn [fun] + "return an object that can be used as a function in rhino, + fun must receive the following arguments [ctx scope this args] + args will be passed through from-js before calling fun and the result + through to-js" + + (proxy [BaseFunction] [] + (call [ctx scope this args] + (to-js (fun ctx scope this (from-js args)) scope ctx)))) diff --git a/test/clj_rhino/core_test.clj b/test/clj_rhino/core_test.clj index ffeb3bb..f05e288 100644 --- a/test/clj_rhino/core_test.clj +++ b/test/clj_rhino/core_test.clj @@ -80,46 +80,49 @@ (is (= (js/get scope "b") false)))) (testing "to-js works" - (js/with-context (fn [ctx] - (let [scope (js/new-safe-scope)] + (js/with-context + (fn [ctx] + (let [scope (js/new-safe-scope)] - (is (= (js/to-js nil scope ctx) nil)) - (is (= (js/to-js 1 scope ctx) 1)) - (is (= (js/to-js 1/2 scope ctx) 0.5)) - (is (= (js/to-js true scope ctx) true)) - (is (= (js/to-js "foo" scope ctx) "foo")) - (is (= (js/to-js :foo scope ctx) "foo")) + (is (= (js/to-js nil scope ctx) nil)) + (is (= (js/to-js 1 scope ctx) 1)) + (is (= (js/to-js 1/2 scope ctx) 0.5)) + (is (= (js/to-js true scope ctx) true)) + (is (= (js/to-js "foo" scope ctx) "foo")) + (is (= (js/to-js :foo scope ctx) "foo")) - (check-array-to-js [] scope ctx) - (check-array-to-js [nil 1 1/2 true "foo" :foo] scope ctx) - (check-array-to-js '(nil 1 1/2 true "foo" :foo) scope ctx) + (check-array-to-js [] scope ctx) + (check-array-to-js [nil 1 1/2 true "foo" :foo] scope ctx) + (check-array-to-js '(nil 1 1/2 true "foo" :foo) scope ctx) - ; check to-js-generic - (check-array-to-js (to-array [nil 1 1/2 true "foo" :foo]) scope ctx) + ; check to-js-generic + (check-array-to-js (to-array [nil 1 1/2 true "foo" :foo]) scope ctx) - (is (= (get (js/to-js {:name "mariano"} scope ctx) "name") "mariano")) - (is (= (get (js/to-js {:name "mariano" :age 27} scope ctx) "age") 27)))))) + (is (= (get (js/to-js {:name "mariano"} scope ctx) "name") "mariano")) + (is (= (get (js/to-js {:name "mariano" :age 27} scope ctx) "age") 27)))))) (testing "to-js works on complex nested structures" - (js/with-context (fn [ctx] - (let [scope (js/new-safe-scope) - obj {:name "bob" - :age 27 - :tags ["yellow" :sponge ["a" :b 3.2]] - :friends {:sandy "squirrel" - :patrick "star"}} - js-obj (js/to-js obj scope ctx)] + (js/with-context + (fn [ctx] + (let [scope (js/new-safe-scope) + obj {:name "bob" + :age 27 + :tags ["yellow" :sponge ["a" :b 3.2]] + :friends {:sandy "squirrel" + :patrick "star"}} + js-obj (js/to-js obj scope ctx)] - (is (= (get js-obj "name") "bob")) - (is (= (get js-obj "age") 27)) - (is (= (.get (get js-obj "tags") 1) "sponge")) - (is (= (.get (.get (get js-obj "tags") 2) 1) "b")) - (is (= (get-in js-obj ["friends" "sandy"]) "squirrel")))))) + (is (= (get js-obj "name") "bob")) + (is (= (get js-obj "age") 27)) + (is (= (.get (get js-obj "tags") 1) "sponge")) + (is (= (.get (.get (get js-obj "tags") 2) 1) "b")) + (is (= (get-in js-obj ["friends" "sandy"]) "squirrel")))))) (testing "unknown types throw exception when converting to-js" - (js/with-context (fn [ctx] - (let [scope (js/new-safe-scope)] - (is (thrown? Exception (js/to-js (atom {}) scope ctx))))))) + (js/with-context + (fn [ctx] + (let [scope (js/new-safe-scope)] + (is (thrown? Exception (js/to-js (atom {}) scope ctx))))))) (testing "from-js works" (is-identity nil) @@ -154,5 +157,13 @@ (assert-simetric-convertion {:b {"c" {:d [{"e" 4}]}}} scope ctx {:b {:c {:d [{:e 4}]}}}) )))) - ) + + (testing "native clojure functions can be added" + (js/with-context + (fn [ctx] + (let [scope (js/new-safe-scope)] + (js/set! scope "add" (js/make-fn (fn [ctx scope this [a b]] (+ a b)))) + (is (= (js/eval scope "add(1, 2)") 3.0)))))) + +)