reacto 0.0.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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +32 -0
  5. data/README.md +1 -0
  6. data/lib/reacto/behaviours.rb +50 -0
  7. data/lib/reacto/constants.rb +7 -0
  8. data/lib/reacto/executors.rb +31 -0
  9. data/lib/reacto/operations/buffer.rb +80 -0
  10. data/lib/reacto/operations/concat.rb +23 -0
  11. data/lib/reacto/operations/diff.rb +36 -0
  12. data/lib/reacto/operations/drop.rb +41 -0
  13. data/lib/reacto/operations/drop_errors.rb +15 -0
  14. data/lib/reacto/operations/flat_map.rb +26 -0
  15. data/lib/reacto/operations/flatten.rb +26 -0
  16. data/lib/reacto/operations/inject.rb +43 -0
  17. data/lib/reacto/operations/last.rb +31 -0
  18. data/lib/reacto/operations/map.rb +38 -0
  19. data/lib/reacto/operations/merge.rb +47 -0
  20. data/lib/reacto/operations/prepend.rb +19 -0
  21. data/lib/reacto/operations/select.rb +25 -0
  22. data/lib/reacto/operations/take.rb +38 -0
  23. data/lib/reacto/operations/throttle.rb +60 -0
  24. data/lib/reacto/operations/track_on.rb +18 -0
  25. data/lib/reacto/operations/uniq.rb +22 -0
  26. data/lib/reacto/operations.rb +23 -0
  27. data/lib/reacto/resources/executor_resource.rb +19 -0
  28. data/lib/reacto/resources.rb +1 -0
  29. data/lib/reacto/subscriptions/buffered_subscription.rb +52 -0
  30. data/lib/reacto/subscriptions/combining_last_subscription.rb +22 -0
  31. data/lib/reacto/subscriptions/combining_subscription.rb +8 -0
  32. data/lib/reacto/subscriptions/composite_subscription.rb +82 -0
  33. data/lib/reacto/subscriptions/executor_subscription.rb +63 -0
  34. data/lib/reacto/subscriptions/flat_map_subscription.rb +20 -0
  35. data/lib/reacto/subscriptions/inner_subscription.rb +47 -0
  36. data/lib/reacto/subscriptions/operation_subscription.rb +25 -0
  37. data/lib/reacto/subscriptions/simple_subscription.rb +80 -0
  38. data/lib/reacto/subscriptions/subscription.rb +21 -0
  39. data/lib/reacto/subscriptions/subscription_wrapper.rb +16 -0
  40. data/lib/reacto/subscriptions/tracker_subscription.rb +76 -0
  41. data/lib/reacto/subscriptions/zipping_subscription.rb +43 -0
  42. data/lib/reacto/subscriptions.rb +27 -0
  43. data/lib/reacto/trackable.rb +317 -0
  44. data/lib/reacto/tracker.rb +33 -0
  45. data/lib/reacto/version.rb +4 -0
  46. data/lib/reacto.rb +10 -0
  47. data/reacto.gemspec +27 -0
  48. data/spec/reacto/create_trackable_spec.rb +245 -0
  49. data/spec/reacto/executors_and_trackable_spec.rb +32 -0
  50. data/spec/reacto/operations/track_on_spec.rb +20 -0
  51. data/spec/reacto/trackable/buffer_spec.rb +58 -0
  52. data/spec/reacto/trackable/throttle_spec.rb +15 -0
  53. data/spec/reacto/trackable/zip_spec.rb +25 -0
  54. data/spec/reacto/trackable_spec.rb +417 -0
  55. data/spec/spec_helper.rb +15 -0
  56. data/spec/support/helpers.rb +16 -0
  57. metadata +150 -0
