rrx 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +40 -0
- data/Rakefile +7 -0
- data/examples/all.rb +36 -0
- data/lib/Reactive/observable.rb +58 -0
- data/lib/Reactive/observable/base.rb +88 -0
- data/lib/Reactive/observable/composite.rb +34 -0
- data/lib/Reactive/observable/double_wrapper.rb +17 -0
- data/lib/Reactive/observable/empty.rb +7 -0
- data/lib/Reactive/observable/first.rb +26 -0
- data/lib/Reactive/observable/from_proc.rb +17 -0
- data/lib/Reactive/observable/generate.rb +41 -0
- data/lib/Reactive/observable/grep.rb +35 -0
- data/lib/Reactive/observable/map.rb +20 -0
- data/lib/Reactive/observable/merge.rb +26 -0
- data/lib/Reactive/observable/push.rb +47 -0
- data/lib/Reactive/observable/skip.rb +25 -0
- data/lib/Reactive/observable/wrapper.rb +10 -0
- data/lib/Reactive/observer.rb +36 -0
- data/lib/Reactive/scheduler.rb +23 -0
- data/lib/Reactive/version.rb +3 -0
- data/lib/rrx.rb +125 -0
- data/rrx.gemspec +25 -0
- data/spec/observable/count_spec.rb +30 -0
- data/spec/observable/first_spec.rb +47 -0
- data/spec/observable/interval_spec.rb +47 -0
- data/spec/observable/push_spec.rb +82 -0
- data/spec/observable/skip_spec.rb +30 -0
- data/spec/observable/timer_spec.rb +41 -0
- data/spec/spec_helper.rb +177 -0
- metadata +149 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Daniel Davis
|
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,40 @@
|
|
1
|
+
# RRX - Ruby Reactive Extensions
|
2
|
+
|
3
|
+
Translated (and in future: rubified) from https://github.com/eilara/Rx.pl
|
4
|
+
for more about Reactive Programming see http://channel9.msdn.com/Shows/Going+Deep/Expert-to-Expert-Brian-Beckman-and-Erik-Meijer-Inside-the-NET-Reactive-Framework-Rx
|
5
|
+
or http://msdn.microsoft.com/en-us/data/gg577609.aspx
|
6
|
+
|
7
|
+
|
8
|
+
TODO:
|
9
|
+
* translate tests
|
10
|
+
* make them pass
|
11
|
+
* documentation
|
12
|
+
* syntax sugar
|
13
|
+
* go over Array and Enumerable to see what additional methods should have a reactive counterpart
|
14
|
+
* prepare some kickass samples
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Add this line to your application's Gemfile:
|
19
|
+
|
20
|
+
gem 'rrx'
|
21
|
+
|
22
|
+
And then execute:
|
23
|
+
|
24
|
+
$ bundle
|
25
|
+
|
26
|
+
Or install it yourself as:
|
27
|
+
|
28
|
+
$ gem install rrx
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
TODO: Write usage instructions here
|
33
|
+
|
34
|
+
## Contributing
|
35
|
+
|
36
|
+
1. Fork it
|
37
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
38
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
39
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
40
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/examples/all.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rrx'
|
3
|
+
|
4
|
+
Reactive::Observable.
|
5
|
+
once(3).
|
6
|
+
merge(
|
7
|
+
Reactive::Observable.once(4)
|
8
|
+
).
|
9
|
+
subscribe(
|
10
|
+
:on_next => lambda {|v| puts "on_next = #{v}" },
|
11
|
+
:on_complete => lambda { puts "done" }
|
12
|
+
)
|
13
|
+
|
14
|
+
EventMachine.run do
|
15
|
+
|
16
|
+
Reactive::Observable.interval(100).
|
17
|
+
map {|x| x * 2 }.
|
18
|
+
grep(lambda {|x| x % 3 == 0 }).
|
19
|
+
first(10).
|
20
|
+
push( Reactive::Observable.once("Bye!") ).
|
21
|
+
subscribe(
|
22
|
+
:on_next => lambda {|v| puts "#{v}" },
|
23
|
+
:on_complete => lambda { puts "done ticking" }
|
24
|
+
)
|
25
|
+
|
26
|
+
|
27
|
+
Reactive::Observable.interval(1000).
|
28
|
+
merge(
|
29
|
+
Reactive::Observable.interval(500)
|
30
|
+
).
|
31
|
+
subscribe(
|
32
|
+
:on_next => lambda {|v| puts "tick #{v}" },
|
33
|
+
:on_complete => lambda { puts "done ticking"; EventMachine.stop_event_loop }
|
34
|
+
)
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Reactive
|
2
|
+
module Observable
|
3
|
+
autoload :Composite, 'reactive/observable/composite'
|
4
|
+
autoload :DoubleWrapper, 'reactive/observable/double_wrapper'
|
5
|
+
autoload :FromProc, 'reactive/observable/from_proc'
|
6
|
+
autoload :Generate, 'reactive/observable/generate'
|
7
|
+
autoload :Merge, 'reactive/observable/merge'
|
8
|
+
autoload :Wrapper, 'reactive/observable/wrapper'
|
9
|
+
autoload :Map, 'reactive/observable/map'
|
10
|
+
autoload :Grep, 'reactive/observable/grep'
|
11
|
+
autoload :First, 'reactive/observable/first'
|
12
|
+
autoload :Push, 'reactive/observable/push'
|
13
|
+
autoload :Skip, 'reactive/observable/skip'
|
14
|
+
autoload :Base, 'reactive/observable/base'
|
15
|
+
|
16
|
+
|
17
|
+
# creation
|
18
|
+
|
19
|
+
def self.once(value)
|
20
|
+
FromProc.new do |observer|
|
21
|
+
observer.on_next(value)
|
22
|
+
observer.on_complete
|
23
|
+
Disposable::Wrapper.new
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.range(from, size)
|
28
|
+
FromProc.new do |observer|
|
29
|
+
from.upto(from + size) {|i| observer.on_next(i) }
|
30
|
+
observer.on_complete
|
31
|
+
Disposable::Wrapper.new
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# from time
|
36
|
+
def self.interval(duration)
|
37
|
+
Generate.new(
|
38
|
+
0,
|
39
|
+
lambda {|x| 1 },
|
40
|
+
lambda {|x| 1 + x },
|
41
|
+
lambda {|x| x },
|
42
|
+
lambda {|x| duration }
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.from_array(array)
|
47
|
+
FromProc.new do |observer|
|
48
|
+
array.each { |v|
|
49
|
+
observer.on_next(v)
|
50
|
+
}
|
51
|
+
observer.on_complete
|
52
|
+
Disposable::Wrapper.new
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Reactive::Observable
|
2
|
+
|
3
|
+
class Base
|
4
|
+
|
5
|
+
class_attribute :attributes
|
6
|
+
self.attributes = []
|
7
|
+
|
8
|
+
def self.add_attributes(*v)
|
9
|
+
defaults = v.last.kind_of?(Hash) ? v.pop.to_a : []
|
10
|
+
v.map! {|x| [x,nil] }
|
11
|
+
self.attributes += v + defaults
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.observer_on_next(&method)
|
15
|
+
observer = Class.new(Reactive::ObserverWrapper) do
|
16
|
+
define_method(:on_next, &method)
|
17
|
+
end
|
18
|
+
const_set(:Observer, observer)
|
19
|
+
end
|
20
|
+
|
21
|
+
delegate :schedule_periodic, :schedule_once, :now, to: :scheduler
|
22
|
+
|
23
|
+
|
24
|
+
def initialize(opts = {})
|
25
|
+
self.class.attributes.each { |name, default|
|
26
|
+
instance_variable_set(:"@#{name}", opts[name] || default)
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def observer_args(observer, parent)
|
31
|
+
#base = {:observer => observer, :parent => parent}
|
32
|
+
opts = self.class.attributes.each_with_object({}) do |attr, hash|
|
33
|
+
hash[attr[0]] = instance_variable_get(:"@#{attr[0]}")
|
34
|
+
end
|
35
|
+
[observer, parent, opts]
|
36
|
+
end
|
37
|
+
|
38
|
+
def scheduler
|
39
|
+
@scheduler ||= Reactive::Scheduler.new
|
40
|
+
end
|
41
|
+
|
42
|
+
def subscribe(handlers)
|
43
|
+
[:on_next, :on_error, :on_complete].each {|h| handlers[h] ||= ->(*v) { } }
|
44
|
+
observer = Reactive::Observer.new(handlers)
|
45
|
+
#???
|
46
|
+
self.subscribe_observer(observer)
|
47
|
+
end
|
48
|
+
|
49
|
+
def subscribe_observer(observer)
|
50
|
+
self.run(observer)
|
51
|
+
end
|
52
|
+
|
53
|
+
##!!!
|
54
|
+
def maybe_scheduler(arg = nil)
|
55
|
+
arg ? {scheduler => arg} : {}
|
56
|
+
end
|
57
|
+
|
58
|
+
#joining
|
59
|
+
|
60
|
+
def merge(observable)
|
61
|
+
#MultiMerge.new(observables)
|
62
|
+
#observable = observables[0]
|
63
|
+
observable ? Merge.new(o1: self, o2: observable) : MergeNotifications(self)
|
64
|
+
end
|
65
|
+
|
66
|
+
def map(&proc)
|
67
|
+
Map.new(target: self, mapper: proc)
|
68
|
+
end
|
69
|
+
|
70
|
+
def grep(predicate)
|
71
|
+
Grep.new(target: self, predicate: predicate)
|
72
|
+
end
|
73
|
+
|
74
|
+
def first(count = 1)
|
75
|
+
First.new(target: self, count: count)
|
76
|
+
end
|
77
|
+
|
78
|
+
def push(observable)
|
79
|
+
Push.new(o1: self, o2: observable)
|
80
|
+
end
|
81
|
+
|
82
|
+
def skip(count)
|
83
|
+
Skip.new(target: self, count: count)
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Reactive::Observable
|
2
|
+
|
3
|
+
class Composite < Base
|
4
|
+
|
5
|
+
def run(observer)
|
6
|
+
disposable_parent = build_disposable_parent
|
7
|
+
observer_class = self.class.const_get('Observer')
|
8
|
+
observer_wrapper = observer_class.new(*observer_args(observer, disposable_parent))
|
9
|
+
|
10
|
+
disposables = initial_subscriptions.map {|o| o.subscribe_observer(observer_wrapper) }
|
11
|
+
|
12
|
+
fill_disposable_parent(disposable_parent, disposables)
|
13
|
+
disposable_parent
|
14
|
+
end
|
15
|
+
|
16
|
+
def fill_disposable_parent(parent, disposables)
|
17
|
+
parent.target = disposables unless parent.target
|
18
|
+
end
|
19
|
+
|
20
|
+
def initial_subscriptions
|
21
|
+
[]
|
22
|
+
end
|
23
|
+
|
24
|
+
#def observer_args(observer, parent)
|
25
|
+
# [observer, parent]
|
26
|
+
#end
|
27
|
+
|
28
|
+
private
|
29
|
+
def build_disposable_parent
|
30
|
+
Reactive::Disposable::Wrapper.new
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Reactive::Observable
|
2
|
+
|
3
|
+
class First < Wrapper
|
4
|
+
add_attributes :count
|
5
|
+
|
6
|
+
class Observer < Reactive::ObserverWrapper
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
@taken = 0
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
#??? count is nilled (since it's an attribute), but we might not want it to...
|
14
|
+
#can be less change attribute nilling on unwrap to opt in/out or write unwrap method?
|
15
|
+
#or leave as is because everything is sort of perfect?
|
16
|
+
def on_next(value)
|
17
|
+
@target.on_next(value)
|
18
|
+
@taken += 1
|
19
|
+
on_complete if @taken == @count
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Reactive
|
2
|
+
module Observable
|
3
|
+
class Generate < Base
|
4
|
+
attr_reader :init_value, :continue_perdicate, :step_action, :result_projection, :inter_step_duration
|
5
|
+
|
6
|
+
def initialize(init_value, continue_perdicate, step_action, result_projection, inter_step_duration)
|
7
|
+
@init_value, @continue_perdicate, @step_action, @result_projection, @inter_step_duration = init_value, continue_perdicate, step_action, result_projection, inter_step_duration
|
8
|
+
end
|
9
|
+
|
10
|
+
def run(observer)
|
11
|
+
is_first = true
|
12
|
+
state = @init_value
|
13
|
+
duration = @inter_step_duration
|
14
|
+
action = @step_action
|
15
|
+
projection = @result_projection
|
16
|
+
continue = @continue_perdicate
|
17
|
+
|
18
|
+
init_duration = duration.call(state)
|
19
|
+
self.schedule_periodic(init_duration, lambda do
|
20
|
+
if is_first
|
21
|
+
is_first = false
|
22
|
+
else
|
23
|
+
state = action.call(state)
|
24
|
+
end
|
25
|
+
result = projection.call(state)
|
26
|
+
observer.on_next(result)
|
27
|
+
|
28
|
+
if continue.call(state)
|
29
|
+
duration.call(state)
|
30
|
+
else
|
31
|
+
observer.on_complete()
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
end)
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class Reactive::Observable::Grep < Reactive::Observable::Wrapper
|
2
|
+
|
3
|
+
add_attributes :predicate
|
4
|
+
|
5
|
+
#def initialize(value, predicate)
|
6
|
+
# @predicate = predicate
|
7
|
+
# super(value)
|
8
|
+
#end
|
9
|
+
|
10
|
+
#def observer_args(observer, parent)
|
11
|
+
# [observer, parent, @predicate]
|
12
|
+
#end
|
13
|
+
|
14
|
+
class Observer < Reactive::ObserverWrapper
|
15
|
+
attr_reader :predicate
|
16
|
+
|
17
|
+
#def initialize(value, parent, opts={})
|
18
|
+
# @predicate = opts[:predicate]
|
19
|
+
# super(value, parent)
|
20
|
+
#end
|
21
|
+
|
22
|
+
def on_next(value)
|
23
|
+
begin
|
24
|
+
should_pass = @predicate.call(value)
|
25
|
+
rescue Exception => e
|
26
|
+
pp e
|
27
|
+
on_error.call(e)
|
28
|
+
else
|
29
|
+
@target.on_next(value) if should_pass
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|