initial import

This commit is contained in:
Mariano Guerra 2013-01-08 16:55:55 +01:00
commit 31a1807ec7
5 changed files with 269 additions and 0 deletions

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
/target
/lib
/classes
/checkouts
pom.xml
*.jar
*.class
.lein-deps-sum
.lein-failures
.lein-plugins

68
README.rest Normal file
View file

@ -0,0 +1,68 @@
clj-rhino
=========
a nice wrapper to handle rhino from clojure
who?
----
marianoguerra
why?
----
the java api for rhino is not really nice
how?
----
you can see the tests for some usage, here are some REPL examples:
user=> (require '[clj-rhino :as js])
nil
user=> (def sc (js/new-safe-scope))
#'user/sc
user=> (js/eval sc "1 + 1")
2
user=> (js/eval sc "a = 1 + 1")
2
user=> (js/get sc "a")
2
user=> (js/get sc "b")
#<UniqueTag org.mozilla.javascript.UniqueTag@172897f: NOT_FOUND>
user=> (js/undefined? (js/get sc "b"))
true
user=> (js/get sc "b" :w00t?)
:w00t?
user=> (js/defined? (js/get sc "b"))
false
user=> (js/set! sc "b" 42)
nil
user=> (js/defined? (js/get sc "b"))
true
user=> (js/get sc "b" :w00t?)
42
user=> (js/eval sc "a = {name: 'spongebob'}")
#<NativeObject [object Object]>
user=> (js/get-in sc [:a :name])
"spongebob"
user=> (js/get-in sc [:a :age])
#<UniqueTag org.mozilla.javascript.UniqueTag@172897f: NOT_FOUND>
user=> (js/get-in sc [:a :age] :dont-know)
:dont-know
user=> (def compiled-fun (js/compile-function sc "function (a, b) { return a + b; }" :filename "foo.js"))
#'user/compiled-fun
user=> (js/set! sc "add" compiled-fun)
nil
user=> (js/eval sc "add(1, 3)")
4.0
user=>
license?
--------
it seems the clojure people under this circumstances say something like:
Copyright © 2013 marianoguerra
Distributed under the Eclipse Public License, the same as Clojure.

7
project.clj Normal file
View file

@ -0,0 +1,7 @@
(defproject clj-rhino "0.1.0-SNAPSHOT"
:description "library to ease the interaction between rhino and clojure"
:url "http://github.com/marianoguerra/clj-rhino"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.4.0"]
[org.mozilla/rhino "1.7R4"]])

117
src/clj_rhino.clj Normal file
View file

@ -0,0 +1,117 @@
(ns clj-rhino
(:refer-clojure :exclude (eval get get-in set!))
(:import [org.mozilla.javascript Context UniqueTag]))
(def insecure-vars ["isXMLName" "uneval" "InternalError" "JavaException"
"With" "Call" "Script" "Iterator" "StopIteration",
"Packages" "java" "javax" "org" "com" "edu" "net"
"getClass" "JavaAdapter" "JavaImporter" "Continuation"
"XML" "XMLList" "Namespace" "QName"])
(defn with-context [fun]
"create a context call fun with it and safelly exit the context"
(let [ctx (Context/enter)]
(try
(fun ctx)
(finally (Context/exit)))))
(defn with-context-if-nil [ctx fun]
"create a context if ctx is nil, otherwise use ctx and call fun with it,
exit safelly after if ctx was created here, otherwise is up to the caller
(which should be inside a with-context somewhere up the call stack)"
(if ctx
(fun ctx)
(with-context fun)))
(defn eval [scope code & {:keys [ctx filename line-number sec-domain]}]
(with-context-if-nil ctx (fn [ctx1]
(.evaluateString ctx1 scope code
(or filename "<eval>")
(or line-number 1) sec-domain))))
(defn undefined? [value]
"return true if value is undefined"
(= value (. UniqueTag NOT_FOUND)))
(def defined? (comp not undefined?))
(defn set! [scope name value]
"bind an object to a name in scope"
(.put scope name scope value))
(defn get
"return the object referenced by var-name in scope,
UniqueTag.NOT_FOUND if not found or not-found if supplied"
([scope var-name]
; TODO: return undefined when scope doesn't have .get
(.get scope (name var-name) scope))
([scope var-name not-found]
(let [result (.get scope (name var-name) scope)]
(if (undefined? result)
not-found
result))))
(defn get-in
"Returns the value in a nested scope,
where ks is a sequence of keys. Returns nil if the key is not present,
or the not-found value if supplied."
([scope ks]
(reduce get scope ks))
([scope ks not-found]
(loop [sentinel (Object.)
scope scope
ks (seq ks)]
(if ks
(let [scope (get scope (first ks) sentinel)]
(if (identical? sentinel scope)
not-found
(recur sentinel scope (next ks))))
scope))))
(defn new-root-scope [& [ctx sealed vars-to-remove]]
"create a new root js scope and return it
make it sealed if sealed is true
remove vars-to-remove if non nil (a seq of strings)"
(with-context-if-nil ctx (fn [ctx]
(let [scope (.initStandardObjects ctx nil true)]
; force loading RegExp
(eval scope "RegExp;" :ctx ctx)
(dorun (map #(.delete scope %) (or vars-to-remove [])))
(when sealed
(.sealObject scope))
scope))))
(defn new-safe-root-scope [& [ctx]]
"create a new root js scope removing dangerous references and sealing it"
(new-root-scope ctx true insecure-vars))
(defn new-scope [& [ctx parent-scope vars-to-remove]]
"create a new scope with parent-scope as parent, if parent-scope is nil
create it"
(with-context-if-nil ctx (fn [ctx]
(let [parent-scope (or parent-scope
(new-root-scope ctx true
vars-to-remove))
scope (.newObject ctx parent-scope)]
(doto scope
(.setPrototype parent-scope)
(.setParentScope nil))
scope))))
(defn new-safe-scope [& [ctx]]
"create a new scope using a safe root scope as parent"
(new-scope ctx (new-safe-root-scope ctx)))
(defn compile-function [scope code & {:keys [ctx filename line-number sec-domain]}]
"compile and return function defined in code"
(with-context-if-nil ctx (fn [ctx]
(.compileFunction ctx scope code
(or filename "<eval>")
(or line-number 1) sec-domain))))

View file

@ -0,0 +1,67 @@
(ns clj-rhino.core-test
(:import [org.mozilla.javascript Context UniqueTag EvaluatorException])
(:use clojure.test)
(:require [clj-rhino :as js]))
(defn- assert-var-undefined [scope]
(fn [name]
(js/eval scope (str "a = typeof " name))
(is (= (js/get scope "a") "undefined"))))
(deftest js-test
(testing "undefined? works"
(is (not (js/undefined? 1)))
(is (js/undefined? (. UniqueTag NOT_FOUND))))
(testing "get returns undefined if get'ing inexistent var"
(let [scope (js/new-scope)]
(is (js/undefined? (js/get scope "foo")))))
(testing "get returns default if get'ing inexistent var and default provided"
(let [scope (js/new-scope)]
(is (= (js/get scope "foo" 4) 4))))
(testing "safe scope doesn't have dangerous references"
(let [scope (js/new-safe-scope)
unsafe-scope (js/new-scope)]
(js/eval scope "a = typeof With")
(js/eval unsafe-scope "a = typeof With")
(is (= (js/get scope "a") "undefined"))
(is (= (js/get unsafe-scope "a") "function"))
(dorun (map (assert-var-undefined scope) js/insecure-vars))))
(testing "set function sets a variable in the scope"
(let [scope (js/new-safe-scope)]
(js/set! scope "a" 2)
(is (= (js/get scope "a") 2))))
(testing "functions can be compiled"
(let [scope (js/new-safe-scope)
code "function (a, b) { return a + b; }"
compiled-fun (js/compile-function scope code :filename "foo.js")]
(js/set! scope "add" compiled-fun)
(is (= (js/eval scope "add(1, 2)") 3.0))))
(testing "root scope doesn't allow var modifications"
(let [scope (js/new-safe-root-scope)]
(is (thrown? EvaluatorException (js/eval scope "Object = null")))))
(testing "code can be evaled"
(let [scope (js/new-scope)]
(js/eval scope "a = 1;")
(is (= (js/get scope "a") 1.0))
(js/eval scope "s = 'hello';")
(is (= (js/get scope "s") "hello"))
(js/eval scope "o = {name: 'mariano'};")
(is (= (js/get (js/get scope "o") "name") "mariano"))
(is (= (js/get-in scope [:o :name]) "mariano"))
(js/eval scope "b = false")
; TODO: this fails
; (is (= (js/get-in scope [:b :name] :not-found) :not-found))
(is (= (js/get scope "b") false))))
)