zweikopf 0.0.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](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: ""
|