rinterface 0.0.3

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/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.cache
2
+ *.beam
3
+ *.dump
4
+ *.gem
data/README.md ADDED
@@ -0,0 +1,111 @@
1
+ rinterface
2
+ ==========
3
+
4
+ Pure Ruby client that can send RPC calls to an Erlang node.
5
+ _It's very much a work in progress._
6
+
7
+ __License:__ MIT ?
8
+
9
+
10
+ ## Install
11
+
12
+ gem install rinterface
13
+
14
+
15
+ ## Try it out
16
+
17
+ Open a terminal and run:
18
+
19
+ rake run
20
+
21
+ This will start the erlang node named 'math'.
22
+
23
+ Open another terminal, and run:
24
+
25
+ ruby examples/client.rb
26
+
27
+
28
+ ## How to use?
29
+
30
+ In your Ruby code, make a call to the Erlang node like this:
31
+
32
+ Erlang::Node(nodename,module,function,args) => [:ok,Response] | [:badprc,Reason]
33
+
34
+
35
+ r = Erlang::Node.rpc("math","math_server","add",[10,20])
36
+
37
+ if r[0] == :badrpc
38
+ puts "Got and Error. Reason #{r[1]}"
39
+ else
40
+ puts "Success: #{r[1]}"
41
+ end
42
+
43
+ Where:
44
+
45
+ * math is the node name (the -sname of the Erlang node)
46
+ * math_server is the name of the module
47
+ * add is the funtion to call
48
+ * [10,20] is an array of arguments to pass to the function
49
+
50
+ The result will always be an Array of the form:
51
+
52
+ [:ok, Response]
53
+
54
+ Where Response is the result from the Erlang, or:
55
+
56
+ [:badrpc, Reason]
57
+
58
+ Where Reason is the 'why' it failed.
59
+
60
+ ### Experimental new way
61
+
62
+ The code above can be written like this now:
63
+
64
+ Erl::MathServer.add(10, 20)
65
+
66
+ (Ain`t Ruby a beauty? ;)
67
+
68
+
69
+ ## So you wanna...
70
+
71
+ ### Test your Erlang code from RSpec...
72
+
73
+ Here's a quick and simple example. Make sure you put the rinterface lib into RAILS_ROOT/lib and start the math_server in 'test'
74
+
75
+
76
+
77
+ ### Call Erlang from your Rails app...
78
+
79
+ Here's a quick and simple example. Make sure you put the rinterface lib into RAILS_ROOT/lib and start the math_server in 'test'
80
+ In the controller:
81
+
82
+ controllers/math_controller.rb
83
+
84
+ require "lib/rinterface"
85
+
86
+ class MathController < ApplicationController
87
+ def index
88
+ a = params[:a]
89
+ b = params[:b]
90
+ r = Erlang::Node.rpc("math","math_server","add",[a.to_i,b.to_i])
91
+
92
+ if r[0] == :badrpc
93
+ @result = "Error"
94
+ else
95
+ @result = r[1]
96
+ end
97
+ end
98
+ end
99
+
100
+
101
+ Finally, add a template for the view, and try 'http://localhost:3000/math?a=2&b=3'.
102
+ This is not ideal yet and not something I'd use yet in production,
103
+ but it's a starting point for experimenting.
104
+
105
+
106
+ ## Specs
107
+
108
+ Don`t forget to run specs before/after patching.
109
+
110
+ Use either "rake spec" or "spec spec",
111
+ it`ll start the erlang daemon automatically.
data/Rakefile ADDED
@@ -0,0 +1,107 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'rubygems'
3
+ require 'rake'
4
+ require 'rake/clean'
5
+ CLEAN << FileList['**/*.dump']
6
+ CLOBBER << FileList['**/*.beam']
7
+ SRC = FileList['**/*.erl']
8
+ OBJ = SRC.ext("beam") #)pathmap("%{examples,spec}X.beam")
9
+ ERLC_FLAGS = "-Iinclude +warn_unused_vars +warn_unused_import"
10
+ START_MODULE = "math_server"
11
+
12
+ # Gem
13
+ #
14
+ begin
15
+ require 'jeweler'
16
+ Jeweler::Tasks.new do |gem|
17
+ gem.name = "rinterface"
18
+ gem.summary = "Erlang RPC Ruby Client"
19
+ gem.description = "Pure Ruby client that can send RPC calls to an Erlang node"
20
+ gem.email = "r@interf.ace"
21
+ gem.homepage = "http://github.com/davebryson/rinterface"
22
+ gem.authors = ["Dave Bryson", "Éverton Ribeiro", "Marcos Piccinini"]
23
+ gem.add_dependency "eventmachine"
24
+ gem.add_development_dependency "rspec"
25
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
26
+ end
27
+ Jeweler::GemcutterTasks.new
28
+ rescue LoadError
29
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
30
+ end
31
+
32
+ # Compile
33
+ #
34
+ rule ".beam" => ".erl" do |t|
35
+ sh "erlc -o #{t.name.split("/")[0]} -W #{ERLC_FLAGS} #{t.source}"
36
+ end
37
+
38
+ desc "Compile all"
39
+ task :compile => OBJ
40
+ task :default => :compile
41
+
42
+ # Shell
43
+ #
44
+ desc "Open up a shell"
45
+ task :shell => [:compile] do
46
+ sh("erl -sname rinterface -pa examples")
47
+ end
48
+
49
+ namespace :daemon do
50
+ desc "Daemon run #{START_MODULE}:start()"
51
+ task :start => [:compile] do
52
+ pid = sh("erl -heart -W -pa examples -sname math -s #{START_MODULE} -detached")
53
+ puts "Now try 'ruby examples/client.rb' #{pid}"
54
+ end
55
+
56
+ task :status do
57
+ sh("ps aux | grep #{START_MODULE}")
58
+ end
59
+ end
60
+
61
+ namespace :spec do
62
+ desc "Open up a shell and run spec test server"
63
+ task :run => [:compile] do
64
+ sh("erl -noshell -W -pa spec -sname spec -s spec_server&")
65
+ end
66
+ desc "Open up a shell and run spec test server"
67
+ task :shell => [:compile] do
68
+ sh("erl -W -pa spec -sname spec -s spec_server")
69
+ end
70
+ end
71
+
72
+ task :test => [:compile] do
73
+ puts "Running suite.."
74
+ Rake::Task["spec"].invoke
75
+ Rake::Task["spec"].invoke
76
+ end
77
+
78
+ # Spec
79
+ #
80
+ require 'spec/rake/spectask'
81
+ Spec::Rake::SpecTask.new(:spec) do |spec|
82
+ spec.libs << 'lib' << 'spec'
83
+ spec.spec_files = FileList['spec/**/*_spec.rb']
84
+ end
85
+
86
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
87
+ spec.libs << 'lib' << 'spec'
88
+ spec.pattern = 'spec/**/*_spec.rb'
89
+ spec.rcov = true
90
+ end
91
+ task :spec => :check_dependencies
92
+
93
+ # RDoc
94
+ #
95
+ require 'rake/rdoctask'
96
+ Rake::RDocTask.new do |rdoc|
97
+ if File.exist?('VERSION')
98
+ version = File.read('VERSION')
99
+ else
100
+ version = ""
101
+ end
102
+
103
+ rdoc.rdoc_dir = 'rdoc'
104
+ rdoc.title = "rinterface #{version}"
105
+ rdoc.rdoc_files.include('README*')
106
+ rdoc.rdoc_files.include('lib/**/*.rb')
107
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.3
@@ -0,0 +1,19 @@
1
+ require 'lib/rinterface'
2
+
3
+
4
+ # Try different responses...
5
+
6
+ puts "Bad rpc test. Try to call the wrong service"
7
+ r = Erlang::Node.rpc("math","matx_server","add",[10,20])
8
+ puts "Got: #{r.inspect}"
9
+
10
+ puts "--------"
11
+ puts "Bad port test, No Port for Service. Can't find a port for 'ath'"
12
+ r = Erlang::Node.rpc("ath","matx_server","add",[10,20])
13
+ puts "Got: #{r.inspect}"
14
+
15
+ puts "--------"
16
+ puts "Good call, add 10 + 20"
17
+ r = Erlang::Node.rpc("math","math_server","add",[10,20])
18
+ puts "Got: #{r.inspect}"
19
+
@@ -0,0 +1,28 @@
1
+ %%
2
+ %% Simple add server
3
+ %%
4
+ -module(math_server).
5
+ -export([start/0,add/2]).
6
+
7
+ start() ->
8
+ register(?MODULE,spawn(fun() -> loop() end)).
9
+
10
+ add(X,Y) ->
11
+ ?MODULE ! {self(),add,X,Y},
12
+ receive
13
+ {?MODULE,Response} -> Response
14
+ end.
15
+
16
+ sum(X,Y) -> X+Y.
17
+
18
+ loop() ->
19
+ receive
20
+ {From,add,X,Y} ->
21
+ error_logger:info_msg("Got the request, and doing the add...~n"),
22
+ Sum = X+Y,
23
+ From ! {?MODULE,Sum},
24
+ loop();
25
+ Any ->
26
+ error_logger:info_msg("Got a crazy msg: ~p~n",[Any]),
27
+ loop()
28
+ end.
@@ -0,0 +1,58 @@
1
+ #
2
+ # Connects to epmd to find the port number of the requested node
3
+ # this only implements port_please_request
4
+ #
5
+ module Erlang
6
+ class EpmdConnection < EM::Connection
7
+ include EM::Deferrable
8
+ attr_accessor :nodename
9
+
10
+ def self.lookup_node(nodename)
11
+ EM.connect("127.0.0.1",4369,self) do |conn|
12
+ conn.nodename = nodename
13
+ end
14
+ end
15
+
16
+ def connection_completed
17
+ send_data lookup_port
18
+ end
19
+
20
+ def receive_data(data)
21
+ parse_response(data)
22
+ end
23
+
24
+ def unbind
25
+ end
26
+
27
+ def lookup_port
28
+ out = StringIO.new('', 'w')
29
+
30
+ # Create the header with length: 2
31
+ out.write([@nodename.size + 1].pack('n'))
32
+
33
+ # Next the request
34
+ # tag. Length: 1
35
+ out.write([122].pack("C"))
36
+ # nodename
37
+ out.write(nodename)
38
+ out.string
39
+ end
40
+
41
+ # If we get a good result we only return
42
+ # the port (not reading all the information
43
+ def parse_response(input)
44
+ i = StringIO.new(input)
45
+ code = i.read(1).unpack('C').first
46
+ result = i.read(1).unpack('C').first
47
+ if result == 0
48
+ # good response read the port
49
+ port = i.read(2).unpack('n').first
50
+ set_deferred_success port
51
+ else
52
+ set_deferred_failure 0
53
+ end
54
+ end
55
+
56
+ end
57
+ end
58
+
@@ -0,0 +1,24 @@
1
+ module Rinterface
2
+
3
+ module Erl
4
+
5
+ class << self
6
+
7
+ def method_missing(fun, node, *rest)
8
+ Erlang::Node.fun(node.to_s, @@module, fun.to_s, *rest)
9
+ end
10
+
11
+ def const_missing(m)
12
+ @@module = snake(m)
13
+ self
14
+ end
15
+
16
+ def snake(txt)
17
+ txt.to_s.split(/(?=[A-Z])/).join('_').downcase
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,223 @@
1
+ module Erlang
2
+
3
+ class Decode
4
+ include External::Types
5
+
6
+ attr_accessor :in
7
+
8
+ def self.read_bits(string)
9
+ new(StringIO.new(string))
10
+ end
11
+
12
+ def self.read_any_from(string)
13
+ new(StringIO.new(string)).read_any
14
+ end
15
+
16
+ def initialize(ins)
17
+ @in = ins
18
+ @peeked = ""
19
+ end
20
+
21
+ def read_any
22
+ raise "Bad Math on Version" unless read_1 == External::VERSION
23
+ read_any_raw
24
+ end
25
+
26
+ def read_any_raw
27
+ case peek_1
28
+ when ATOM then read_atom
29
+ when SMALL_INT then read_small_int
30
+ when INT then read_int
31
+ when SMALL_BIGNUM then read_small_bignum
32
+ when LARGE_BIGNUM then read_large_bignum
33
+ when NEW_FLOAT then read_double
34
+ when FLOAT then read_float
35
+ when NEW_REF then read_new_reference
36
+ when PID then read_pid
37
+ when SMALL_TUPLE then read_small_tuple
38
+ when LARGE_TUPLE then read_large_tuple
39
+ when NIL then read_nil
40
+ when STRING then read_erl_string
41
+ when LIST then read_list
42
+ when BIN then read_bin
43
+ else
44
+ fail("Unknown term tag: #{peek_1}")
45
+ end
46
+ end
47
+
48
+ def read(length)
49
+ # p @peeked
50
+ if length < @peeked.length
51
+ result = @peeked[0...length]
52
+ @peeked = @peeked[length..-1]
53
+ length = 0
54
+ else
55
+ result = @peeked
56
+ @peeked = ''
57
+ length -= result.length
58
+ end
59
+
60
+ if length > 0
61
+ result << @in.read(length)
62
+ end
63
+ result
64
+ end
65
+
66
+ def peek(length)
67
+ if length <= @peeked.length
68
+ @peeked[0...length]
69
+ else
70
+ read_bytes = @in.read(length - @peeked.length)
71
+ @peeked << read_bytes if read_bytes
72
+ @peeked
73
+ end
74
+ end
75
+
76
+ def peek_1
77
+ peek(1).unpack("C").first
78
+ end
79
+
80
+ def peek_2
81
+ peek(2).unpack("n").first
82
+ end
83
+
84
+ def read_1
85
+ read(1).unpack("C").first
86
+ end
87
+
88
+ def read_2
89
+ read(2).unpack("n").first
90
+ end
91
+
92
+ def read_4
93
+ read(4).unpack("N").first
94
+ end
95
+
96
+ def read_string(length)
97
+ read(length)
98
+ end
99
+
100
+ def read_atom
101
+ fail("Invalid Type, not an atom") unless read_1 == ATOM
102
+ length = read_2
103
+ if length == 0
104
+ ''
105
+ else
106
+ read_string(length).to_sym
107
+ end
108
+ end
109
+
110
+ def read_small_int
111
+ fail("Invalid Type, not a small int") unless read_1 == SMALL_INT
112
+ read_1
113
+ end
114
+
115
+ def read_int
116
+ fail("Invalid Type, not an int") unless read_1 == INT
117
+ value = read_4
118
+ negative = (value >> 31)[0] == 1
119
+ value = (value - (1 << 32)) if negative
120
+ value = Fixnum.induced_from(value)
121
+ end
122
+
123
+ def read_small_bignum
124
+ fail("Invalid Type, not a small bignum") unless read_1 == SMALL_BIGNUM
125
+ size = read_1
126
+ sign = read_1
127
+ bytes = read_string(size).unpack("C" * size)
128
+ added = bytes.zip((0..bytes.length).to_a).inject(0) do |result, byte_index|
129
+ byte, index = *byte_index
130
+ value = (byte * (256 ** index))
131
+ sign != 0 ? (result - value) : (result + value)
132
+ end
133
+ Bignum.induced_from(added)
134
+ end
135
+
136
+ def read_large_bignum
137
+ fail("Invalid Type, not a large bignum") unless read_1 == LARGE_BIGNUM
138
+ size = read_4
139
+ sign = read_1
140
+ bytes = read_string(size).unpack("C" * size)
141
+ added = bytes.zip((0..bytes.length).to_a).inject(0) do |result, byte_index|
142
+ byte, index = *byte_index
143
+ value = (byte * (256 ** index))
144
+ sign != 0 ? (result - value) : (result + value)
145
+ end
146
+ Bignum.induced_from(added)
147
+ end
148
+
149
+ def read_double
150
+ fail("Invalid Type, not a double") unless read_1 == NEW_FLOAT
151
+ read_string(64).unpack('G').first
152
+ end
153
+
154
+ def read_float
155
+ fail("Invalid Type, not a float") unless read_1 == FLOAT
156
+
157
+ read_string(32).unpack('g').first
158
+ end
159
+
160
+ def read_new_reference
161
+ fail("Invalid Type, not a new-style reference") unless read_1 == NEW_REF
162
+ size = read_2
163
+ node = read_atom
164
+ creation = read_1
165
+ id = (0...size).map{|i| read_4 }
166
+ NewReference.new(node, creation, id)
167
+ end
168
+
169
+ def read_pid
170
+ fail("Invalid Type, not a pid") unless read_1 == PID
171
+ node = read_atom
172
+ id = read_4
173
+ serial = read_4
174
+ creation = read_1
175
+ Terms::Pid.new(node, id, serial, creation)
176
+ end
177
+
178
+ def read_small_tuple
179
+ fail("Invalid Type, not a small tuple") unless read_1 == SMALL_TUPLE
180
+ arity = read_1
181
+
182
+ (0...arity).map{|i| read_any_raw }
183
+ end
184
+
185
+ def read_large_tuple
186
+ fail("Invalid Type, not a small tuple") unless read_1 == LARGE_TUPLE
187
+ arity = read_4
188
+ (0...arity).map{|i| read_any_raw}
189
+ end
190
+
191
+ def read_nil
192
+ fail("Invalid Type, not a nil list") unless read_1 == NIL
193
+ []
194
+ end
195
+
196
+ def read_erl_string
197
+ fail("Invalid Type, not an erlang string") unless read_1 == STRING
198
+ length = read_2
199
+ read_string(length).unpack('C' * length)
200
+ end
201
+
202
+ def read_list
203
+ fail("Invalid Type, not an erlang list") unless read_1 == LIST
204
+ length = read_4
205
+ list = (0...length).map{|i| read_any_raw}
206
+ read_1
207
+ list
208
+ end
209
+
210
+ def read_bin
211
+ fail("Invalid Type, not an erlang binary") unless read_1 == BIN
212
+ length = read_4
213
+ read_string(length)
214
+ end
215
+
216
+ class DecodeError < StandardError; end
217
+ def fail(str)
218
+ raise DecodeError, str
219
+ end
220
+
221
+ end
222
+
223
+ end
@@ -0,0 +1,107 @@
1
+ module Erlang
2
+ class Encoder
3
+ include External::Types
4
+ include Terms
5
+
6
+ attr_accessor :out
7
+ def initialize
8
+ @out = StringIO.new('', 'w')
9
+ end
10
+
11
+ def rewind
12
+ @out.rewind
13
+ end
14
+
15
+ def term_to_binary obj
16
+ write_1 External::VERSION
17
+ write_any_raw obj
18
+ end
19
+
20
+ def write_any_raw obj
21
+ case obj
22
+ when Symbol then write_symbol(obj)
23
+ when Fixnum, Bignum then write_integer(obj)
24
+ when Float then write_double(obj)
25
+ when Array then write_tuple(obj)
26
+ when String then write_binary(obj)
27
+ when Pid then write_pid(obj)
28
+ when List then write_list(obj)
29
+ else
30
+ raise "Failed encoding!"
31
+ end
32
+ end
33
+
34
+ def write_1(byte)
35
+ @out.write([byte].pack("C"))
36
+ end
37
+
38
+ def write_2(short)
39
+ @out.write([short].pack("n"))
40
+ end
41
+
42
+ def write_4(long)
43
+ @out.write([long].pack("N"))
44
+ end
45
+
46
+ def write_string(string)
47
+ @out.write(string)
48
+ end
49
+
50
+ def write_symbol(sym)
51
+ data = sym.to_s
52
+ write_1 ATOM
53
+ write_2 data.length
54
+ write_string data
55
+ end
56
+
57
+ # TODO: Bignum support
58
+ def write_integer(num)
59
+ if 0 <= num && num < 256
60
+ write_1 SMALL_INT
61
+ write_1 num
62
+ else
63
+ write_1 INT
64
+ write_4 num
65
+ end
66
+ end
67
+
68
+ def write_double(num)
69
+ write_1 NEW_FLOAT
70
+ @out.write([num].pack('G'))
71
+ end
72
+
73
+ def write_tuple(data)
74
+ if data.length < 256
75
+ write_1 SMALL_TUPLE
76
+ write_1 data.length
77
+ else
78
+ write_1 LARGE_TUPLE
79
+ write_4 data.length
80
+ end
81
+ data.each{|e| write_any_raw e }
82
+ end
83
+
84
+ def write_pid(pid)
85
+ write_1(103)
86
+ write_symbol(pid.node)
87
+ write_4((pid.node_id & 0x7fff))
88
+ write_4((pid.serial & 0x1fff))
89
+ write_1((pid.creation & 0x3))
90
+ end
91
+
92
+ def write_list(list)
93
+ len = list.data.size
94
+ write_1(108)
95
+ write_4(len)
96
+ list.data.each{ |i| write_any_raw i }
97
+ write_1(106)
98
+ end
99
+
100
+ def write_binary(data)
101
+ write_1 BIN
102
+ write_4 data.length
103
+ write_string data
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,28 @@
1
+ #
2
+ # adopted from Erlectricity
3
+ # this version is slightly tweaked, a bit sloppy, and needs a cleanin'
4
+ #
5
+ module Erlang
6
+ module Terms
7
+
8
+ class Pid
9
+ attr_reader :node, :node_id, :serial, :creation
10
+ def initialize(node,nid,serial,created)
11
+ @node = node
12
+ @node_id = nid
13
+ @serial = serial
14
+ @creation = created
15
+ end
16
+ end
17
+
18
+ class List
19
+ attr_reader :data
20
+ def initialize(array)
21
+ @data = array
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+
28
+ end