@@ -0,0 +1,60 @@
1
+ require 'concurrent'
2
+ require 'reacto/constants'
3
+ require 'reacto/subscriptions/operation_subscription'
4
+
5
+ module Reacto
6
+ module Operations
7
+ class Throttle
8
+ def initialize(delay)
9
+ @delay = delay
10
+ @last = NO_VALUE
11
+ @ready = false
12
+ @error = NO_VALUE
13
+ @close = false
14
+ end
15
+
16
+ def call(tracker)
17
+ close = -> () { @close = true }
18
+ error = lambda do |e|
19
+ delay_task(tracker) unless @ready
20
+ @error = e
21
+ end
22
+ value = lambda do |v|
23
+ delay_task(tracker) unless @ready
24
+ @last = v
25
+ end
26
+
27
+ Subscriptions::OperationSubscription.new(
28
+ tracker,
29
+ value: value,
30
+ close: close,
31
+ error: error
32
+ )
33
+ end
34
+
35
+ private
36
+
37
+ def finish
38
+ @task.shutdown if @task
39
+ end
40
+
41
+ def delay_task(tracker)
42
+ @task = Concurrent::TimerTask.new(execution_interval: @delay) do
43
+ if @error != NO_VALUE
44
+ tracker.on_error(@error)
45
+ finish
46
+ elsif @close
47
+ tracker.on_close
48
+ finish
49
+ elsif @last != NO_VALUE
50
+ tracker.on_value(@last)
51
+ @last = NO_VALUE
52
+ end
53
+ end
54
+ @task.execute
55
+ @ready = true
56
+ end
57
+ end
58
+ end
59
+ end
60
+
@@ -0,0 +1,18 @@
1
+ require 'reacto/subscriptions/executor_subscription'
2
+
3
+ module Reacto
4
+ module Operations
5
+ class TrackOn
6
+ def initialize(executor)
7
+ @executor = executor
8
+ end
9
+
10
+ def call(tracker)
11
+ Subscriptions::ExecutorSubscription.new(
12
+ tracker, @executor
13
+ )
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,22 @@
1
+ require 'reacto/subscriptions/operation_subscription'
2
+
3
+ module Reacto
4
+ module Operations
5
+ class Uniq
6
+ def passed
7
+ @passed ||= {}
8
+ end
9
+
10
+ def call(tracker)
11
+ value = lambda do |v|
12
+ unless passed[v]
13
+ passed[v] = true
14
+ tracker.on_value(v)
15
+ end
16
+ end
17
+
18
+ Subscriptions::OperationSubscription.new(tracker, value: value)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ require 'reacto/operations/map'
2
+ require 'reacto/operations/select'
3
+ require 'reacto/operations/inject'
4
+ require 'reacto/operations/diff'
5
+ require 'reacto/operations/drop'
6
+ require 'reacto/operations/drop_errors'
7
+ require 'reacto/operations/take'
8
+ require 'reacto/operations/last'
9
+ require 'reacto/operations/track_on'
10
+ require 'reacto/operations/prepend'
11
+ require 'reacto/operations/concat'
12
+ require 'reacto/operations/merge'
13
+ require 'reacto/operations/buffer'
14
+ require 'reacto/operations/uniq'
15
+ require 'reacto/operations/flatten'
16
+ require 'reacto/operations/throttle'
17
+ require 'reacto/operations/flat_map'
18
+
19
+ module Reacto
20
+ module Operations
21
+ end
22
+ end
23
+
@@ -0,0 +1,19 @@
1
+ module Reacto
2
+ module Resources
3
+ class ExecutorResource
4
+ def initialize(executor, threads: [])
5
+ @executor = executor
6
+ @threads = threads
7
+ end
8
+ def cleanup
9
+ @executor.shutdown
10
+ @executor = nil
11
+
12
+ @threads.each do |thread|
13
+ Thread.kill(thread)
14
+ end
15
+ @threads = []
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1 @@
1
+ require 'reacto/resources/executor_resource'
@@ -0,0 +1,52 @@
1
+ module Reacto
2
+ module Subscriptions
3
+ class BufferedSubscription < SimpleSubscription
4
+ include Subscription
5
+
6
+ attr_accessor :current_index, :buffer, :last_error
7
+
8
+ def initialize(parent)
9
+ @parent = parent
10
+ @closed = false
11
+ @active = false
12
+
13
+ @buffer = Hash.new(NO_VALUE)
14
+ @current_index = 0
15
+ @last_error = nil
16
+
17
+ open = lambda do
18
+ @active = true
19
+ @parent.on_open
20
+ end
21
+
22
+ value = lambda do |v|
23
+ @buffer[@current_index] = v
24
+ @current_index += 1
25
+
26
+ @parent.on_value(v)
27
+ end
28
+
29
+ error = lambda do |e|
30
+ @last_error = e
31
+ @parent.on_error(e)
32
+ end
33
+
34
+ close = lambda do
35
+ @closed = true
36
+ @parent.on_close
37
+ end
38
+
39
+ super(open: open, value: value, error: error, close: close)
40
+ end
41
+
42
+ def active?
43
+ @active
44
+ end
45
+
46
+ def closed?
47
+ @closed
48
+ end
49
+ end
50
+ end
51
+ end
52
+
@@ -0,0 +1,22 @@
1
+ require 'reacto/constants'
2
+ require 'reacto/subscriptions/composite_subscription'
3
+
4
+ module Reacto
5
+ module Subscriptions
6
+ class CombiningLastSubscription < CompositeSubscription
7
+ def subscribed?
8
+ @subscriptions.all? { |s| s.subscribed? }
9
+ end
10
+
11
+ def after_on_value(_)
12
+ return if @subscriptions.map(&:last_value).any? { |vl| vl == NO_VALUE }
13
+ @subscriptions.each { |sub| sub.last_value = NO_VALUE }
14
+ end
15
+
16
+ def on_close
17
+ return unless subscribed?
18
+ @subscriber.on_close
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,8 @@
1
+ require 'reacto/subscriptions/composite_subscription'
2
+
3
+ module Reacto
4
+ module Subscriptions
5
+ class CombiningSubscription < CompositeSubscription
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,82 @@
1
+ require 'reacto/constants'
2
+ require 'reacto/subscriptions/subscription'
3
+ require 'reacto/subscriptions/inner_subscription'
4
+
5
+ module Reacto
6
+ module Subscriptions
7
+ class CompositeSubscription
8
+ include Subscription
9
+
10
+ def initialize(combinator, subscriber)
11
+ @combinator = combinator
12
+ @subscriptions = []
13
+ @subscriber = subscriber
14
+ end
15
+
16
+ def subscribed?
17
+ @subscriptions.any? { |s| s.subscribed? }
18
+ end
19
+
20
+ def unsubscribe
21
+ @subscriptions.each(&:unsubscribe)
22
+ @subscriptions = []
23
+ end
24
+
25
+ def add(_)
26
+ end
27
+
28
+ def add_resource(_)
29
+ end
30
+
31
+ def on_open
32
+ return unless subscribed?
33
+ return unless @subscriptions.any? { |s| !s.active? }
34
+ @subscriber.on_open
35
+ end
36
+
37
+ def waiting?
38
+ @subscriptions.map(&:last_value).any? { |v| v == NO_VALUE }
39
+ end
40
+
41
+ def on_value(val)
42
+ return unless subscribed?
43
+ return if waiting?
44
+
45
+ on_value_subscriptions(val)
46
+ after_on_value(val)
47
+ end
48
+
49
+ def on_value_subscriptions(_)
50
+ @subscriber.on_value(
51
+ @combinator.call(*@subscriptions.map(&:last_value))
52
+ )
53
+ end
54
+
55
+ def after_on_value(_)
56
+ #nothing by default
57
+ end
58
+
59
+ def on_error(e)
60
+ # Introduce a multi-error and not call on_error right away when there is
61
+ # an error and an option is set?
62
+ return unless subscribed?
63
+ @subscriber.on_error(e)
64
+ end
65
+
66
+ def on_close
67
+ return unless subscribed?
68
+ return unless @subscriptions.any? { |s| !s.closed? }
69
+
70
+ @subscriber.on_close
71
+ unsubscribe
72
+ end
73
+
74
+ def subscription!
75
+ subscription = InnerSubscription.new(self)
76
+ @subscriptions << subscription
77
+
78
+ subscription
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,63 @@
1
+ require 'reacto/subscriptions/subscription'
2
+ require 'reacto/tracker'
3
+
4
+ module Reacto
5
+ module Subscriptions
6
+ class ExecutorSubscription
7
+ include Subscription
8
+
9
+ def initialize(subscription, executor)
10
+ @executor = executor
11
+ @wrapped = subscription
12
+ @closed = false
13
+ end
14
+
15
+ def subscribed?
16
+ unsubscribe unless @wrapped.subscribed?
17
+
18
+ @executor.running?
19
+ end
20
+
21
+ def unsubscribe
22
+ @wrapped.unsubscribe
23
+
24
+ @executor.post(&@executor.method(:shutdown))
25
+ end
26
+
27
+ def add(subscription)
28
+ @wrapped.add(subscription)
29
+ end
30
+
31
+ def add_resource(resource)
32
+ @wrapped.add_resource(resource)
33
+ end
34
+
35
+ def on_open
36
+ @executor.post(&@wrapped.method(:on_open))
37
+ end
38
+
39
+ def on_value(v)
40
+ return if !subscribed? || @closed
41
+
42
+ @executor.post(v, &@wrapped.method(:on_value))
43
+ end
44
+
45
+ def on_close
46
+ return if !subscribed? || @closed
47
+
48
+ @executor.post(&@wrapped.method(:on_close))
49
+ @executor.post(&method(:unsubscribe))
50
+ @closed = true
51
+ end
52
+
53
+ def on_error(error)
54
+ return if !subscribed? || @closed
55
+
56
+ @executor.post(error, &@wrapped.method(:on_error))
57
+ unsubscribe
58
+ @closed = true
59
+ end
60
+ end
61
+ end
62
+ end
63
+
@@ -0,0 +1,20 @@
1
+ require 'reacto/constants'
2
+ require 'reacto/subscriptions/composite_subscription'
3
+
4
+ module Reacto
5
+ module Subscriptions
6
+ class FlatMapSubscription < CompositeSubscription
7
+ def initialize(subscriber)
8
+ super(nil, subscriber)
9
+ end
10
+
11
+ def waiting?
12
+ false
13
+ end
14
+
15
+ def on_value_subscriptions(v)
16
+ @subscriber.on_value(v)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,47 @@
1
+ module Reacto
2
+ module Subscriptions
3
+ class InnerSubscription < SimpleSubscription
4
+ include Subscription
5
+
6
+ attr_accessor :last_value, :last_error
7
+
8
+ def initialize(parent)
9
+ @parent = parent
10
+ @closed = false
11
+ @active = false
12
+ @last_value = NO_VALUE
13
+ @last_error = nil
14
+
15
+ open = lambda do
16
+ @active = true
17
+ @parent.on_open
18
+ end
19
+
20
+ value = lambda do |v|
21
+ @last_value = v
22
+ @parent.on_value(v)
23
+ end
24
+
25
+ error = lambda do |e|
26
+ @last_error = e
27
+ @parent.on_error(e)
28
+ end
29
+
30
+ close = lambda do
31
+ @closed = true
32
+ @parent.on_close
33
+ end
34
+
35
+ super(open: open, value: value, error: error, close: close)
36
+ end
37
+
38
+ def active?
39
+ @active
40
+ end
41
+
42
+ def closed?
43
+ @closed
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,25 @@
1
+ require 'reacto/subscriptions/subscription'
2
+ require 'reacto/tracker'
3
+
4
+ module Reacto
5
+ module Subscriptions
6
+ class OperationSubscription < Reacto::Tracker
7
+ extend Forwardable
8
+ include Subscription
9
+
10
+ delegate ['subscribed?', :unsubscribe, :add, :add_resource] => :@wrapped
11
+
12
+ def initialize(
13
+ subscription,
14
+ open: subscription.method(:on_open),
15
+ value: subscription.method(:on_value),
16
+ error: subscription.method(:on_error),
17
+ close: subscription.method(:on_close)
18
+ )
19
+ super(open: open, value: value, error: error, close: close)
20
+
21
+ @wrapped = subscription
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,80 @@
1
+ require 'reacto/constants'
2
+ require 'reacto/subscriptions/subscription'
3
+
4
+ module Reacto
5
+ module Subscriptions
6
+ class SimpleSubscription
7
+ include Subscription
8
+
9
+ def initialize(
10
+ open: NO_ACTION,
11
+ value: NO_ACTION,
12
+ error: DEFAULT_ON_ERROR,
13
+ close: NO_ACTION
14
+ )
15
+ @open = open
16
+ @value = value
17
+ @error = error
18
+ @close = close
19
+
20
+ @subscribed = true
21
+ @subscriptions = []
22
+ @resources = []
23
+ end
24
+
25
+ def subscribed?
26
+ @subscribed
27
+ end
28
+
29
+ def unsubscribe
30
+ @subscriptions.each(&:unsubscribe)
31
+ @subscribed = false
32
+ @resources.each(&:cleanup)
33
+ @resources = []
34
+ end
35
+
36
+ def add(subscription)
37
+ return unless subscribed?
38
+
39
+ @subscriptions << subscription
40
+ end
41
+
42
+ def add_resource(resource)
43
+ return unless subscribed?
44
+
45
+ @resources << resource
46
+ end
47
+
48
+ def on_open
49
+ return unless subscribed?
50
+
51
+ @open.call
52
+ @subscriptions.each(&:on_open)
53
+ end
54
+
55
+ def on_value(v)
56
+ return unless subscribed?
57
+
58
+ @value.call(v)
59
+ @subscriptions.each { |s| s.on_value(v) }
60
+ end
61
+
62
+ def on_error(e)
63
+ return unless subscribed?
64
+
65
+ @error.call(error)
66
+ @subscriptions.each { |s| s.on_error(e) }
67
+ unsubscribe
68
+ end
69
+
70
+ def on_close
71
+ return unless subscribed?
72
+
73
+ @close.call
74
+ @subscriptions.each(&:on_close)
75
+ unsubscribe
76
+ end
77
+ end
78
+ end
79
+ end
80
+
@@ -0,0 +1,21 @@
1
+ module Reacto
2
+ module Subscriptions
3
+ module Subscription
4
+ def subscribed?
5
+ raise NotImplementedError.new('Abstract method!')
6
+ end
7
+
8
+ def unsubscribe
9
+ raise NotImplementedError.new('Abstract method!')
10
+ end
11
+
12
+ def add(subscription)
13
+ raise NotImplementedError.new('Abstract method!')
14
+ end
15
+
16
+ def add_resource(resource)
17
+ raise NotImplementedError.new('Abstract method!')
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ require 'reacto/subscriptions/subscription'
2
+
3
+ module Reacto
4
+ module Subscriptions
5
+ class SubscriptionWrapper
6
+ extend Forwardable
7
+ include Subscription
8
+
9
+ delegate ['subscribed?', :unsubscribe, :add, :add_resource] => :@wrapped
10
+
11
+ def initialize(wrapped)
12
+ @wrapped = wrapped
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,76 @@
1
+ require 'reacto/subscriptions/subscription'
2
+
3
+ module Reacto
4
+ module Subscriptions
5
+ class TrackerSubscription
6
+ include Subscription
7
+
8
+ def initialize(notification_tracker, trackable)
9
+ @notification_tracker = notification_tracker
10
+ @trackable = trackable
11
+
12
+ @subscribed = true
13
+ @subscriptions = []
14
+ @resources = []
15
+ end
16
+
17
+ def subscribed?
18
+ @subscribed
19
+ end
20
+
21
+ def unsubscribe
22
+ @subscriptions.each(&:unsubscribe)
23
+
24
+ @trackable.off(@notification_tracker)
25
+
26
+ @trackable = nil
27
+ @notification_tracker = nil
28
+ @subscribed = false
29
+ @resources.each(&:cleanup)
30
+ @resources = []
31
+ end
32
+
33
+ def add_resource(resource)
34
+ return unless subscribed?
35
+
36
+ @resources << resource
37
+ end
38
+
39
+ def add(subscription)
40
+ return unless subscribed?
41
+
42
+ @subscriptions << subscription
43
+ end
44
+
45
+ def on_open
46
+ return unless subscribed?
47
+
48
+ @notification_tracker.on_open
49
+ @subscriptions.each(&:on_open)
50
+ end
51
+
52
+ def on_value(v)
53
+ return unless subscribed?
54
+
55
+ @notification_tracker.on_value(v)
56
+ @subscriptions.each { |s| s.on_value(v) }
57
+ end
58
+
59
+ def on_error(e)
60
+ return unless subscribed?
61
+
62
+ @notification_tracker.on_error(e)
63
+ @subscriptions.each { |s| s.on_error(e) }
64
+ unsubscribe
65
+ end
66
+
67
+ def on_close
68
+ return unless subscribed?
69
+
70
+ @notification_tracker.on_close
71
+ @subscriptions.each(&:on_close)
72
+ unsubscribe
73
+ end
74
+ end
75
+ end
76
+ end