elrpc 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +190 -17
- data/elrpc.gemspec +2 -2
- data/lib/elrpc/version.rb +1 -1
- data/lib/elrpc.rb +22 -5
- data/sample/echo-client.el +2 -2
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ff7cff0814489d4e855848dd5f6c59ebbcffcf0
|
4
|
+
data.tar.gz: 246d1edd90a7f3ecf6f039b23a8b9c13f7fc053b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf75699c742ea27c6c49073a45e5c01c50f4b6411946738236906459dcaf54263e6c6d765681b34d3ad62d884691f38b2ac5d031da070601276b836078e38d88
|
7
|
+
data.tar.gz: 502a9ac563cc7886a70dd4c66b0f0a5b99448478d3faa5656f872bbe104ccad86c269b70ad4022f2f533eedd1f975fa0e0e3613715f84b5393872d5fc7821e65
|
data/README.md
CHANGED
@@ -3,14 +3,15 @@
|
|
3
3
|
EPC is an RPC stack for Emacs Lisp and Elrpc is an implementation of EPC in Ruby.
|
4
4
|
Using elrpc, you can develop an emacs extension with Ruby code.
|
5
5
|
|
6
|
-
- https://github.com/kiwanami/emacs-epc
|
6
|
+
- [EPC at github](https://github.com/kiwanami/emacs-epc)
|
7
7
|
|
8
8
|
## Sample Code
|
9
9
|
|
10
|
-
### Ruby code (
|
10
|
+
### Ruby code (server process)
|
11
11
|
|
12
|
-
This code
|
12
|
+
This code is started by the client process, such as Emacs Lisp.
|
13
13
|
|
14
|
+
`echo.rb`
|
14
15
|
```ruby
|
15
16
|
require 'elrpc'
|
16
17
|
|
@@ -27,16 +28,17 @@ end
|
|
27
28
|
server.wait
|
28
29
|
```
|
29
30
|
|
30
|
-
### Emacs Lisp code (
|
31
|
+
### Emacs Lisp code (client process)
|
31
32
|
|
32
|
-
This elisp code calls the
|
33
|
+
This elisp code calls the server process.
|
33
34
|
The package `epc` is required.
|
34
35
|
|
36
|
+
`echo-client.el`
|
35
37
|
```el
|
36
38
|
(require 'epc)
|
37
39
|
|
38
40
|
(let (epc)
|
39
|
-
;; start a
|
41
|
+
;; start a server process (using bundle exec)
|
40
42
|
(setq epc (epc:start-epc "bundle" '("exec" "ruby" "echo.rb")))
|
41
43
|
|
42
44
|
(deferred:$
|
@@ -49,14 +51,15 @@ The package `epc` is required.
|
|
49
51
|
(epc:stop-epc epc)) ; just `eval-last-sexp' here
|
50
52
|
```
|
51
53
|
|
52
|
-
### Ruby code (
|
54
|
+
### Ruby code (client process)
|
53
55
|
|
54
|
-
You can also write the
|
56
|
+
You can also write the client process code in Ruby.
|
55
57
|
|
58
|
+
`echo-client.rb`
|
56
59
|
```ruby
|
57
60
|
require 'elrpc'
|
58
61
|
|
59
|
-
# start a
|
62
|
+
# start a server process
|
60
63
|
cl = Elrpc.start_process(["ruby","echo.rb"])
|
61
64
|
|
62
65
|
# synchronous calling
|
@@ -71,7 +74,7 @@ puts "2 wait"
|
|
71
74
|
sleep 0.2
|
72
75
|
|
73
76
|
puts "4 ok"
|
74
|
-
# kill the
|
77
|
+
# kill the server process
|
75
78
|
cl.stop
|
76
79
|
```
|
77
80
|
|
@@ -103,6 +106,16 @@ Or install it yourself as:
|
|
103
106
|
|
104
107
|
## API Document
|
105
108
|
|
109
|
+
### EPC Overview
|
110
|
+
|
111
|
+
The EPC uses a peer-to-peer-architecture. After the connection is established, both peers can define remote methods and call the methods at the other side.
|
112
|
+
|
113
|
+
Let we define the words *server* and *client*. *Server* is a process which opens a TCP port and waiting for the connection. *Client* is a process which connects to the *server*. In most cases, a *client* process starts a *server* process. Then, the *server* process provides some services to the *client* process.
|
114
|
+
|
115
|
+
This diagram shows the API usage and the relation of processes.
|
116
|
+
|
117
|
+
![EPC Overview](https://cacoo.com/diagrams/cFQgb5d5W9MphN8z-B5059.png)
|
118
|
+
|
106
119
|
Please see the EPC document for the overview of EPC stack and protocol details.
|
107
120
|
|
108
121
|
- [EPC Readme](https://github.com/kiwanami/emacs-epc)
|
@@ -111,21 +124,181 @@ Please see the `elparser` document for object serialization.
|
|
111
124
|
|
112
125
|
- [elparser Readme](https://github.com/kiwanami/ruby-elparser)
|
113
126
|
|
114
|
-
###
|
127
|
+
### Building Server-Process
|
128
|
+
|
129
|
+
- Module method : `Elrpc::start_server(methods = [], port = 0)`
|
130
|
+
- Arguments
|
131
|
+
- `methods` : Array of `Elrpc::Method` instances.
|
132
|
+
- `port` : TCP Port number. 0 means that the number is decided by OS.
|
133
|
+
- Return
|
134
|
+
- A `Elrpc::RPCService` instance
|
135
|
+
|
136
|
+
|
137
|
+
*Sample Code*
|
138
|
+
```ruby
|
139
|
+
server = Elrpc::start_server
|
140
|
+
```
|
141
|
+
|
142
|
+
|
143
|
+
### Defining Remote Method
|
144
|
+
|
145
|
+
- `Elrpc::RPCService#def_method(name, argdoc=nil, docstring=nil, &block)`
|
146
|
+
- Arguments
|
147
|
+
- `name` : String. Method name which is referred by the peer process.
|
148
|
+
- `argdoc` : String[optional]. Argument information for human.
|
149
|
+
- `docstring` : String[optional]. Method information for human.
|
150
|
+
- `&block` : Block. Code block which is called by the peer process. The return value is serialized and sent to the peer.
|
151
|
+
|
152
|
+
The return value of the code block is serialized and sent to the peer process. So, if the return value includes wrong values which can't be serialized by `elparser`, the runtime exception `EPCStackError` is thrown to the method calling of the peer process.
|
153
|
+
|
154
|
+
*Sample Code*
|
155
|
+
```ruby
|
156
|
+
server.def_method("echo") do |arg|
|
157
|
+
arg
|
158
|
+
end
|
159
|
+
|
160
|
+
server.def_method("add") do |a, b|
|
161
|
+
a + b
|
162
|
+
end
|
115
163
|
|
116
|
-
|
164
|
+
server.def_method("inject",
|
165
|
+
"init(initial value), op(operator symbol), list",
|
166
|
+
"Apply Enumerable#inject method.") do |init, op, list|
|
167
|
+
list.inject(init, op)
|
168
|
+
end
|
169
|
+
```
|
117
170
|
|
118
|
-
###
|
171
|
+
### Calling Remote Method
|
172
|
+
|
173
|
+
If the peer process defines some methods, the instance of `Elrpc::RPCService` can call the peer's method, regardless of the server process or the client one. (See the EPC document.)
|
174
|
+
|
175
|
+
- `Elrpc::RPCService#call_method(name, *args)`
|
176
|
+
- Synchronous method calling. The current thread is blocked until the calling result is returned.
|
177
|
+
- Arguments
|
178
|
+
- `name` : String. Method name to call.
|
179
|
+
- `args` : Array(Variable length arguments) Argument values.
|
180
|
+
- Return
|
181
|
+
- The return value which is returned by the peer process.
|
182
|
+
- Exception
|
183
|
+
- `EPCRuntimeError` : An exception which is thrown by the peer's method.
|
184
|
+
- `EPCStackError` : An exception which is thrown by the EPC protocol stack.
|
185
|
+
|
186
|
+
- `Elrpc::RPCService#call_method_async(name, *args, &block)`
|
187
|
+
- Asynchronous method calling. The current thread is not blocked. The calling result is passed to the code block.
|
188
|
+
- Arguments
|
189
|
+
- `name` : String. Method name to call.
|
190
|
+
- `args` : Array(Variable length arguments) Argument values.
|
191
|
+
- `block` : Block.
|
192
|
+
- Block Arguments
|
193
|
+
- `err` : If `nil`, the method calling succeed and the return value is bounded by `value`. If not `nil`, an exception is thrown within the method calling.
|
194
|
+
- `EPCRuntimeError` : An exception which is thrown by the peer's method.
|
195
|
+
- `EPCStackError` : An exception which is thrown by the EPC protocol stack.
|
196
|
+
- `value` : The return value which is returned by the peer process.
|
197
|
+
|
198
|
+
*Sample Code*
|
199
|
+
```ruby
|
200
|
+
puts server.call_method("echo", "hello")
|
119
201
|
|
120
|
-
|
202
|
+
server.call_method_async("echo", "hello") do |err, value|
|
203
|
+
puts value
|
204
|
+
end
|
121
205
|
|
122
|
-
|
206
|
+
puts server.call_method("add", 1, 2)
|
207
|
+
|
208
|
+
server.call_method("add", 1, 2) do |err, value|
|
209
|
+
puts value
|
210
|
+
end
|
211
|
+
|
212
|
+
puts server.call_method("inject", 0, :+, [1,2,3,4])
|
213
|
+
puts server.call_method("inject", 1, :*, [1,2,3,4])
|
214
|
+
```
|
123
215
|
|
124
216
|
### Utilities
|
125
217
|
|
126
|
-
|
218
|
+
- `Elrpc::RPCService#query_methods`
|
219
|
+
- `Elrpc::RPCService#query_methods_async`
|
220
|
+
- Return
|
221
|
+
- Array of method specs of the peer process.
|
222
|
+
|
223
|
+
*Sample Code*
|
224
|
+
```ruby
|
225
|
+
server.query_methods
|
226
|
+
# => [[:echo, nil, nil], [:add, nil, nil], [:inject, "init, op, list", "Enumerable#inject"]]
|
227
|
+
```
|
228
|
+
|
229
|
+
### EPC Process
|
230
|
+
|
231
|
+
Elrpc can implement the client process which starts a server process. The server process can be implemented in Ruby and the other language, such as Perl, Python and Emacs Lisp.
|
232
|
+
|
233
|
+
- Module method `Elrpc::start_process(cmd)`
|
234
|
+
- Argument
|
235
|
+
- `cmd` : Array. Command line elements for the server process.
|
236
|
+
- Return
|
237
|
+
- An instance of `Elrpc::Service`.
|
238
|
+
|
239
|
+
*Sample Code*
|
240
|
+
```ruby
|
241
|
+
cl = Elrpc.start_process(["ruby","echo.rb"])
|
242
|
+
|
243
|
+
puts cl.call_method("echo", "1 hello")
|
244
|
+
|
245
|
+
cl.stop
|
246
|
+
```
|
247
|
+
|
248
|
+
## Development
|
249
|
+
|
250
|
+
In most cases, the client process is Emacs and the server one is implemented by Elrpc to extend Emacs functions in Ruby.
|
251
|
+
However, it may be difficult to develop the programs which belong to the different environment.
|
252
|
+
So, at first, it is better to implement both sides in Ruby and write tests.
|
253
|
+
|
254
|
+
If you want to watch the STDOUT and STDERR of the server process, start the process from command line and connect to the process with `irb` or `pry`, like following:
|
255
|
+
|
256
|
+
*Starting the server process*
|
257
|
+
```
|
258
|
+
$ bundle exec ruby echo.rb
|
259
|
+
12345
|
260
|
+
```
|
261
|
+
|
262
|
+
`12345` is port number to connect from the client process. The number changes each time.
|
263
|
+
Then, start `irb` in the another terminal.
|
264
|
+
|
265
|
+
*Connecting to the process from irb*
|
266
|
+
```
|
267
|
+
$ bundle exec irb
|
268
|
+
> require 'elrpc'
|
269
|
+
> cl = Elrpc.start_client(12345)
|
270
|
+
> cl.call_method("echo", "hello")
|
271
|
+
```
|
272
|
+
|
273
|
+
When you invoke `call_method`, the first terminal in which the server process runs, may show some output.
|
274
|
+
|
275
|
+
## Performance
|
276
|
+
|
277
|
+
EPC is designed for fast communication between Emacs and other processes.
|
278
|
+
Employing S-exp serialization and keeping TCP connection, EPC is faster than HTTP-based RPC, such as JSON-RPC.
|
279
|
+
|
280
|
+
Executing the benchmark program `test/echo-bench.rb`, You can check the performance in your environment. The program measures following aspects:
|
281
|
+
|
282
|
+
- round trip time of method invocation
|
283
|
+
- synchronous/asynchronous calling
|
284
|
+
- string transportation
|
285
|
+
- array/list serialization and transportation
|
286
|
+
- hash/alist serialization and transportation
|
287
|
+
|
288
|
+
Here is the result on Lenovo X240 with Intel Core i7-4600U CPU 2.10GHz, 8GB RAM, ruby 2.1.4p265 x86_64-linux.
|
289
|
+
|
290
|
+
```
|
291
|
+
$ bundle exec ruby test/echo-bench.rb
|
292
|
+
user system total real
|
293
|
+
int 0.180000 0.040000 0.220000 ( 0.402582)
|
294
|
+
int_a 0.180000 0.010000 0.190000 ( 0.205672)
|
295
|
+
string 0.390000 0.040000 0.430000 ( 0.753418)
|
296
|
+
array 1.410000 0.070000 1.480000 ( 2.687667)
|
297
|
+
hash 3.930000 0.070000 4.000000 ( 7.632865)
|
298
|
+
(call/sec) int: 9090.9 int_async: 10526.3 string: 4651.2 array: 1351.4 hash: 500.0
|
299
|
+
```
|
127
300
|
|
128
|
-
|
301
|
+
In the condition Ruby to Ruby, `elrpc` can perform around 10000 call/sec.
|
129
302
|
|
130
303
|
## License
|
131
304
|
|
data/elrpc.gemspec
CHANGED
@@ -20,6 +20,6 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_runtime_dependency "elparser", "~> 0.0"
|
22
22
|
spec.add_development_dependency "bundler", "~> 1.7"
|
23
|
-
spec.add_development_dependency "rake", "~> 10
|
24
|
-
spec.add_development_dependency "test-unit", "~> 3
|
23
|
+
spec.add_development_dependency "rake", "~> 10"
|
24
|
+
spec.add_development_dependency "test-unit", "~> 3"
|
25
25
|
end
|
data/lib/elrpc/version.rb
CHANGED
data/lib/elrpc.rb
CHANGED
@@ -5,6 +5,7 @@ require "socket"
|
|
5
5
|
require "thread"
|
6
6
|
require "monitor"
|
7
7
|
require "logger"
|
8
|
+
require "open3"
|
8
9
|
|
9
10
|
require "elparser"
|
10
11
|
|
@@ -72,17 +73,24 @@ module Elrpc
|
|
72
73
|
|
73
74
|
def _start_logger
|
74
75
|
return Thread.start do
|
76
|
+
stdout = @io3[1]
|
77
|
+
stderr = @io3[2]
|
75
78
|
loop do
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
+
IO.select([stdout, stderr]).flatten.compact.each do |io|
|
80
|
+
io.each do |line|
|
81
|
+
next if line.nil? || line.empty?
|
82
|
+
@logger.puts(line) if @logger
|
83
|
+
end
|
84
|
+
end
|
85
|
+
break if stdout.eof? && stderr.eof?
|
79
86
|
end
|
80
87
|
end
|
81
88
|
end
|
82
89
|
|
83
90
|
def start
|
84
|
-
@
|
85
|
-
@port = @
|
91
|
+
@io3 = Open3.popen3(@cmd) # stdin, stdout, stderr, wait_thr
|
92
|
+
@port = @io3[2].readline.to_i
|
93
|
+
@io3[0].close
|
86
94
|
@output = nil
|
87
95
|
@thread = _start_logger
|
88
96
|
@client = Elrpc.start_client(@port)
|
@@ -115,6 +123,8 @@ module Elrpc
|
|
115
123
|
|
116
124
|
def stop
|
117
125
|
@client.stop
|
126
|
+
@io3[1].close
|
127
|
+
@io3[2].close
|
118
128
|
end
|
119
129
|
|
120
130
|
end
|
@@ -149,6 +159,9 @@ module Elrpc
|
|
149
159
|
def message
|
150
160
|
"#{@_classname} : #{@_message}"
|
151
161
|
end
|
162
|
+
def to_s
|
163
|
+
message
|
164
|
+
end
|
152
165
|
|
153
166
|
def remote_classname
|
154
167
|
@_classname
|
@@ -159,6 +172,7 @@ module Elrpc
|
|
159
172
|
def remote_backtrace
|
160
173
|
@_backtrace
|
161
174
|
end
|
175
|
+
|
162
176
|
end
|
163
177
|
|
164
178
|
class EPCStackError < StandardError
|
@@ -171,6 +185,9 @@ module Elrpc
|
|
171
185
|
def message
|
172
186
|
"#{@_classname} : #{@_message}"
|
173
187
|
end
|
188
|
+
def to_s
|
189
|
+
message
|
190
|
+
end
|
174
191
|
|
175
192
|
def remote_classname
|
176
193
|
@_classname
|
data/sample/echo-client.el
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
(require 'epc)
|
2
2
|
|
3
3
|
(let (epc)
|
4
|
-
;; start a
|
4
|
+
;; start a server process
|
5
5
|
;; bundle exec
|
6
6
|
(setq epc (epc:start-epc "bundle" '("exec" "ruby" "echo.rb")))
|
7
7
|
|
@@ -21,7 +21,7 @@
|
|
21
21
|
;; synchronous calling (debug purpose)
|
22
22
|
(message "%S" (epc:call-sync epc 'echo '(world)))
|
23
23
|
|
24
|
-
;;
|
24
|
+
;; kill the server process
|
25
25
|
(epc:stop-epc epc)
|
26
26
|
)
|
27
27
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: elrpc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- SAKURAI Masashi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-12-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: elparser
|
@@ -44,28 +44,28 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '10
|
47
|
+
version: '10'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '10
|
54
|
+
version: '10'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: test-unit
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '3
|
61
|
+
version: '3'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '3
|
68
|
+
version: '3'
|
69
69
|
description: EPC (RPC stack for the Emacs Lisp) for Ruby.
|
70
70
|
email:
|
71
71
|
- m.sakurai@kiwanami.net
|
@@ -112,7 +112,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
112
112
|
version: '0'
|
113
113
|
requirements: []
|
114
114
|
rubyforge_project:
|
115
|
-
rubygems_version: 2.4.
|
115
|
+
rubygems_version: 2.4.8
|
116
116
|
signing_key:
|
117
117
|
specification_version: 4
|
118
118
|
summary: EPC (RPC stack for the Emacs Lisp) for Ruby.
|