zweikopf 0.0.6 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +82 -1
- data/lib/zweikopf/version.rb +1 -1
- data/project.clj +2 -2
- data/spec/zweikopf/performance_spec.rb +2 -1
- data/src/zweikopf/core.clj +44 -32
- data/src/zweikopf/multi.clj +145 -0
- data/test/zweikopf/core_test.clj +77 -10
- metadata +3 -2
data/README.md
CHANGED
@@ -17,8 +17,15 @@ Or install it yourself as:
|
|
17
17
|
|
18
18
|
$ gem install zweikopf
|
19
19
|
|
20
|
+
For Clojure-driven projects, add this line to your `project.clj`:
|
21
|
+
|
22
|
+
```clojure
|
23
|
+
[zweikopf "0.4.0"]
|
24
|
+
```
|
20
25
|
## Usage
|
21
26
|
|
27
|
+
### From Ruby code: Clojure->Ruby transformations
|
28
|
+
|
22
29
|
Transfroming from Clojure entities to Ruby ones is as easy as:
|
23
30
|
|
24
31
|
```ruby
|
@@ -35,6 +42,8 @@ Zweikopf::Transformer.from_clj(clojure_var)
|
|
35
42
|
# => {:a => 1, :b => {:c => [{:d => 2}, {:e => 3}, { :f => 4}]}}
|
36
43
|
```
|
37
44
|
|
45
|
+
### From Ruby code: Ruby->Clojure transformations
|
46
|
+
|
38
47
|
And backwards:
|
39
48
|
|
40
49
|
```ruby
|
@@ -51,6 +60,8 @@ Zweikopf::Transformer.from_ruby(ruby_var)
|
|
51
60
|
# => {:a 1 :b {:c [{:d 2} {:e 3} {:f 4}]}}
|
52
61
|
```
|
53
62
|
|
63
|
+
### Custom conversion
|
64
|
+
|
54
65
|
When performing Ruby to Clojure transformation, you may leave out some space for customization:
|
55
66
|
|
56
67
|
```ruby
|
@@ -70,8 +81,74 @@ Zweikopf::Transformer.from_ruby({:a => 1, :b => CustomTransformedEntry.new }) do
|
|
70
81
|
# => {:a 1 :b {:c 3 :d 4}}
|
71
82
|
```
|
72
83
|
|
84
|
+
### From Clojure code:
|
85
|
+
|
86
|
+
With Clojure version everything is extremely simple:
|
87
|
+
|
88
|
+
```clojure
|
89
|
+
(:require 'zweikopf.core)
|
90
|
+
|
91
|
+
;; You _must_ call it, otherwise Ruby Runtime won't get initialized.
|
92
|
+
(init-ruby-context)
|
93
|
+
|
94
|
+
;; To convert Clojure DS to Ruby, run:
|
95
|
+
(rubyize {:a 1 :b 2})
|
96
|
+
|
97
|
+
;; To convert Ruby DS to Clojure, run
|
98
|
+
(clojurize ruby-obj)
|
99
|
+
|
100
|
+
;; If you want to execute arbitrary Ruby code, use ruby-eval:
|
101
|
+
(ruby-eval "puts 'Hello World'") ;; Or any other portion of Ruby code you'd like to execute
|
102
|
+
|
103
|
+
;; In order to require a file:
|
104
|
+
(ruby-require "filename")
|
105
|
+
|
106
|
+
;; In order to load:
|
107
|
+
(ruby-load "filename")
|
108
|
+
|
109
|
+
;; Call a method on a Ruby object:
|
110
|
+
;; This will call `#to_hash` method on `ruby-obj`
|
111
|
+
(call-ruby ruby-obj :to_hash)
|
112
|
+
|
113
|
+
;; To set gem-path:
|
114
|
+
(set-gem-path "my-gem-path")
|
115
|
+
|
116
|
+
;; To set gem-home:
|
117
|
+
(set-gem-path "my-gem-home")
|
118
|
+
|
119
|
+
;; To add custom convertor from Ruby to Clojure, extend protocol Clojurize
|
120
|
+
;; For example, convertion of RubyTime class to java Date
|
121
|
+
(extend-protocol Clojurize
|
122
|
+
org.jruby.RubyTime
|
123
|
+
(clojurize [this]
|
124
|
+
(.toJava this java.util.Date)))
|
125
|
+
|
126
|
+
;; To add custom convertor from Clojure to Ruby, extend protocol Rubyize
|
127
|
+
;; For example, convertion of Clojure Keyword class to Ruby Symbol
|
128
|
+
(extend-protocol Rubyize
|
129
|
+
clojure.lang.Keyword
|
130
|
+
(rubyize [this]
|
131
|
+
(.fastNewSymbol ruby-runtime (name this))))
|
132
|
+
```
|
133
|
+
|
134
|
+
# Pitfalls
|
135
|
+
|
136
|
+
When using Rails and DateTime conversion, you should call `DateTime#utc` before you can call `#to_time`.
|
137
|
+
|
138
|
+
It's very easy to package all your gems in a Jar, if you decide to do so, you need to either use
|
139
|
+
the files that were extracted by the runtime (which is by itself quite tricky, and you may run into
|
140
|
+
some issues with Bundler, if you use it), alternative is to materialize (extract) your gems from
|
141
|
+
jar manually. We're not yet ready to open our sorce for jar extraction, but you can write up your
|
142
|
+
own quite quickly, using `FileReader`, `JarFile` and `JarInputStream` files.
|
143
|
+
|
144
|
+
Other than that, JRuby/Clojure integration is very smooth and painless.
|
145
|
+
|
73
146
|
# Performance
|
74
147
|
|
148
|
+
We highly recommend using target language convertor. If you pass rather small data structures to Ruby scripts,
|
149
|
+
and return large portions back, use Clojure version. If you pass smaller amounts of data to Clojure code,
|
150
|
+
and it returns larger cunks, use Ruby version of transformer.
|
151
|
+
|
75
152
|
## Conversion from Ruby hash to Clojure PersistentHash Map
|
76
153
|
|
77
154
|
Most of time 52% according to the rough estimate is spent while converting from ruby Symbol to clojure Keyword.
|
@@ -86,6 +163,10 @@ Most of time 52% according to the rough estimate is spent while converting from
|
|
86
163
|
|
87
164
|
# Copyright
|
88
165
|
|
89
|
-
Copyright (C)
|
166
|
+
Copyright (C) 2012-2013 Alex Petrov and [contributors](https://github.com/ifesdjeen/zweikopf/graphs/contributors).
|
90
167
|
|
91
168
|
Distributed under the Eclipse Public License, the same as Clojure.
|
169
|
+
|
170
|
+
|
171
|
+
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/ifesdjeen/zweikopf/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
|
172
|
+
|
data/lib/zweikopf/version.rb
CHANGED
data/project.clj
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
(defproject zweikopf "
|
1
|
+
(defproject zweikopf "1.0.0-SNAPSHOT"
|
2
2
|
:description "jruby clojure interop"
|
3
3
|
:dependencies [[org.clojure/clojure "1.4.0"]
|
4
|
-
[org.jruby/jruby-complete "1.
|
4
|
+
[org.jruby/jruby-complete "1.7.1"]]
|
5
5
|
:profiles {:dev {:dependencies []}})
|
@@ -14,8 +14,9 @@ describe :performance do
|
|
14
14
|
|
15
15
|
it "this spec simply shows performance, very roughly, please do your testing depending on your needs" do
|
16
16
|
time_before = Time.now.to_f
|
17
|
+
ruby_obj = {:a => 1, :b => 2}
|
17
18
|
100000.times do |i|
|
18
|
-
Zweikopf::Transformer.from_ruby(
|
19
|
+
Zweikopf::Transformer.from_ruby(ruby_obj)
|
19
20
|
end
|
20
21
|
puts "Ruby to Clojure, time elapsed: #{Time.now.to_f - time_before} seconds"
|
21
22
|
|
data/src/zweikopf/core.clj
CHANGED
@@ -1,33 +1,45 @@
|
|
1
1
|
(ns zweikopf.core
|
2
|
-
(:require [
|
3
|
-
(:import [org.jruby
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
(
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
(
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
2
|
+
(:require [zweikopf.multi :as multi])
|
3
|
+
(:import [org.jruby.embed ScriptingContainer LocalContextScope]
|
4
|
+
[org.jruby Ruby]))
|
5
|
+
|
6
|
+
(declare ^ScriptingContainer ruby)
|
7
|
+
(declare ^Ruby ruby-runtime)
|
8
|
+
|
9
|
+
(defn clojurize
|
10
|
+
[this]
|
11
|
+
(multi/clojurize this ruby))
|
12
|
+
|
13
|
+
(defn rubyize
|
14
|
+
[this]
|
15
|
+
(multi/rubyize this ruby))
|
16
|
+
|
17
|
+
(defn ruby-eval
|
18
|
+
[script]
|
19
|
+
(multi/ruby-eval ruby script))
|
20
|
+
|
21
|
+
(defn ruby-require
|
22
|
+
[lib]
|
23
|
+
(ruby-eval (format "require '%s'" lib)))
|
24
|
+
|
25
|
+
(defn ruby-load
|
26
|
+
[lib]
|
27
|
+
(ruby-eval (format "load '%s'" lib)))
|
28
|
+
|
29
|
+
(defn call-ruby
|
30
|
+
[& args]
|
31
|
+
(apply multi/call-ruby ruby args))
|
32
|
+
|
33
|
+
(defn set-gem-path
|
34
|
+
"Sets GEM_PATH Environment variable"
|
35
|
+
[gem-path]
|
36
|
+
(ruby-eval (str "ENV['GEM_PATH']='" gem-path "'")))
|
37
|
+
|
38
|
+
(defn set-gem-home
|
39
|
+
[gem-home]
|
40
|
+
(ruby-eval (str "ENV['GEM_HOME']='" gem-home "'")))
|
41
|
+
|
42
|
+
(defn init-ruby-context
|
43
|
+
[]
|
44
|
+
(defonce ruby (ScriptingContainer. LocalContextScope/SINGLETON))
|
45
|
+
(defonce ruby-runtime (.getRuntime (.getProvider ruby))))
|
@@ -0,0 +1,145 @@
|
|
1
|
+
(ns zweikopf.multi
|
2
|
+
(:require [clojure.string :as str])
|
3
|
+
(:import (org.jruby.embed ScriptingContainer
|
4
|
+
LocalContextScope)
|
5
|
+
(org.jruby Ruby
|
6
|
+
RubyArray
|
7
|
+
RubyBasicObject
|
8
|
+
RubyHash
|
9
|
+
RubyHash$RubyHashEntry
|
10
|
+
RubyObject
|
11
|
+
RubyRational
|
12
|
+
RubyString
|
13
|
+
RubySymbol)))
|
14
|
+
|
15
|
+
(defprotocol Clojurize
|
16
|
+
(clojurize [this ^ScriptingContainer ruby]))
|
17
|
+
|
18
|
+
(defprotocol Rubyize
|
19
|
+
(rubyize [this ^ScriptingContainer ruby]))
|
20
|
+
|
21
|
+
(defn- ^Ruby runtime
|
22
|
+
[^ScriptingContainer container]
|
23
|
+
(-> container .getProvider .getRuntime))
|
24
|
+
|
25
|
+
(defn ruby-eval
|
26
|
+
[^ScriptingContainer ruby script]
|
27
|
+
(.runScriptlet ruby script))
|
28
|
+
|
29
|
+
(defn call-ruby
|
30
|
+
[^ScriptingContainer container klass method & args]
|
31
|
+
(let [method-name (name method)
|
32
|
+
klass (if (string? klass)
|
33
|
+
(ruby-eval container (str/replace klass "/" "::"))
|
34
|
+
klass)]
|
35
|
+
(if (empty? args)
|
36
|
+
(.callMethod container klass method-name Object)
|
37
|
+
(.callMethod container klass method-name (object-array args) Object))))
|
38
|
+
|
39
|
+
(defn context
|
40
|
+
[]
|
41
|
+
(ScriptingContainer. LocalContextScope/SINGLETHREAD))
|
42
|
+
|
43
|
+
(defn terminate
|
44
|
+
[^ScriptingContainer ctx]
|
45
|
+
(.terminate ctx))
|
46
|
+
|
47
|
+
(extend-protocol Clojurize
|
48
|
+
nil
|
49
|
+
(clojurize [this _]
|
50
|
+
nil)
|
51
|
+
|
52
|
+
RubySymbol
|
53
|
+
(clojurize [this _]
|
54
|
+
(clojure.lang.Keyword/intern (.toString this)))
|
55
|
+
|
56
|
+
RubyHash
|
57
|
+
(clojurize [this ruby]
|
58
|
+
(persistent!
|
59
|
+
(reduce (fn [acc ^RubyHash$RubyHashEntry entry]
|
60
|
+
(assoc! acc
|
61
|
+
(clojurize (.getKey entry) ruby)
|
62
|
+
(clojurize (.getValue entry) ruby)))
|
63
|
+
(transient {})
|
64
|
+
(.directEntrySet this))))
|
65
|
+
|
66
|
+
RubyArray
|
67
|
+
(clojurize [this ruby]
|
68
|
+
(mapv #(clojurize % ruby) this))
|
69
|
+
|
70
|
+
RubyString
|
71
|
+
(clojurize [this _]
|
72
|
+
(.decodeString this))
|
73
|
+
|
74
|
+
org.jruby.RubyNil
|
75
|
+
(clojurize [_ _]
|
76
|
+
nil)
|
77
|
+
|
78
|
+
org.jruby.RubyRational
|
79
|
+
(clojurize [this ruby]
|
80
|
+
(let [context (.getCurrentContext (runtime ruby))
|
81
|
+
numerator (clojurize (.numerator this context) ruby)
|
82
|
+
denominator (clojurize (.denominator this context) ruby)]
|
83
|
+
(/ numerator denominator)))
|
84
|
+
|
85
|
+
org.jruby.RubyFixnum
|
86
|
+
(clojurize [this _]
|
87
|
+
(.getLongValue this))
|
88
|
+
|
89
|
+
org.jruby.RubyFloat
|
90
|
+
(clojurize [this _]
|
91
|
+
(.getDoubleValue this))
|
92
|
+
|
93
|
+
org.jruby.RubyBoolean
|
94
|
+
(clojurize [this _]
|
95
|
+
(.isTrue this))
|
96
|
+
|
97
|
+
org.jruby.RubyTime
|
98
|
+
(clojurize [this _]
|
99
|
+
(.toJava this java.util.Date))
|
100
|
+
|
101
|
+
org.jruby.RubyObject
|
102
|
+
(clojurize [this ruby]
|
103
|
+
(condp #(call-ruby ruby %2 :respond_to? %1) this
|
104
|
+
"to_hash" (clojurize (call-ruby ruby this :to_hash) ruby)
|
105
|
+
"strftime" (-> (call-ruby ruby this :strftime "%s")
|
106
|
+
Long/parseLong
|
107
|
+
(* 1000)
|
108
|
+
java.util.Date.)))
|
109
|
+
|
110
|
+
java.lang.Object
|
111
|
+
(clojurize [this _]
|
112
|
+
this))
|
113
|
+
|
114
|
+
(defn- apply-to-keys-and-values [m f]
|
115
|
+
(into {} (for [[k v] m]
|
116
|
+
[(f k) (f v)])))
|
117
|
+
|
118
|
+
(extend-protocol Rubyize
|
119
|
+
clojure.lang.IPersistentMap
|
120
|
+
(rubyize [this ruby]
|
121
|
+
(doto (RubyHash. (runtime ruby))
|
122
|
+
(.putAll (apply-to-keys-and-values this #(rubyize % ruby)))))
|
123
|
+
|
124
|
+
clojure.lang.Ratio
|
125
|
+
(rubyize [this ruby]
|
126
|
+
(RubyRational/newRational (runtime ruby)
|
127
|
+
(.numerator this)
|
128
|
+
(.denominator this)))
|
129
|
+
|
130
|
+
clojure.lang.Seqable
|
131
|
+
(rubyize [this ruby]
|
132
|
+
(doto (RubyArray/newArray (runtime ruby))
|
133
|
+
(.addAll (for [item this] (rubyize item ruby)))))
|
134
|
+
|
135
|
+
clojure.lang.Keyword
|
136
|
+
(rubyize [this ruby]
|
137
|
+
(.fastNewSymbol (runtime ruby) (name this)))
|
138
|
+
|
139
|
+
java.lang.Object
|
140
|
+
(rubyize [this _]
|
141
|
+
this)
|
142
|
+
|
143
|
+
nil
|
144
|
+
(rubyize [_ _]
|
145
|
+
nil))
|
data/test/zweikopf/core_test.clj
CHANGED
@@ -3,21 +3,88 @@
|
|
3
3
|
(:use clojure.test
|
4
4
|
zweikopf.core))
|
5
5
|
|
6
|
-
(
|
7
|
-
|
6
|
+
(use-fixtures :once (fn [f]
|
7
|
+
(init-ruby-context)
|
8
|
+
(f)))
|
8
9
|
|
9
|
-
|
10
|
+
|
11
|
+
(deftest clojurize-test
|
12
|
+
(ruby-eval "require 'date'")
|
13
|
+
(testing "Numbers"
|
14
|
+
(testing "Integer"
|
15
|
+
(is (= 123 (clojurize (ruby-eval "123")))))
|
16
|
+
(testing "Floating point"
|
17
|
+
(is (= 123.45 (clojurize (ruby-eval "123.45")))))
|
18
|
+
(testing "Rational"
|
19
|
+
(is (= 1/3 (clojurize (ruby-eval "Rational(1,3)")))))
|
20
|
+
(testing "Big Decimals"
|
21
|
+
(is (= 12.34M (clojurize (ruby-eval "require 'bigdecimal'; BigDecimal.new(12.34,4)"))))))
|
10
22
|
(testing "Empty hash"
|
11
|
-
(is (= {} (clojurize (
|
23
|
+
(is (= {} (clojurize (ruby-eval "{}")))))
|
12
24
|
(testing "Non-empty hash"
|
13
|
-
(is (= {:a 1 :b 2} (clojurize (
|
25
|
+
(is (= {:a 1 :b 2} (clojurize (ruby-eval "{:a => 1, :b =>2 }")))))
|
26
|
+
(testing "Deep hash"
|
27
|
+
(is (= {:a 1 :b {:c 3 :d 4}} (clojurize (ruby-eval "{:a => 1, :b => {:c => 3, :d =>4}}")))))
|
28
|
+
(testing "Empty array"
|
29
|
+
(is (= [] (clojurize (ruby-eval "[]")))))
|
30
|
+
(testing "Non-empty array"
|
31
|
+
(is (= [1 :a "b"]) (clojurize (ruby-eval "[1, :a, 'b']"))))
|
32
|
+
(testing "Deep array"
|
33
|
+
(is (= [:a [:c :d] :e :f]) (clojurize (ruby-eval "[:a, [:c, :d], :e, :f]"))))
|
34
|
+
(testing "Complex DS"
|
35
|
+
(is (= [:a [:c {:d :e}] :f :g] (clojurize (ruby-eval "[:a, [:c, {:d => :e}], :f, :g]")))))
|
36
|
+
(testing "Times"
|
37
|
+
(testing "DateTime"
|
38
|
+
(let [date (clojurize (ruby-eval "DateTime.new(2013,2,19,12,34,56)"))] ;; => Tue, 19 Feb 2013 12:34:56 +0000
|
39
|
+
(is (= java.util.Date (class date)))
|
40
|
+
(is (= #inst "2013-02-19T12:34:56" date))))))
|
41
|
+
|
42
|
+
(deftest rubyize-test
|
43
|
+
(testing "Numbers"
|
44
|
+
(testing "Integer"
|
45
|
+
(is (= (ruby-eval "123")
|
46
|
+
(rubyize 123))))
|
47
|
+
(testing "Floating point"
|
48
|
+
(is (= (ruby-eval "123.45")
|
49
|
+
(rubyize 123.45))))
|
50
|
+
(testing "Rational"
|
51
|
+
(is (= (ruby-eval "Rational(1,3)")
|
52
|
+
(rubyize 1/3))))
|
53
|
+
(testing "Big Decimals"
|
54
|
+
(is (= (ruby-eval "require 'bigdecimal'; BigDecimal.new(12.34,4)")
|
55
|
+
(rubyize 12.34M)))))
|
56
|
+
(testing "Emtpy hash"
|
57
|
+
(is (.equals (ruby-eval "{}") (rubyize {}))))
|
58
|
+
(testing "Non-emtpy hash"
|
59
|
+
(is (.equals (ruby-eval "{:a => 1, :b => 2}") (rubyize {:a 1 :b 2}))))
|
14
60
|
(testing "Deep hash"
|
15
|
-
(is (
|
61
|
+
(is (.equals (ruby-eval "{:a => 1, :b => {:c => 3, :d =>4}}") (rubyize {:a 1 :b {:c 3 :d 4}}))))
|
16
62
|
|
17
|
-
(deftest array-test
|
18
63
|
(testing "Empty array"
|
19
|
-
(is (=
|
64
|
+
(is (= (ruby-eval "[]") (rubyize []))))
|
65
|
+
|
66
|
+
(testing "Empty list"
|
67
|
+
(is (= (ruby-eval "[]") (rubyize '()))))
|
68
|
+
|
20
69
|
(testing "Non-empty array"
|
21
|
-
(is (= [1 :a
|
70
|
+
(is (= (ruby-eval "[1, :a, 'b']") (rubyize [1 :a "b"])) ))
|
71
|
+
|
72
|
+
(testing "Lazy seq"
|
73
|
+
(is (= (ruby-eval "[:a, :b, :c]") (rubyize (lazy-seq [:a :b :c])))))
|
74
|
+
|
22
75
|
(testing "Deep array"
|
23
|
-
(is (= [:a [:c :d] :e :f]) (
|
76
|
+
(is (= (ruby-eval "[:a, [:c, :d], :e, :f]") (rubyize [:a [:c :d] :e :f])) ))
|
77
|
+
|
78
|
+
(testing "Complex DS"
|
79
|
+
(is (= (ruby-eval "[:a, [:c, {:d => :e}], :f, :g]") (rubyize [:a [:c {:d :e}] :f :g])))))
|
80
|
+
|
81
|
+
(deftest performance
|
82
|
+
(testing "this spec simply shows performance, very roughly, please do your testing depending on your needs"
|
83
|
+
(time
|
84
|
+
(let [ruby-obj (ruby-eval "{:a => 1, :b => 2 }")]
|
85
|
+
(dotimes [i 100000]
|
86
|
+
(clojurize ruby-obj))))
|
87
|
+
(time
|
88
|
+
(let [clj-obj {:a 1 :b 2}]
|
89
|
+
(dotimes [i 100000]
|
90
|
+
(rubyize clj-obj))))))
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: zweikopf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0
|
5
|
+
version: 0.4.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Oleksandr Petrov
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date:
|
13
|
+
date: 2014-01-27 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rake
|
@@ -72,6 +72,7 @@ files:
|
|
72
72
|
- spec/zweikopf/transformer_clojure_to_ruby_spec.rb
|
73
73
|
- spec/zweikopf/transformer_ruby_to_clojure_spec.rb
|
74
74
|
- src/zweikopf/core.clj
|
75
|
+
- src/zweikopf/multi.clj
|
75
76
|
- test/zweikopf/core_test.clj
|
76
77
|
- zweikopf.gemspec
|
77
78
|
homepage: ""
|