bellite 0.0.1

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,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bellite.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Ivan
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Bellite
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'bellite'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install bellite
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/bellite.gemspec ADDED
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/bellite/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Ivan"]
6
+ gem.email = ["asmer@asmer.org.ua"]
7
+ gem.description = "Bellite JSON-RPC Client library"
8
+ gem.summary = "Implements connection to JSON-RPC server, calling remote methods and bindings to server events"
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "bellite"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Bellite::VERSION
17
+ end
@@ -0,0 +1,3 @@
1
+ module Bellite
2
+ VERSION = "0.0.1"
3
+ end
data/lib/bellite.rb ADDED
@@ -0,0 +1,729 @@
1
+ #!/usr/bin/ruby
2
+ require 'json'
3
+ require 'socket'
4
+ include Socket::Constants
5
+
6
+
7
+ # Clone of {http://docs.python.org/2/library/functools.html#functools.partial Python functools.partial}
8
+ # Creates a lambda with passed func and applies to it args. Args can be overriden when this lambda called.
9
+ #
10
+ #@param [Proc] func - func to be called with partial parameters
11
+ #@param [Array] *args - default parameters which should be passed to func
12
+ #@return [Proc] wrapper around func which passes *args or new args, passed to wrapper to func
13
+ def partial(func, *args)
14
+ return Proc.new do |*new_args|
15
+ merged_params = args.clone()
16
+ new_args.each_with_index {|v,k|
17
+ merged_params[k] = new_args[k]
18
+ }
19
+ func.call(*merged_params)
20
+ end
21
+ end
22
+
23
+
24
+ # Simple implementation of {http://docs.python.org/2/library/asyncore.html Python Asyncore}
25
+ class Async
26
+ #!attribute @@map
27
+ # List of objects, implements asyncore methods
28
+ @@map = []
29
+
30
+ #Checks sockets from map objects using {IO.select}
31
+ #@param [Float] timeout timeout in seconds, passed to {IO.select}
32
+ #@param [Hash] map list of objects implements asyncore methods
33
+ def Async.check(timeout=false, map=false)
34
+ if not map
35
+ map = @@map
36
+ end
37
+ readable = []
38
+ writable = []
39
+ excepted = []
40
+
41
+ changedCount = 0
42
+ map.each do |obj|
43
+ if obj.writable?
44
+ if obj.fileno
45
+ writable << obj.fileno
46
+ end
47
+ end
48
+
49
+ if obj.readable?
50
+ if obj.fileno
51
+ readable << obj.fileno
52
+ end
53
+ end
54
+
55
+ if obj.exceptable?
56
+ if obj.fileno
57
+ excepted << obj.fileno
58
+ end
59
+ end
60
+ end
61
+
62
+ if readable.size == 0 and writable.size == 0 and excepted.size == 0
63
+ return false
64
+ end
65
+
66
+ if (timeout)
67
+ r,w,e = IO.select(readable, writable, excepted,timeout)
68
+ else
69
+ r,w,e = IO.select(readable, writable, excepted)
70
+ end
71
+ map.each do |obj|
72
+ if obj.writable? and w.include? obj.fileno
73
+ obj.handle_write_event
74
+ end
75
+ if obj.readable? and r.include? obj.fileno
76
+ obj.handle_read_event
77
+ end
78
+ if obj.exceptable? and e.include? obj.fileno
79
+ obj.handle_expt_event
80
+ end
81
+ end
82
+
83
+ return r.size + w.size + e.size
84
+ end
85
+
86
+ #Running check while at least one socket in objects map is connected
87
+ #@param [Float] timeout - timeout, passed to {.check}
88
+ #@param [Hash] map, passed to {.check}
89
+ def Async.loop(timeout,map=false)
90
+ while Async.check(timeout, map) != false
91
+ end
92
+ end
93
+ end
94
+
95
+
96
+
97
+ # Implements new pythonic {http://docs.python.org/2/library/stdtypes.html#dict.setdefault setdefault} method in ruby class Hash
98
+ class Hash
99
+ #(see {Hash})
100
+ #@param key key of hash
101
+ #@param value value of key in hash
102
+ #@return [Value] if key in hash - hash value, attached to key; otherwise value, passed to setdefault
103
+ def setdefault(key, value)
104
+ if self[key] == nil
105
+ self[key] = value
106
+ end
107
+ return self[key]
108
+ end
109
+ end
110
+
111
+
112
+ # @abstract This is common interface of Bellite json-rpc API
113
+ class BelliteJsonRpcApi
114
+ #Constructor. Connects to server with cred credentials
115
+ #@param [String] cred Credentials in format: 'host:port/token';
116
+ def initialize(cred)
117
+ cred = findCredentials(cred)
118
+ if cred
119
+ _connect(cred)
120
+ end
121
+ end
122
+
123
+ #Authenticates with server using token
124
+ #@param [String] token Token to access server
125
+ #@return {Promise}
126
+ def auth(token)
127
+ return _invoke('auth', [token])
128
+ end
129
+
130
+ #Server version
131
+ #@return {Promise}
132
+ def version
133
+ return _invoke('version')
134
+ end
135
+
136
+ #Ping server
137
+ #@return {Promise}
138
+ def ping
139
+ return _invoke('ping')
140
+ end
141
+
142
+ #invokes respondsTo
143
+ #@param [Fixnum] selfId Id for internal use (events, for example)
144
+ #@param [Hash] cmd - some data, which can be encoded as JSON to pass to server
145
+ #@return {Promise}
146
+ def respondsTo(selfId, cmd)
147
+ if not selfId
148
+ selfId = 0
149
+ end
150
+ return _invoke('respondsTo', [selfId, cmd])
151
+ end
152
+
153
+ #perform JSON-RPC request
154
+ #@param [String] cmd command
155
+ #@param [Array] *args Variable-length arguments, passed with cmd. :key => value ruby Hash parameter sugar also works
156
+ #@return [Promise]
157
+ def perform(selfId, cmd, *args)
158
+ if args.size > 1
159
+ args.each do |arg|
160
+ if arg.instance_of(Hash)
161
+ raise ArgumentError, "Cannot specify both positional and keyword arguments"
162
+ end
163
+ end
164
+ end
165
+ if args.size == 1 and (args[0].instance_of? Hash or args[0].instance_of? Array)
166
+ args = args[0]
167
+ end
168
+ if args == []
169
+ args = nil
170
+ end
171
+ if not selfId
172
+ selfId = 0
173
+ end
174
+ return _invoke('perform',[selfId, cmd, args])
175
+ end
176
+
177
+ # binds Event on some evtType
178
+ #@param [Fixnum] selfId internal id
179
+ #@param [String] evtType type of event
180
+ #@param [Fixnum] res
181
+ #@param [Hash] ctx - context, passed to event handler
182
+ #@return [Promise]
183
+ def bindEvent(selfId=0, evtType='*', res = -1, ctx=false)
184
+ if not selfId
185
+ selfId = 0
186
+ end
187
+ return _invoke('bindEvent',[selfId, evtType, res, ctx])
188
+ end
189
+
190
+ # Unbins Event on some evtType
191
+ #@param [Fixnum] selfId internal id
192
+ #@param [String] evtType type of event
193
+ #@return [Promise]
194
+ def unbindEvent(selfId, evtType=false)
195
+ if not selfId
196
+ selfId = 0
197
+ end
198
+ return _invoke('unbindEvent',[selfId, evtType])
199
+ end
200
+
201
+
202
+ # Finds credentials in environment variable BELLITE_SERVER or in passed parameter
203
+ #@param [String] cred server credentials in format host:port/token
204
+ #@return [Hash] with credentials or false if failed
205
+ def findCredentials(cred=false)
206
+ if not cred
207
+ cred = ENV['BELLITE_SERVER']
208
+ if not cred
209
+ cred = '127.0.0.1:3099/bellite-demo-host';
210
+ $stderr.puts 'BELLITE_SERVER environment variable not found, using "'+cred+'"'
211
+ end
212
+ elsif not cred.instance_of String
213
+ return cred
214
+ end
215
+
216
+ begin
217
+ host, token = cred.split('/', 2)
218
+ host, port = host.split(':', 2)
219
+ return ({"credentials" => cred, "token" => token, "host" => host, "port" => Integer(port)})
220
+ rescue
221
+ return false
222
+ end
223
+ end
224
+
225
+ #@abstract Connecting to JSON-RPC server
226
+ #@param [String] host Host
227
+ #@param [Fixnum] port Port
228
+ def _connect(host, port)
229
+ raise NotImplementedError, "Subclass Responsibility"
230
+ end
231
+
232
+ #@abstract Invokes method
233
+ #@param [String] method
234
+ #@param [Hash] params Something JSONable to pass as ethod params
235
+ def _invoke(method, params=nil)
236
+ raise NotImplementedError, "Subclass Responsibility"
237
+ end
238
+ end
239
+
240
+
241
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
242
+ #
243
+
244
+
245
+ #@abstract Next level of Bellite server API implementation: Basic operations with server like Bellite.
246
+ class BelliteJsonRpc < BelliteJsonRpcApi
247
+ #constructor.
248
+ #@param [String] cred Server credentials
249
+ #@param [Boolean] logging Logging flag
250
+ def initialize(cred=false, logging=false)
251
+ @_resultMap = {}
252
+ @_evtTypeMap = {}
253
+ @_nextMsgId = 100
254
+ super (cred)
255
+ @logging = logging
256
+ end
257
+
258
+ #Notify JSON-RPC server by method call, but skip response
259
+ #@param [String] method Method to call
260
+ #@param [Hash,Array,String,Float,Fixnum] params parameters for method
261
+ #@return [Boolean] true if sent
262
+ def _notify(method, params=nil)
263
+ return _sendJsonRpc(method, params)
264
+ end
265
+
266
+ #Calls JSON-RPC method
267
+ #@param [String] method Method to call
268
+ #@param [Hash,Array,String,Float,Fixnum] params parameters for method
269
+ #@return [Promise]
270
+ def _invoke(method, params=nil)
271
+ msgId = @_nextMsgId
272
+ @_nextMsgId += 1
273
+ res = _newResult(msgId)
274
+ _sendJsonRpc(method, params, msgId)
275
+ return res.promise
276
+ end
277
+
278
+ #returns new Promise object, attached to msgId
279
+ #@param [Fixnum] msgId id of message for this result
280
+ #@return [Promise]
281
+ def _newResult(msgId)
282
+ res = deferred()
283
+ @_resultMap[msgId] = res
284
+ return res
285
+ end
286
+
287
+ #Sends JSON-RPC call to server
288
+ #@param [String] method Method to call
289
+ #@param [Hash,Array,String,Float,Fixnum] params parameters for method
290
+ #@param [Fixnum] msgId Message Id. If default, it uses internal autoincrement counter
291
+ #@return [Boolean] True if sent
292
+ def _sendJsonRpc(method, params=nil, msgId=false)
293
+ msg = {"jsonrpc" => "2.0", "method" => method}
294
+ if params
295
+ msg['params'] = params
296
+ end
297
+ if msgId
298
+ msg['id'] = msgId
299
+ end
300
+ logSend(msg)
301
+ return _sendMessage(JSON.fast_generate(msg))
302
+ end
303
+
304
+ #Sends JSON-RPC string to server
305
+ #@param [String] msg JSON-encoded JSON-RPC call to send
306
+ #@return [Boolean] True if sent
307
+ def _sendMessage(msg)
308
+ raise NotImplementedError('Subclass Responsibility')
309
+ end
310
+
311
+ #Puts send packets to STDOUT
312
+ #@param [String] msg Same as for _sendMessage
313
+ def logSend(msg)
314
+ puts "send ==> " + JSON.fast_generate(msg)
315
+ end
316
+
317
+ #Puts received packets to STDOUT
318
+ #@param [String] msg Same as for _sendMessage
319
+ def logRecv(msg)
320
+ puts "recv ==> " + JSON.fast_generate(msg)
321
+ end
322
+
323
+ #Receives JSON-RPC response or call from JSON-RPC Server
324
+ #@param [Array<String>] msgList Array of messages from Server
325
+ def _recvJsonRpc(msgList)
326
+ msgList.each do |msg|
327
+ begin
328
+ msg = JSON.parse(msg)
329
+ isCall = msg.has_key?("method")
330
+ rescue
331
+ next
332
+ end
333
+ logRecv(msg)
334
+ begin
335
+ if isCall
336
+ on_rpc_call(msg)
337
+ else
338
+ on_rpc_response(msg)
339
+ end
340
+ end
341
+ end
342
+ end
343
+
344
+ #Called on JSON-RPC call FROM server.
345
+ #@param [String] msg Server response with call
346
+ def on_rpc_call(msg)
347
+ if msg['method'] == 'event'
348
+ args = msg['params']
349
+ emit(args['evtType'], args)
350
+ end
351
+ end
352
+
353
+ #Called on JSON-RPC response (not call) from Server
354
+ #@param [String] msg Server respon with data
355
+ def on_rpc_response(msg)
356
+ tgt = @_resultMap.delete msg['id']
357
+ if tgt == nil
358
+ return
359
+ end
360
+
361
+ if msg.has_key?('error')
362
+ tgt.reject.call(msg['error'])
363
+ else
364
+ tgt.resolve.call(msg['result'])
365
+ end
366
+ end
367
+
368
+ #Called on connect to remote JSON-RPC server.
369
+ #@param [Hash] cred Server credentials: port, host, token and original `credentials` string
370
+ def on_connect(cred)
371
+ auth(cred['token'])._then.call(method(:on_auth_succeeded), method(:on_auth_failed))
372
+ end
373
+
374
+ #Called when auth is successfully ended
375
+ # Emits 'auth' and 'ready' event handlers
376
+ #@param [String] msg Message from JSON-RPC server
377
+ def on_auth_succeeded(msg)
378
+ emit('auth', true, msg)
379
+ emit('ready')
380
+ end
381
+
382
+ #Called when auth is failed
383
+ # Emits 'auth' event handlers with fail flag
384
+ #@param [String] msg Message from JSON-RPC server
385
+ def on_auth_failed(msg)
386
+ emit('auth', false, msg)
387
+ end
388
+
389
+
390
+ #~ micro event implementation ~~~~~~~~~~~~~~~~~~~~~~~
391
+ #
392
+
393
+ #Adds ready event handler
394
+ #@param [Proc] fnReady Event hanlder lambda
395
+ #@return [Proc] Your event handler
396
+ def ready(fnReady)
397
+ return on('ready', fnReady)
398
+ end
399
+
400
+ #Adds any event handler
401
+ #@param [String] key Event name like `ready`
402
+ #@param [Proc] fn Function to bind on event
403
+ #@return [Proc] Your event handler or bindEvent method to bind your handler later if you skip fn
404
+ def on(key, fn=false)
405
+ bindEvent = lambda do |fn|
406
+ @_evtTypeMap.setdefault(key, []) << fn
407
+ return fn
408
+ end
409
+ if not fn
410
+ return bindEvent
411
+ else
412
+ return bindEvent.call(fn)
413
+ end
414
+ end
415
+
416
+ #Calls event handler when event occured
417
+ #@param [String] key Event name like `ready`
418
+ #@param [Hash,Array,Float,String,Fixnum] *args Argument to pass to event handler(s).
419
+ def emit(key, *args)
420
+ if @_evtTypeMap.has_key? key
421
+ @_evtTypeMap[key].each do |fn|
422
+ begin
423
+ fn.call(self, *args)
424
+ rescue
425
+ puts "EMIT exception"
426
+ end
427
+ end
428
+ end
429
+ end
430
+ end
431
+
432
+
433
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
434
+ #
435
+
436
+
437
+ #Main Bellite class to use
438
+ class Bellite < BelliteJsonRpc
439
+
440
+
441
+ #!attribute [rw] timeout
442
+ # Timeout of IO.select socket wait
443
+ #@return [Float] Timeout value in seconds
444
+ attr_accessor :timeout
445
+
446
+ #Connecting to JSON-RPC server
447
+ # Calls on_connect if connection successfull
448
+ #@param [Hash] cred Server credentials: port, host, token and original `credentials` string
449
+ def _connect(cred)
450
+ @timeout = 0.5
451
+ @conn = TCPSocket.new cred['host'], cred['port']
452
+ @buf = ""
453
+
454
+ if @conn
455
+ on_connect(cred)
456
+ end
457
+ end
458
+
459
+ #Obtains responses from JSON-RPC server
460
+ #@param [Float] timeout Timeout, if false, @_timeout used
461
+ def loop(timeout=0.5)
462
+ if timeout == false
463
+ timeout = @timeout
464
+ end
465
+ Async.loop(timeout, [self])
466
+ end
467
+
468
+ #Sends message to server
469
+ #@param [String] msg JSON-encoded JSON-RPC call to send
470
+ #@return [Boolean] True if sent
471
+ def _sendMessage(msg)
472
+ if not isConnected?
473
+ return false
474
+ end
475
+
476
+ @conn.puts(msg + "\0")
477
+ return true
478
+ end
479
+
480
+ #Checks, is connection still alive
481
+ #@return [Boolean] True if connection alive
482
+ def isConnected?
483
+ return @conn != false
484
+ end
485
+
486
+ #Closing connection to JSON-RPC Server
487
+ def close()
488
+ @conn.close
489
+ @conn = false
490
+ return true
491
+ end
492
+
493
+ #Returns TCPSocket connection for {Async}
494
+ #@return [TCPSocket]
495
+ def fileno()
496
+ return @conn
497
+ end
498
+
499
+ #Returns is this object readable for {Async}
500
+ #@return [Boolean]
501
+ def readable?
502
+ return true
503
+ end
504
+
505
+ #Returns is this object can read out-of-band data for {Async}
506
+ #@return [Boolean]
507
+ def exceptable?
508
+ return false
509
+ end
510
+
511
+ #Reads new data and runs {#_recvJsonRpc}
512
+ # This method called by {Async.loop}
513
+ #@return [Boolean]
514
+ def handle_read_event()
515
+ if not isConnected?
516
+ return false
517
+ end
518
+
519
+ buf = @buf
520
+ begin
521
+ while true
522
+ begin
523
+ part = @conn.recv_nonblock(4096)
524
+ rescue IO::WaitReadable
525
+ break
526
+ end
527
+ if not part
528
+ close()
529
+ break
530
+ elsif part == ""
531
+ break
532
+ else
533
+ buf += part
534
+ end
535
+ end
536
+ rescue
537
+ end
538
+
539
+
540
+ buf = buf.split("\0")
541
+ _recvJsonRpc(buf)
542
+ end
543
+
544
+ #Returns is this object can write data to socket
545
+ # This method called by {Async}
546
+ #@return [Boolean]
547
+ def writable?()
548
+ return false
549
+ end
550
+
551
+ #Writes new data to socket
552
+ # Stub for {Async}
553
+ def handle_write_event()
554
+ end
555
+
556
+ #Handles out-of-band data
557
+ # This method can be called by {Async}. Closes connection if called
558
+ def handle_expt_event()
559
+ close()
560
+ end
561
+
562
+ #Handles socket closing
563
+ # This method can be called by {Async}
564
+ def handle_close()
565
+ close()
566
+ end
567
+
568
+ #Handles socket error
569
+ # This method can be called by {Async}. Closing connection
570
+ def handle_error()
571
+ close()
572
+ end
573
+ end
574
+
575
+ #@abstract Promise API
576
+ class PromiseApi
577
+ #Runs then-function call with same function for success and failure
578
+ #@param [Proc] fn Function to handle success or failure
579
+ #@return Result of then-function call
580
+ def always(fn)
581
+ return @_then.call(fn, fn)
582
+ end
583
+
584
+ #Runs then-function in case of failure
585
+ #@param [Proc] failure Failure handler
586
+ #@return Result of then-function call
587
+ def fail(failure)
588
+ return @_then.call(false, failure)
589
+ end
590
+
591
+ #Runs then-function in case of success
592
+ #@param [Proc] success Success handler
593
+ #@return Result of then-function call
594
+ def done(success)
595
+ return @_then.call(success,false)
596
+ end
597
+ end
598
+
599
+ #Class implements promise/future promise
600
+ class Promise < PromiseApi
601
+ #Constructing object with then-function
602
+ #@param [Proc] _then Then-function
603
+ def initialize(_then)
604
+ if _then
605
+ @_then = _then
606
+ end
607
+ end
608
+
609
+ #Returns this object
610
+ #@return [Promise]
611
+ def promise
612
+ return self
613
+ end
614
+
615
+ #@!attribute [r] _then
616
+ # Then-function
617
+ #@return [Proc,lambda]
618
+ def _then
619
+ @_then
620
+ end
621
+ end
622
+
623
+ #Implements Future
624
+ class Future < PromiseApi
625
+ #Constructing object with then, success and failure functions
626
+ #@param [Proc,lambda] _then Then-function
627
+ #@param [Proc,lambda] resolve Success-function
628
+ #@param [Proc,lambda] reject Failure-function
629
+ def initialize(_then, resolve=false, reject=false)
630
+ @promise = Promise.new _then
631
+ if resolve
632
+ @resolve = resolve
633
+ end
634
+ if reject
635
+ @reject = reject
636
+ end
637
+ end
638
+
639
+
640
+ #@!attribute [r] resolve
641
+ # Success-function
642
+ #@return [Proc,lambda]
643
+ def resolve
644
+ @resolve
645
+ end
646
+
647
+ #@!attribute [r] reject
648
+ # Failure-function
649
+ #@return [Proc,lambda]
650
+ def reject
651
+ @reject
652
+ end
653
+
654
+ #@!attribute [r] promise
655
+ # Promise object of this Future
656
+ #@return [Promise]
657
+ def promise
658
+ @promise
659
+ end
660
+ end
661
+
662
+ #Creates Future object for JSON-RPC Server response
663
+ #@return [Future]
664
+ def deferred()
665
+ cb = []
666
+ answer = false
667
+
668
+ future = false
669
+ reject = false
670
+
671
+ _then = lambda do |*args|
672
+ success = args[0] || false
673
+ failure = args[1] || false
674
+ cb << [success,failure]
675
+ if answer
676
+ answer.call
677
+ end
678
+ return future.promise
679
+ end
680
+
681
+ resolve = lambda do |result|
682
+ while cb.size > 0
683
+ success, failure = cb.pop()
684
+ begin
685
+ if success != false
686
+ res = success.call(result)
687
+ if res != false
688
+ result = res
689
+ end
690
+ end
691
+ rescue Exception => err
692
+ if failure != false
693
+ res = failure.call(err)
694
+ elsif cb.size = 0
695
+ #excepthook
696
+ end
697
+ if res == false
698
+ return reject.call(err)
699
+ else
700
+ return reject.call(res)
701
+ end
702
+ end
703
+ end
704
+ answer = partial(resolve, result)
705
+ end
706
+
707
+ reject = lambda do |error|
708
+ while cb.size > 0
709
+ failure = cb.pop()[1]
710
+ begin
711
+ if failure != false
712
+ res = failure.call(error)
713
+ if res != false
714
+ error = res
715
+ end
716
+ end
717
+ rescue Exception => err
718
+ res = err
719
+ if cb.size == 0
720
+ #excepthook
721
+ end
722
+ end
723
+ end
724
+ answer = partial(reject, error)
725
+ end
726
+
727
+ future = Future.new _then, resolve, reject
728
+ return future
729
+ end
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require '../bellite.rb'
4
+ app = Bellite.new
5
+ app.ready Proc.new {
6
+ puts "READY"
7
+ app.ping
8
+ app.version
9
+ app.perform(142, "echo", {"name" => [nil, true, 42, "value"]})
10
+
11
+ app.bindEvent(118, "*")
12
+ app.unbindEvent(118, "*")
13
+
14
+ app.on("testEvent", lambda { |app, eobj|
15
+ puts "TEST EVENT"
16
+ puts eobj
17
+ if eobj['evt']
18
+ app.perform(0, eobj['evt'])
19
+ else
20
+ app.close
21
+ end
22
+ })
23
+
24
+ app.bindEvent(0, "testEvent", 42, {'testCtx' => true})
25
+ app.perform(0, "testEvent")
26
+ }
27
+
28
+ app.timeout = false
29
+ app.loop false
@@ -0,0 +1,264 @@
1
+ /*-*- coding: utf-8 -*- vim: set ts=4 sw=4 expandtab
2
+ ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
3
+ ##~ Copyright (C) 2002-2012 Bellite.io ##
4
+ ##~ ##
5
+ ##~ This library is free software; you can redistribute it ##
6
+ ##~ and/or modify it under the terms of the MIT style License as ##
7
+ ##~ found in the LICENSE file included with this distribution. ##
8
+ ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##*/
9
+
10
+ "use strict";
11
+ function createMockBelliteServer(ns) {
12
+ var self = {
13
+ server: require('net').createServer(),
14
+ token: ns.token || require('crypto').randomBytes(8).toString('hex'),
15
+ shutdownTest: function() {
16
+ self.shutdownTest = null;
17
+ if (self.server)
18
+ self.server.close();
19
+ self.allConnections.forEach(function(conn){
20
+ if (conn) conn.end() }) },
21
+ allConnections: [] }
22
+
23
+ var debugLog = ns.debugLog
24
+
25
+ self.server.on('listening', function () {
26
+ var addr = this.address()
27
+ self.env = addr.address+':'+addr.port+'/'+self.token
28
+ process.env.BELLITE_SERVER = self.env
29
+ if (debugLog) debugLog('Serving BELLITE_SERVER="'+ process.env.BELLITE_SERVER +'"')
30
+ ns.listening();
31
+ })
32
+
33
+ self.server.on('connection', function (conn) {
34
+ self.allConnections.push(conn)
35
+ conn.setEncoding("UTF-8");
36
+ conn.setNoDelay(true);
37
+ conn.setKeepAlive(true, 0);
38
+
39
+ var api = {
40
+ sendMessage: function(msg) {
41
+ if (debugLog) debugLog('reply ==> ', msg);
42
+ return conn.write(msg+'\0') },
43
+ shutdown: function() { return conn.end() },
44
+ fireEvent: function(evtType, selfId, evt, ctx) {
45
+ return this.send('event', {evtType: evtType, selfId: selfId||0, evt:evt, ctx:ctx}) },
46
+ send: function(method, params, id) {
47
+ var msg = {jsonrpc: "2.0", id:id, method:method, params:params}
48
+ return this.sendMessage(JSON.stringify(msg)) },
49
+ answer: function(result, id) {
50
+ if (id===undefined) return false;
51
+ var msg = {jsonrpc: "2.0", id:id, result:result}
52
+ return this.sendMessage(JSON.stringify(msg)) },
53
+ error: function(error, id) {
54
+ if (id===undefined) return false;
55
+ var msg = {jsonrpc: "2.0", id:id, error:error}
56
+ return this.sendMessage(JSON.stringify(msg)) },
57
+ checkAuth: function(token) {
58
+ clearTimeout(authTimeout);
59
+ return this.authorized = (token == self.token) }
60
+ },
61
+ tgt = ns.connect(api),
62
+ authTimeout = setTimeout(api.shutdown, 250);
63
+
64
+ var connBuf='';
65
+ conn.on('data', function(data) {
66
+ data = (connBuf+data).split('\0')
67
+ connBuf = data.pop()
68
+ while (data.length) {
69
+ var msg = data.shift();
70
+ if (debugLog) debugLog('invoke <== ', msg);
71
+ try { msg = JSON.parse(msg) }
72
+ catch (err) { tgt.parse_error(err, msg); continue }
73
+ if (msg.method!==undefined)
74
+ tgt.invoke(msg)
75
+ else tgt.response(msg)
76
+ } })
77
+ conn.on('close', function() { tgt.conn_close() })
78
+ conn.on('error', function(err) { tgt.conn_error(err) })
79
+ })
80
+
81
+ self.server.on('close', function() {
82
+ self.server = null; ns.server_close() })
83
+ self.server.on('error', function(err) {
84
+ ns.server_error(err) })
85
+
86
+ self.server.listen(ns.port || 0, '127.0.0.1')
87
+ return self;
88
+ }
89
+ exports.createMockBelliteServer = createMockBelliteServer;
90
+
91
+ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
92
+
93
+ function testBelliteJSONRPC(opt, doneCallback) {
94
+ opt.log = opt.log || {};
95
+ function log(key, args) {
96
+ var log=opt.log[key];
97
+ if (!log) opt.log[key] = log = []
98
+ var ea = Array.prototype.slice.call(arguments, 1);
99
+ if (ea.length<2) ea = ea[0]===undefined ? 0 : ea[0];
100
+ log.push(ea)
101
+ return this }
102
+
103
+ var rpc = {
104
+ connect: function(api) {
105
+ log('connect')
106
+ this.api = api;
107
+ this.evtCtx = {}
108
+ return this },
109
+ conn_close: function() { log('conn_close') },
110
+ conn_error: function(err) { log('conn_error', err) },
111
+ parse_error: function(err,msg) { log('parse_error', err, msg) },
112
+ response: function(msg) { log('response', msg) },
113
+ invoke: function(msg) {
114
+ var pre = this.api.authorized ? 'meth_' : 'meth0_';
115
+ var meth = this[pre+msg.method] || this.meth_unknown;
116
+ log('invoke', pre+msg.method, msg.params)
117
+ return meth.call(this, msg.params, msg)},
118
+
119
+ meth_unknown: function(args, msg) {
120
+ log('meth_unknown', msg.method, args)
121
+ this.api.answer(["unknown method ", null], msg.id) },
122
+ meth_ping: function(args, msg) {
123
+ log('meth_ping', args)
124
+ this.api.answer([null, true, "pong"], msg.id) },
125
+ meth_version: function(args, msg) {
126
+ log('meth_version', args)
127
+ this.api.answer([null, {"server":"bellite", "version":"1.4.3", "platform":"node/test"}], msg.id) },
128
+
129
+ meth0_ping: function(args, msg) { this.meth_ping(args, msg) },
130
+ meth0_version: function(args, msg) { this.meth_version(args, msg) },
131
+ meth0_auth: function(args, msg) {
132
+ log('meth_auth', args)
133
+ if (this.api.checkAuth(args[0])) {
134
+ log('authorize', true)
135
+ this.api.answer([null, true, "authorized"], msg.id)
136
+ } else {
137
+ log('authorize', false)
138
+ this.api.error({code:401, message:"Unauthorized"}, msg.id)
139
+ this.api.shutdown()
140
+ } },
141
+
142
+ meth_bindEvent: function(args, msg) {
143
+ log('meth_bindEvent', args)
144
+ this.evtCtx[args[1]] = args[3]
145
+ this.api.answer([null, true], msg.id) },
146
+ meth_unbindEvent: function(args, msg) {
147
+ log('meth_unbindEvent', args)
148
+ this.api.answer([null, true], msg.id) },
149
+ meth_perform: function(args, msg) {
150
+ log('meth_perform', args)
151
+ var self=this, cmd=this['cmd_'+args[1]] || this.cmd_mock
152
+ cmd.call(this, args, function(res) {
153
+ self.api.answer([null, res], msg.id) }) },
154
+
155
+ cmd_mock: function(args, answer) {
156
+ answer([null, {'a mock':'result'}]) },
157
+ cmd_testEvent: function(args, answer) {
158
+ log('cmd_testEvent', args)
159
+ answer([null, true, 'firingTestEvent'])
160
+ var resp = 'dyn_'+require('crypto').randomBytes(4).toString('hex')
161
+ this['cmd_'+resp] = this.dynamic_response
162
+ this.api.fireEvent('testEvent', 0, resp, this.evtCtx.testEvent) },
163
+ dynamic_response: function(args, answer) {
164
+ log('dynamic_response', args)
165
+ answer([null, true, 'awesome'])
166
+ this.api.fireEvent('testEvent', 0, null, this.evtCtx.testEvent) }
167
+ };
168
+
169
+ var test = createMockBelliteServer({
170
+ port: opt.port, token: opt.token, debugLog: opt.debugLog,
171
+ listening: function() {
172
+ opt.execClient(spawnClient) },
173
+ server_close: function() {},
174
+ server_error: function(err) { log('server_error', err) },
175
+ connect: function(api) {
176
+ return Object.create(rpc).connect(api) }
177
+ })
178
+
179
+ function spawnClient(exec, args) {
180
+ var cp = require('child_process')
181
+ test.proc = cp.spawn(exec, args, {stdio:'inherit',cwd:__dirname})
182
+ test.proc.on('exit', function(code, signal) {
183
+ log('process_exit', code, signal)
184
+ test.proc = false;
185
+ done(code!==0 ? 'subprocess spawning error' : null) })
186
+ return test.proc }
187
+
188
+ function done(err) {
189
+ if (done.timer)
190
+ clearTimeout(done.timer)
191
+
192
+ if (test.proc) {
193
+ test.proc.kill()
194
+ test.proc = null
195
+ }
196
+ if (test.shutdownTest) {
197
+ test.shutdownTest()
198
+ setTimeout(function() { doneCallback(err, opt.log, opt) }, 100)
199
+ }}
200
+ if (opt.timeout!==false)
201
+ done.timer = setTimeout(function() {
202
+ log('timeout'); done('timeout') }, opt.timeout || 2000)
203
+ }
204
+ exports.testBelliteJSONRPC = testBelliteJSONRPC;
205
+
206
+ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
207
+
208
+ function assetTestResults(err, log, opt) {
209
+ var assert=require('assert');
210
+ try {
211
+ assert.equal(log.connect && log.connect.length, 1, "should connect exactly once")
212
+ assert.equal(log.server_error, null, "should not experience server errors")
213
+ assert.equal(log.conn_error, null, "should not experience connection errors")
214
+
215
+ assert.equal(log.parse_error, null, "should not experience JSON parsing errors")
216
+ assert.equal(log.response, null, "should not receive JSON-RPC 2.0 responses")
217
+
218
+ assert.equal(log.meth_auth && log.meth_auth.length, 1, "should call auth exactly once")
219
+ assert.deepEqual(log.authorize, [true], "should authorize successfully")
220
+
221
+ assert.equal(log.meth_unknown, null, "should never call an unknown method")
222
+
223
+ assert.deepEqual(log.meth_ping, [0], "should call ping once with no args")
224
+ assert.deepEqual(log.meth_version, [0], "should call version once with no args")
225
+ assert.equal(log.meth_perform && log.meth_perform.length, 3, "should call perform 3 times")
226
+ assert.equal(log.meth_bindEvent && log.meth_bindEvent.length, 2, "should call bindEvent 3 times")
227
+ assert.equal(log.meth_unbindEvent && log.meth_unbindEvent.length, 1, "should call unbindEvent 2 times")
228
+
229
+ assert.deepEqual(log.cmd_testEvent, [[0, 'testEvent', null]], "should call testEvent")
230
+ assert.equal(log.dynamic_response && log.dynamic_response.length, 1, "should call dynamic_response from event")
231
+
232
+ assert.equal(err, null, "terminated with an error")
233
+
234
+ console.log("All Bellite JSON-RPC protocol tests passed")
235
+ process.exit(0) // success
236
+
237
+ } catch(test_err) {
238
+ console.log('\nBellite Mock Server Call Log:')
239
+ console.log(log)
240
+ console.log('\n')
241
+
242
+ console.error(test_err.stack.split(/\n\s*/).slice(0,2).join('\n'))
243
+ console.error(test_err)
244
+ console.error()
245
+
246
+ process.exit(1) // failure
247
+ }
248
+ }
249
+ exports.assetTestResults = assetTestResults;
250
+
251
+ if (!module.parent) {
252
+ // test the bellist JSON-RPC interactions
253
+ testBelliteJSONRPC({
254
+ debugLog: console.log,
255
+ timeout: 2000,
256
+ //timeout: false,
257
+ //port: 3099,
258
+ //token: 'bellite-demo-host',
259
+
260
+ execClient: function(spawn) {
261
+ spawn('ruby', [__dirname+'/_doBelliteTest.rb'])
262
+ }
263
+ }, assetTestResults)
264
+ }
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*- vim: set ts=4 sw=4 expandtab
3
+ ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
4
+ ##~ Copyright (C) 2002-2012 Bellite.io ##
5
+ ##~ ##
6
+ ##~ This library is free software; you can redistribute it ##
7
+ ##~ and/or modify it under the terms of the MIT style License as ##
8
+ ##~ found in the LICENSE file included with this distribution. ##
9
+ ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
10
+
11
+ import unittest, os, sys
12
+
13
+ class TestJSONRPC(unittest.TestCase):
14
+ def testBelliteClient(self):
15
+ src = os.path.splitext(__file__)[0]+'.js'
16
+ assert 0==os.system('node '+src)
17
+
18
+
19
+ if __name__=='__main__':
20
+ unittest.main()
21
+
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bellite
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Ivan
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-12-01 00:00:00 Z
19
+ dependencies: []
20
+
21
+ description: Bellite JSON-RPC Client library
22
+ email:
23
+ - asmer@asmer.org.ua
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - .gitignore
32
+ - Gemfile
33
+ - LICENSE
34
+ - README.md
35
+ - Rakefile
36
+ - bellite.gemspec
37
+ - lib/bellite.rb
38
+ - lib/bellite/version.rb
39
+ - test/_doBelliteTest.rb
40
+ - test/testClientJSONRPC.js
41
+ - test/testClientJSONRPC.py
42
+ homepage: ""
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options: []
47
+
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ hash: 3
56
+ segments:
57
+ - 0
58
+ version: "0"
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ hash: 3
65
+ segments:
66
+ - 0
67
+ version: "0"
68
+ requirements: []
69
+
70
+ rubyforge_project:
71
+ rubygems_version: 1.8.24
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: Implements connection to JSON-RPC server, calling remote methods and bindings to server events
75
+ test_files:
76
+ - test/_doBelliteTest.rb
77
+ - test/testClientJSONRPC.js
78
+ - test/testClientJSONRPC.py
79
+ has_rdoc: