rserve-client 0.1.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.tar.gz.sig +0 -0
- data/History.txt +3 -0
- data/Manifest.txt +31 -0
- data/README.txt +50 -0
- data/Rakefile +14 -0
- data/lib/rserve.rb +14 -0
- data/lib/rserve/connection.rb +138 -0
- data/lib/rserve/engine.rb +40 -0
- data/lib/rserve/packet.rb +31 -0
- data/lib/rserve/protocol.rb +183 -0
- data/lib/rserve/protocol/rexpfactory.rb +360 -0
- data/lib/rserve/rexp.rb +234 -0
- data/lib/rserve/rexp/double.rb +46 -0
- data/lib/rserve/rexp/genericvector.rb +29 -0
- data/lib/rserve/rexp/integer.rb +49 -0
- data/lib/rserve/rexp/list.rb +33 -0
- data/lib/rserve/rexp/logical.rb +48 -0
- data/lib/rserve/rexp/string.rb +33 -0
- data/lib/rserve/rexp/symbol.rb +26 -0
- data/lib/rserve/rexp/vector.rb +23 -0
- data/lib/rserve/rlist.rb +124 -0
- data/lib/rserve/talk.rb +123 -0
- data/spec/rserve_connection_spec.rb +43 -0
- data/spec/rserve_double_spec.rb +55 -0
- data/spec/rserve_integer_spec.rb +55 -0
- data/spec/rserve_packet_spec.rb +19 -0
- data/spec/rserve_protocol_spec.rb +75 -0
- data/spec/rserve_rexpfactory_spec.rb +44 -0
- data/spec/rserve_spec.rb +18 -0
- data/spec/rserve_talk_spec.rb +73 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +4 -0
- metadata +149 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
module Rserve
|
2
|
+
class REXP
|
3
|
+
class Double < REXP::Vector
|
4
|
+
attr_reader :payload
|
5
|
+
NA = 0x7ff00000000007a2;
|
6
|
+
def initialize(data, attrs=nil)
|
7
|
+
@payload=case data
|
8
|
+
when Numeric
|
9
|
+
[data.to_f]
|
10
|
+
when Array
|
11
|
+
data
|
12
|
+
else
|
13
|
+
raise ArgumentError, "Should be Numeric or Array"
|
14
|
+
end
|
15
|
+
super(attrs)
|
16
|
+
end
|
17
|
+
def length
|
18
|
+
payload.length
|
19
|
+
end
|
20
|
+
def integer?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
def numeric?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
def as_integers
|
27
|
+
@payload.map(&:to_i)
|
28
|
+
end
|
29
|
+
def as_doubles
|
30
|
+
@payload
|
31
|
+
end
|
32
|
+
def as_strings
|
33
|
+
@payload.map(&:to_s)
|
34
|
+
end
|
35
|
+
|
36
|
+
def na?(value=nil)
|
37
|
+
return value == NA unless value.nil?
|
38
|
+
@payload.map {|v| v==NA}
|
39
|
+
end
|
40
|
+
def to_debug_string
|
41
|
+
t=super
|
42
|
+
t << "{" << @payload.map(&:to_s).join(",") << "}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Rserve
|
2
|
+
class REXP
|
3
|
+
class GenericVector < REXP::Vector
|
4
|
+
attr_reader :payload
|
5
|
+
def initialize(list,attr=nil)
|
6
|
+
super(attr)
|
7
|
+
@payload=list.nil? ? Rlist.new() : list
|
8
|
+
|
9
|
+
if (payload.named?)
|
10
|
+
@attr = REXP::List.new(
|
11
|
+
Rlist.new([REXP::String.new(payload.keys())],
|
12
|
+
["names"]));
|
13
|
+
end
|
14
|
+
end
|
15
|
+
def length
|
16
|
+
@payload.size
|
17
|
+
end
|
18
|
+
def list?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
def recursive?
|
22
|
+
true
|
23
|
+
end
|
24
|
+
def as_list
|
25
|
+
@payload
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Rserve
|
2
|
+
class REXP
|
3
|
+
class Integer < REXP::Vector
|
4
|
+
public_class_method :new
|
5
|
+
attr_reader :payload
|
6
|
+
NA = -2147483648;
|
7
|
+
def initialize(data, attrs=nil)
|
8
|
+
@payload=case data
|
9
|
+
when Integer
|
10
|
+
[data]
|
11
|
+
when Numeric
|
12
|
+
[data.to_i]
|
13
|
+
when Array
|
14
|
+
data
|
15
|
+
else
|
16
|
+
raise ArgumentError, "Should be Numeric or Array"
|
17
|
+
end
|
18
|
+
super(attrs)
|
19
|
+
end
|
20
|
+
def length
|
21
|
+
payload.length
|
22
|
+
end
|
23
|
+
def integer?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
def numeric?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
def as_integers
|
30
|
+
@payload
|
31
|
+
end
|
32
|
+
def as_doubles
|
33
|
+
@payload.map(&:to_f)
|
34
|
+
end
|
35
|
+
def as_strings
|
36
|
+
@payload.map(&:to_s)
|
37
|
+
end
|
38
|
+
|
39
|
+
def na?(value=nil)
|
40
|
+
return value == NA unless value.nil?
|
41
|
+
@payload.map {|v| v==NA}
|
42
|
+
end
|
43
|
+
def to_debug_string
|
44
|
+
t=super
|
45
|
+
t << "{" << @payload.map(&:to_s).join(",") << "}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Rserve
|
2
|
+
class REXP
|
3
|
+
class List < REXP::Vector
|
4
|
+
attr_reader :payload
|
5
|
+
def initialize(list, attrs=nil)
|
6
|
+
@payload=list.nil? ? RList.new : list
|
7
|
+
super(attrs)
|
8
|
+
end
|
9
|
+
def length
|
10
|
+
payload.size
|
11
|
+
end
|
12
|
+
def list?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
def pair_list?
|
16
|
+
true
|
17
|
+
end
|
18
|
+
def recursive?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
def as_list
|
22
|
+
@payload
|
23
|
+
end
|
24
|
+
def to_s
|
25
|
+
super+(as_list.named? ? "named":"")
|
26
|
+
end
|
27
|
+
def to_debug_string
|
28
|
+
t=super
|
29
|
+
t << "{" << @payload.map {|k,v| "#{k}=#{v}"}.join(",\n") << "}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Rserve
|
2
|
+
class REXP
|
3
|
+
class Logical < REXP::Vector
|
4
|
+
attr_reader :payload
|
5
|
+
NA_internal = -2147483648;
|
6
|
+
NA=-128
|
7
|
+
TRUE=1
|
8
|
+
FALSE=0
|
9
|
+
def na?(value=nil)
|
10
|
+
if value
|
11
|
+
value==NA
|
12
|
+
else
|
13
|
+
@payload.map {|v| v==NA}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
def initialize(l,attr=nil)
|
17
|
+
super(attr)
|
18
|
+
if l.is_a? Array
|
19
|
+
@payload=l.map {|l| l==1 ? TRUE : FALSE}
|
20
|
+
else
|
21
|
+
@payload = [ l==1 ? TRUE : FALSE ]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
def length
|
25
|
+
@payload.length
|
26
|
+
end
|
27
|
+
def logical?
|
28
|
+
true
|
29
|
+
end
|
30
|
+
def as_integers
|
31
|
+
@payload.map {|v| v==NA ? REXP::Integer::NA : ( v == FALSE ? 0 : 1 )}
|
32
|
+
end
|
33
|
+
def as_doubles
|
34
|
+
@payload.map {|v| v==NA ? REXP::Double::NA : ( v == FALSE ? 0.0 : 1.0 )}
|
35
|
+
end
|
36
|
+
def as_strings
|
37
|
+
@payload.map {|v| v==NA ? REXP::Double::NA : ( v == FALSE ? "FALSE" : "TRUE" )}
|
38
|
+
end
|
39
|
+
def true?
|
40
|
+
@payload.map {|v| v==TRUE}
|
41
|
+
end
|
42
|
+
def false?
|
43
|
+
@payload.map {|v| v==FALSE}
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Rserve
|
2
|
+
class REXP
|
3
|
+
class String < REXP::Vector
|
4
|
+
attr_reader :payload
|
5
|
+
def initialize(data, attrs=nil)
|
6
|
+
@payload=case data
|
7
|
+
when Array
|
8
|
+
data.map {|v| v.to_s}
|
9
|
+
else
|
10
|
+
[data.to_s]
|
11
|
+
end
|
12
|
+
super(attrs)
|
13
|
+
end
|
14
|
+
def length
|
15
|
+
payload.length
|
16
|
+
end
|
17
|
+
def string?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
def as_strings
|
21
|
+
@payload
|
22
|
+
end
|
23
|
+
|
24
|
+
def na?
|
25
|
+
@payload.map {|v| v.nil}
|
26
|
+
end
|
27
|
+
def to_debug_string
|
28
|
+
t=super
|
29
|
+
t << "{" << @payload.map(&:to_s).join(",") << "}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Rserve
|
2
|
+
class REXP
|
3
|
+
class Symbol < REXP::Vector
|
4
|
+
attr_reader :name
|
5
|
+
def initialize(name)
|
6
|
+
super()
|
7
|
+
@name== name.nil? ? "":name
|
8
|
+
end
|
9
|
+
def symbol?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
def as_string
|
13
|
+
@name
|
14
|
+
end
|
15
|
+
def as_strings
|
16
|
+
[@name]
|
17
|
+
end
|
18
|
+
def to_s
|
19
|
+
super+"["+name+"]"
|
20
|
+
end
|
21
|
+
def to_debug_string
|
22
|
+
super+"["+name+"]"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Rserve
|
2
|
+
class REXP
|
3
|
+
class Vector < REXP
|
4
|
+
# returns the length of the vector (i.e. the number of elements)
|
5
|
+
# @return length of the vector
|
6
|
+
def length;
|
7
|
+
end;
|
8
|
+
def vector?;
|
9
|
+
true;
|
10
|
+
end
|
11
|
+
# returns a boolean vector of the same length as this vector with <code>true</code> for NA values and <code>false</code> for any other values
|
12
|
+
# @return a boolean vector of the same length as this vector with <code>true</code> for NA values and <code>false</code> for any other values */
|
13
|
+
def na?
|
14
|
+
end
|
15
|
+
def to_s
|
16
|
+
super+"[#{length}]"
|
17
|
+
end
|
18
|
+
def to_debug_string
|
19
|
+
super+"[#{length}]"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/rserve/rlist.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
module Rserve
|
2
|
+
class Rlist
|
3
|
+
include Enumerable
|
4
|
+
attr_reader :names
|
5
|
+
attr_reader :data
|
6
|
+
def initialize(data=nil,n=nil)
|
7
|
+
@names=nil
|
8
|
+
@data=[]
|
9
|
+
if data
|
10
|
+
@data=case data
|
11
|
+
when Array
|
12
|
+
data
|
13
|
+
when Numeric
|
14
|
+
Array.new(data)
|
15
|
+
else
|
16
|
+
raise ArgumentError
|
17
|
+
end
|
18
|
+
end
|
19
|
+
if n
|
20
|
+
@names=Array.new(@data.size)
|
21
|
+
n.each_index {|i| @names[i]=n[i]} if n.respond_to? :each_index
|
22
|
+
end
|
23
|
+
end
|
24
|
+
def named?
|
25
|
+
!@names.nil?
|
26
|
+
end
|
27
|
+
def [](v)
|
28
|
+
if v.is_a? String
|
29
|
+
return nil if @names.nil?
|
30
|
+
i=@names.index(v)
|
31
|
+
return i.nil? ? nil : @data[i]
|
32
|
+
elsif v.is_a? Integer
|
33
|
+
return @data[i]
|
34
|
+
else
|
35
|
+
raise ArgumentError,"Should be String or Integer"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def at(v)
|
40
|
+
@data[v]
|
41
|
+
end
|
42
|
+
|
43
|
+
def key_at(v)
|
44
|
+
return nil if @names.nil?
|
45
|
+
@names[v]
|
46
|
+
end
|
47
|
+
def size
|
48
|
+
@data.size
|
49
|
+
end
|
50
|
+
|
51
|
+
def each
|
52
|
+
@data.each do |v|
|
53
|
+
yield v
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def set_key_at(i,value)
|
58
|
+
if @names.nil?
|
59
|
+
@names=Array.new
|
60
|
+
end
|
61
|
+
if @names.size<size
|
62
|
+
(size-@names.size).times {@names.push(nil)}
|
63
|
+
end
|
64
|
+
|
65
|
+
@names[i]=value if i < size
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
def keys
|
70
|
+
@names
|
71
|
+
end
|
72
|
+
|
73
|
+
def put(key,value)
|
74
|
+
if key.nil?
|
75
|
+
add(value)
|
76
|
+
return nil
|
77
|
+
end
|
78
|
+
if !names.nil?
|
79
|
+
p=names.index(key)
|
80
|
+
if !p.nil?
|
81
|
+
return @names[p]=value
|
82
|
+
end
|
83
|
+
end
|
84
|
+
i=size
|
85
|
+
add(value)
|
86
|
+
if(@names.nil?)
|
87
|
+
@names=Array.new(i+1)
|
88
|
+
while(@names.size<i) do
|
89
|
+
@names.push(nil)
|
90
|
+
end
|
91
|
+
@names.push(key)
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
def add(a,b=nil)
|
96
|
+
if b.nil?
|
97
|
+
@data.push(a)
|
98
|
+
@names=Array.new(@data.size-1) if @names.nil?
|
99
|
+
@names.push(nil)
|
100
|
+
else
|
101
|
+
@data.insert(a,b)
|
102
|
+
@names.insert(a,nil)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
def clear
|
106
|
+
@data.clear
|
107
|
+
@names=nil
|
108
|
+
end
|
109
|
+
def clone
|
110
|
+
RList.new(@data,@names)
|
111
|
+
end
|
112
|
+
def remove(elem)
|
113
|
+
if elem.is_a? String
|
114
|
+
i=@data.index(elem)
|
115
|
+
elsif elem.is_a? Integer
|
116
|
+
i=elem
|
117
|
+
end
|
118
|
+
return false if i.nil?
|
119
|
+
@data.delete_at(i)
|
120
|
+
@names.delete_at(i) unless @names.nil?
|
121
|
+
@names=nil if size==0
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/rserve/talk.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
module Rserve
|
2
|
+
# Class which 'talk' to the server.
|
3
|
+
#
|
4
|
+
# I separated the 'abstract' aspect of the protocol on
|
5
|
+
# Protocol module, for better testing
|
6
|
+
#
|
7
|
+
class Talk
|
8
|
+
include Rserve::Protocol
|
9
|
+
attr_reader :io
|
10
|
+
def initialize(io)
|
11
|
+
@io=io
|
12
|
+
end
|
13
|
+
# sends a request with attached prefix and parameters.
|
14
|
+
# All parameters should be enter on Hash
|
15
|
+
# Both :prefix and :cont can be <code>nil</code>. Effectively <code>request(:cmd=>a,:prefix=>b,:cont=>nil)</code> and <code>request(:cmd=>a,:prefix=>nil,:cont=>b)</code> are equivalent.
|
16
|
+
# @param :cmd command - a special command of -1 prevents request from sending anything
|
17
|
+
# @param :prefix - this content is sent *before* cont. It is provided to save memory copy operations where a small header precedes a large data chunk (usually prefix conatins the parameter header and cont contains the actual data).
|
18
|
+
# @param :cont contents
|
19
|
+
# @param :offset offset in cont where to start sending (if <0 then 0 is assumed, if >cont.length then no cont is sent)
|
20
|
+
# @param :len number of bytes in cont to send (it is clipped to the length of cont if necessary)
|
21
|
+
# @return returned packet or <code>null</code> if something went wrong */
|
22
|
+
def request(params=Hash.new)
|
23
|
+
|
24
|
+
cmd = params.delete :cmd
|
25
|
+
prefix = params.delete :prefix
|
26
|
+
cont = params.delete :cont
|
27
|
+
offset = params.delete :offset
|
28
|
+
len = params.delete :len
|
29
|
+
|
30
|
+
if cont.is_a? String
|
31
|
+
cont=request_string(cont)
|
32
|
+
elsif cont.is_a? Integer
|
33
|
+
cont=request_int(cont)
|
34
|
+
end
|
35
|
+
raise ArgumentError, ":cont should be an Enumerable" if !cont.nil? and !cont.is_a? Enumerable
|
36
|
+
|
37
|
+
if len.nil?
|
38
|
+
len=(cont.nil?) ? 0 : cont.length
|
39
|
+
end
|
40
|
+
|
41
|
+
offset||=0
|
42
|
+
|
43
|
+
|
44
|
+
if (!cont.nil?)
|
45
|
+
if (offset>=cont.length)
|
46
|
+
cont=nil;len=0
|
47
|
+
elsif (len>cont.length-offset)
|
48
|
+
len=cont.length-offset
|
49
|
+
end
|
50
|
+
end
|
51
|
+
offset=0 if offset<0
|
52
|
+
len=0 if len<0
|
53
|
+
contlen=(cont.nil?) ? 0 : len
|
54
|
+
contlen+=prefix.length if (!prefix.nil? and prefix.length>0)
|
55
|
+
|
56
|
+
hdr=Array.new(16)
|
57
|
+
set_int(cmd,hdr,0)
|
58
|
+
set_int(contlen,hdr,4);
|
59
|
+
8.upto(15) {|i| hdr[i]=0}
|
60
|
+
if (cmd!=-1)
|
61
|
+
io.write(hdr.pack("C*"))
|
62
|
+
if (!prefix.nil? && prefix.length>0)
|
63
|
+
io.write(prefix.pack("C*"))
|
64
|
+
puts "SEND PREFIX #{prefix}" if $DEBUG
|
65
|
+
end
|
66
|
+
if (!cont.nil? && cont.length>0)
|
67
|
+
io.write(cont.slice(offset, len).pack("C*"))
|
68
|
+
puts "SEND CONTENT '#{cont.slice(offset, len).pack("C*")}'" if $DEBUG
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
ih=io.recv(16).unpack("C*")
|
73
|
+
|
74
|
+
return nil if (ih.length!=16)
|
75
|
+
|
76
|
+
puts "Answer: #{ih.to_s}" if $DEBUG
|
77
|
+
|
78
|
+
rep=get_int(ih,0);
|
79
|
+
|
80
|
+
rl =get_int(ih,4);
|
81
|
+
|
82
|
+
if $DEBUG
|
83
|
+
puts "rep: #{rep} #{rep.class} #{rep & 0x00FFFFFF}"
|
84
|
+
puts "rl: #{rl} #{rl.class}"
|
85
|
+
end
|
86
|
+
|
87
|
+
if (rl>0)
|
88
|
+
ct=Array.new();
|
89
|
+
n=0;
|
90
|
+
while (n<rl) do
|
91
|
+
data=io.recv(rl-n).unpack("C*")
|
92
|
+
ct+=data
|
93
|
+
rd=data.length
|
94
|
+
n+=rd;
|
95
|
+
end
|
96
|
+
|
97
|
+
return Packet.new(rep,ct);
|
98
|
+
end
|
99
|
+
|
100
|
+
return Packet.new(rep,nil);
|
101
|
+
end
|
102
|
+
def request_string(s)
|
103
|
+
b=s.unpack("C*")
|
104
|
+
sl=b.length+1;
|
105
|
+
sl=(sl&0xfffffc)+4 if ((sl&3)>0) # make sure the length is divisible by 4
|
106
|
+
rq=Array.new(sl+5)
|
107
|
+
|
108
|
+
b.length.times {|i| rq[i+4]=b[i]}
|
109
|
+
((b.length)..sl).each {|i|
|
110
|
+
rq[i+4]=0
|
111
|
+
}
|
112
|
+
set_hdr(DT_STRING,sl,rq,0)
|
113
|
+
rq
|
114
|
+
end
|
115
|
+
def request_int(par)
|
116
|
+
rq=Array.new(8)
|
117
|
+
set_int(par,rq,4)
|
118
|
+
set_hdr(DT_INT,4,rq,0)
|
119
|
+
rq
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|