reacto 0.1.0 → 1.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 (128) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/README.md +932 -11
  4. data/doc/reactive_programming_with_reacto.md +238 -0
  5. data/lib/reacto.rb +70 -0
  6. data/lib/reacto/behaviours.rb +24 -1
  7. data/lib/reacto/constants.rb +4 -1
  8. data/lib/reacto/executors.rb +8 -10
  9. data/lib/reacto/labeled_trackable.rb +14 -2
  10. data/lib/reacto/operations.rb +23 -2
  11. data/lib/reacto/operations/act.rb +69 -0
  12. data/lib/reacto/operations/append.rb +45 -0
  13. data/lib/reacto/operations/blocking_enumerable.rb +40 -0
  14. data/lib/reacto/operations/buffer.rb +1 -4
  15. data/lib/reacto/operations/chunk.rb +81 -0
  16. data/lib/reacto/operations/chunk_while.rb +56 -0
  17. data/lib/reacto/operations/cycle.rb +27 -0
  18. data/lib/reacto/operations/delay_each.rb +75 -0
  19. data/lib/reacto/operations/depend_on.rb +4 -5
  20. data/lib/reacto/operations/diff.rb +8 -10
  21. data/lib/reacto/operations/drop.rb +6 -8
  22. data/lib/reacto/operations/drop_while.rb +23 -0
  23. data/lib/reacto/operations/each_collect.rb +57 -0
  24. data/lib/reacto/operations/each_with_object.rb +31 -0
  25. data/lib/reacto/operations/extremums.rb +54 -0
  26. data/lib/reacto/operations/find_index.rb +28 -0
  27. data/lib/reacto/operations/flat_map.rb +2 -2
  28. data/lib/reacto/operations/flatten.rb +2 -7
  29. data/lib/reacto/operations/flatten_labeled.rb +44 -0
  30. data/lib/reacto/operations/{label.rb → group_by_label.rb} +1 -1
  31. data/lib/reacto/operations/include.rb +40 -0
  32. data/lib/reacto/operations/inject.rb +15 -9
  33. data/lib/reacto/operations/map.rb +15 -13
  34. data/lib/reacto/operations/merge.rb +17 -16
  35. data/lib/reacto/operations/operation_on_labeled.rb +29 -0
  36. data/lib/reacto/operations/partition.rb +52 -0
  37. data/lib/reacto/operations/prepend.rb +0 -3
  38. data/lib/reacto/operations/rescue_and_replace_error.rb +21 -0
  39. data/lib/reacto/operations/retry.rb +26 -0
  40. data/lib/reacto/operations/retry_when.rb +30 -0
  41. data/lib/reacto/operations/select.rb +2 -6
  42. data/lib/reacto/operations/slice.rb +50 -0
  43. data/lib/reacto/operations/slice_when.rb +41 -0
  44. data/lib/reacto/operations/split_labeled.rb +32 -0
  45. data/lib/reacto/operations/take.rb +9 -14
  46. data/lib/reacto/operations/take_while.rb +28 -0
  47. data/lib/reacto/operations/throttle.rb +2 -3
  48. data/lib/reacto/operations/track_on.rb +1 -3
  49. data/lib/reacto/shared_trackable.rb +2 -5
  50. data/lib/reacto/subscriptions/buffered_subscription.rb +10 -9
  51. data/lib/reacto/subscriptions/executor_subscription.rb +12 -4
  52. data/lib/reacto/subscriptions/tracker_subscription.rb +0 -4
  53. data/lib/reacto/subscriptions/zipping_subscription.rb +0 -1
  54. data/lib/reacto/trackable.rb +429 -64
  55. data/lib/reacto/version.rb +1 -1
  56. data/reacto.gemspec +2 -3
  57. data/spec/reacto/labeled_trackable_spec.rb +17 -0
  58. data/spec/reacto/trackable/act_spec.rb +15 -0
  59. data/spec/reacto/trackable/all_spec.rb +38 -0
  60. data/spec/reacto/trackable/any_spec.rb +39 -0
  61. data/spec/reacto/trackable/append_spec.rb +38 -0
  62. data/spec/reacto/trackable/buffer_spec.rb +11 -15
  63. data/spec/reacto/trackable/chunk_spec.rb +86 -0
  64. data/spec/reacto/trackable/chunk_while_spec.rb +22 -0
  65. data/spec/reacto/trackable/class_level/combine_last_spec.rb +1 -3
  66. data/spec/reacto/trackable/class_level/interval_spec.rb +4 -6
  67. data/spec/reacto/trackable/class_level/make_spec.rb +0 -15
  68. data/spec/reacto/trackable/{zip_spec.rb → class_level/zip_spec.rb} +0 -2
  69. data/spec/reacto/trackable/concat_spec.rb +12 -12
  70. data/spec/reacto/trackable/count_spec.rb +38 -0
  71. data/spec/reacto/trackable/cycle_spec.rb +14 -0
  72. data/spec/reacto/trackable/delay_each_spec.rb +18 -0
  73. data/spec/reacto/trackable/depend_on_spec.rb +6 -9
  74. data/spec/reacto/trackable/diff_spec.rb +3 -5
  75. data/spec/reacto/trackable/drop_errors_spec.rb +1 -3
  76. data/spec/reacto/trackable/drop_while_spec.rb +15 -0
  77. data/spec/reacto/trackable/each_cons_spec.rb +53 -0
  78. data/spec/reacto/trackable/each_slice_spec.rb +37 -0
  79. data/spec/reacto/trackable/each_with_index_spec.rb +33 -0
  80. data/spec/reacto/trackable/each_with_object_spec.rb +26 -0
  81. data/spec/reacto/trackable/entries_spec.rb +25 -0
  82. data/spec/reacto/trackable/execute_on_spec.rb +33 -0
  83. data/spec/reacto/trackable/find_index_spec.rb +31 -0
  84. data/spec/reacto/trackable/find_spec.rb +34 -0
  85. data/spec/reacto/trackable/first_spec.rb +36 -0
  86. data/spec/reacto/trackable/flat_map_latest_spec.rb +5 -5
  87. data/spec/reacto/trackable/flat_map_spec.rb +25 -0
  88. data/spec/reacto/trackable/flatten_labeled_spec.rb +48 -0
  89. data/spec/reacto/trackable/grep_spec.rb +29 -0
  90. data/spec/reacto/trackable/grep_v_spec.rb +23 -0
  91. data/spec/reacto/trackable/{label_spec.rb → group_by_label_spec.rb} +4 -11
  92. data/spec/reacto/trackable/include_spec.rb +23 -0
  93. data/spec/reacto/trackable/inject_spec.rb +30 -4
  94. data/spec/reacto/trackable/lift_spec.rb +1 -3
  95. data/spec/reacto/trackable/map_spec.rb +17 -3
  96. data/spec/reacto/trackable/max_by_spec.rb +12 -0
  97. data/spec/reacto/trackable/max_spec.rb +19 -0
  98. data/spec/reacto/trackable/merge_spec.rb +6 -7
  99. data/spec/reacto/trackable/min_by_spec.rb +12 -0
  100. data/spec/reacto/trackable/min_spec.rb +19 -0
  101. data/spec/reacto/trackable/minmax_by_spec.rb +12 -0
  102. data/spec/reacto/trackable/minmax_spec.rb +19 -0
  103. data/spec/reacto/trackable/none_spec.rb +38 -0
  104. data/spec/reacto/trackable/on_spec.rb +11 -4
  105. data/spec/reacto/trackable/one_spec.rb +38 -0
  106. data/spec/reacto/trackable/partition_spec.rb +23 -0
  107. data/spec/reacto/trackable/prepend_spec.rb +1 -3
  108. data/spec/reacto/trackable/reject_spec.rb +21 -0
  109. data/spec/reacto/trackable/rescue_and_replace_error_spec.rb +48 -0
  110. data/spec/reacto/trackable/rescue_and_replace_error_with_spec.rb +26 -0
  111. data/spec/reacto/trackable/retry_spec.rb +50 -0
  112. data/spec/reacto/trackable/retry_when_spec.rb +33 -0
  113. data/spec/reacto/trackable/select_spec.rb +18 -7
  114. data/spec/reacto/trackable/slice_after_spec.rb +38 -0
  115. data/spec/reacto/trackable/slice_before_spec.rb +38 -0
  116. data/spec/reacto/trackable/slice_when_spec.rb +26 -0
  117. data/spec/reacto/trackable/sort_by_spec.rb +16 -0
  118. data/spec/reacto/trackable/sort_spec.rb +23 -0
  119. data/spec/reacto/trackable/split_labeled_spec.rb +37 -0
  120. data/spec/reacto/trackable/take_while_spec.rb +16 -0
  121. data/spec/reacto/trackable/throttle_spec.rb +2 -3
  122. data/spec/reacto/trackable/track_on_spec.rb +2 -3
  123. data/spec/reacto/trackable/uniq_spec.rb +2 -4
  124. data/spec/support/helpers.rb +9 -1
  125. metadata +135 -25
  126. data/Gemfile.lock +0 -32
  127. data/lib/reacto/operations/cache.rb +0 -53
  128. data/spec/reacto/trackable/cache_spec.rb +0 -64
@@ -15,11 +15,11 @@ module Reacto
15
15
 
16
16
  def call(tracker)
17
17
  close = -> () { @close = true }
18
- error = lambda do |e|
18
+ error = -> (e) do
19
19
  delay_task(tracker) unless @ready
20
20
  @error = e
21
21
  end
22
- value = lambda do |v|
22
+ value = -> (v) do
23
23
  delay_task(tracker) unless @ready
24
24
  @last = v
25
25
  end
@@ -57,4 +57,3 @@ module Reacto
57
57
  end
58
58
  end
59
59
  end
60
-
@@ -8,9 +8,7 @@ module Reacto
8
8
  end
9
9
 
10
10
  def call(tracker)
11
- Subscriptions::ExecutorSubscription.new(
12
- tracker, @executor
13
- )
11
+ Subscriptions::ExecutorSubscription.new(tracker, @executor)
14
12
  end
15
13
  end
16
14
  end
@@ -2,11 +2,8 @@ require 'reacto/trackable'
2
2
 
3
3
  module Reacto
4
4
  class SharedTrackable < Trackable
5
- def initialize(
6
- behaviour = NO_ACTION, executor = nil, activate_on_subscribe = false,
7
- &block
8
- )
9
- super(behaviour, executor, &block)
5
+ def initialize(executor = nil, activate_on_subscribe = false, &block)
6
+ super(executor, &block)
10
7
 
11
8
  @activate_on_subscribe = activate_on_subscribe
12
9
  @active = false
@@ -1,9 +1,11 @@
1
+ require 'concurrent'
2
+
1
3
  module Reacto
2
4
  module Subscriptions
3
5
  class BufferedSubscription < SimpleSubscription
4
6
  include Subscription
5
7
 
6
- attr_accessor :current_index, :buffer, :last_error
8
+ attr_accessor :buffer, :last_error
7
9
 
8
10
  def initialize(parent)
9
11
  @parent = parent
@@ -11,27 +13,27 @@ module Reacto
11
13
  @active = false
12
14
 
13
15
  @buffer = Hash.new(NO_VALUE)
14
- @current_index = 0
16
+ @current_index = Concurrent::AtomicFixnum.new(0)
15
17
  @last_error = nil
16
18
 
17
- open = lambda do
19
+ open = -> () do
18
20
  @active = true
19
21
  @parent.on_open
20
22
  end
21
23
 
22
- value = lambda do |v|
23
- @buffer[@current_index] = v
24
- @current_index += 1
24
+ value = -> (v) do
25
+ @buffer[@current_index.value] = v
26
+ @current_index.increment
25
27
 
26
28
  @parent.on_value(v)
27
29
  end
28
30
 
29
- error = lambda do |e|
31
+ error = -> (e) do
30
32
  @last_error = e
31
33
  @parent.on_error(e)
32
34
  end
33
35
 
34
- close = lambda do
36
+ close = -> () do
35
37
  @closed = true
36
38
  @parent.on_close
37
39
  end
@@ -49,4 +51,3 @@ module Reacto
49
51
  end
50
52
  end
51
53
  end
52
-
@@ -45,17 +45,25 @@ module Reacto
45
45
  def on_close
46
46
  return if !subscribed? || @closed
47
47
 
48
+ unsubscribe_subscription = Subscriptions.on_close_and_error do
49
+ @executor.post(&method(:unsubscribe))
50
+ @closed = true
51
+ end
52
+ @wrapped.add(unsubscribe_subscription)
53
+
48
54
  @executor.post(&@wrapped.method(:on_close))
49
- @executor.post(&method(:unsubscribe))
50
- @closed = true
51
55
  end
52
56
 
53
57
  def on_error(error)
54
58
  return if !subscribed? || @closed
55
59
 
60
+ unsubscribe_subscription = Subscriptions.on_close_and_error do
61
+ @executor.post(&method(:unsubscribe))
62
+ @closed = true
63
+ end
64
+ @wrapped.add(unsubscribe_subscription)
65
+
56
66
  @executor.post(error, &@wrapped.method(:on_error))
57
- unsubscribe
58
- @closed = true
59
67
  end
60
68
  end
61
69
  end
@@ -32,14 +32,10 @@ module Reacto
32
32
  end
33
33
 
34
34
  def add_resource(resource)
35
- return unless subscribed?
36
-
37
35
  @resources << resource
38
36
  end
39
37
 
40
38
  def add(subscription)
41
- return unless subscribed?
42
-
43
39
  @subscriptions << subscription
44
40
  end
45
41
 
@@ -40,4 +40,3 @@ module Reacto
40
40
  end
41
41
  end
42
42
  end
43
-
@@ -9,10 +9,21 @@ require 'reacto/operations'
9
9
  require 'reacto/executors'
10
10
  require 'reacto/resources'
11
11
 
12
- # TODO: Refactor the constructors and the factory methods
13
12
  module Reacto
14
13
  class Trackable
15
- TOPICS = [:open, :value, :error, :close]
14
+ include Enumerable
15
+
16
+ TOPICS = %i(open value error close)
17
+
18
+ EXECUTOR_ALIASES = {
19
+ new_thread: Executors.new_thread,
20
+ background: Executors.tasks,
21
+ tasks: Executors.tasks,
22
+ io: Executors.io,
23
+ current: Executors.current,
24
+ immediate: Executors.immediate,
25
+ now: Executors.immediate
26
+ }
16
27
 
17
28
  class << self
18
29
  def never
@@ -25,6 +36,10 @@ module Reacto
25
36
  )
26
37
  end
27
38
 
39
+ def concat(*trackables)
40
+ trackables.inject { |current, trackable| current.concat(trackable) }
41
+ end
42
+
28
43
  def combine_last(*trackables, &block)
29
44
  combine_create(
30
45
  Subscriptions::CombiningLastSubscription, *trackables, &block
@@ -35,24 +50,27 @@ module Reacto
35
50
  combine_create(Subscriptions::ZippingSubscription, *trackables, &block)
36
51
  end
37
52
 
38
- def close(executor = nil)
39
- make(nil, executor) do |subscriber|
40
- subscriber.on_close
41
- end
53
+ def close(executor: nil)
54
+ make(executor) { |subscriber| subscriber.on_close }
42
55
  end
43
56
 
44
- def error(err, executor = nil)
45
- make(nil, executor) do |subscriber|
57
+ def error(err, executor: nil)
58
+ make(executor) do |subscriber|
46
59
  subscriber.on_error(err)
47
60
  end
48
61
  end
49
62
 
50
- def make(behaviour = NO_ACTION, executor = nil, &block)
51
- behaviour = block_given? ? block : behaviour
52
- self.new(behaviour, executor)
63
+ def make(executor_param = nil, executor: nil, &block)
64
+ real_executor = executor_param ? executor_param : executor
65
+
66
+ behaviour = block_given? ? block : NO_ACTION
67
+ self.new(real_executor, &behaviour)
53
68
  end
54
69
 
55
70
  def later(secs, value, executor: Reacto::Executors.tasks)
71
+ stored = EXECUTOR_ALIASES[executor]
72
+ executor = stored if stored
73
+
56
74
  if executor.is_a?(Concurrent::ImmediateExecutor)
57
75
  make do |tracker|
58
76
  sleep secs
@@ -72,6 +90,9 @@ module Reacto
72
90
  enumerator = Behaviours.integers_enumerator,
73
91
  executor: nil
74
92
  )
93
+ stored = EXECUTOR_ALIASES[executor]
94
+ executor = stored if stored
95
+
75
96
  if executor.is_a?(Concurrent::ImmediateExecutor)
76
97
  make do |tracker|
77
98
  Behaviours.with_close_and_error(tracker) do |subscriber|
@@ -91,7 +112,7 @@ module Reacto
91
112
  end
92
113
  else
93
114
  make do |tracker|
94
- Thread::abort_on_exception = true
115
+ Thread.abort_on_exception = true
95
116
 
96
117
  queue = Queue.new
97
118
  task = Concurrent::TimerTask.new(execution_interval: interval) do
@@ -134,27 +155,15 @@ module Reacto
134
155
  )
135
156
  end
136
157
 
137
- def value(value, executor = nil)
138
- make(Behaviours.single_value(value), executor)
158
+ def value(value, executor: nil)
159
+ make(executor, &Behaviours.single_value(value))
139
160
  end
140
161
 
141
- def enumerable(enumerable, executor = nil)
142
- make(nil, executor) do |tracker|
143
- begin
144
- enumerable.each do |val|
145
- break unless tracker.subscribed?
146
- tracker.on_value(val)
147
- end
148
-
149
- tracker.on_close if tracker.subscribed?
150
- rescue => error
151
- tracker.on_error(error) if tracker.subscribed?
152
- end
153
- end
162
+ def enumerable(enumerable, executor: nil)
163
+ make(executor, &Behaviours.enumerable(enumerable))
154
164
  end
155
165
 
156
- def combine_with(function, *trackables)
157
- end
166
+ alias_method :combine_latest, :combine
158
167
 
159
168
  private
160
169
 
@@ -168,12 +177,164 @@ module Reacto
168
177
  end
169
178
  end
170
179
 
171
- def initialize(behaviour = NO_ACTION, executor = nil, &block)
172
- @behaviour = block_given? ? block : behaviour
180
+ def initialize(executor = nil, &block)
181
+ @behaviour = block_given? ? block : NO_ACTION
182
+
183
+ stored = EXECUTOR_ALIASES[executor]
184
+ executor = stored if stored
173
185
  @executor = executor
174
186
  end
175
187
 
176
- def on(trackers = {})
188
+ def all?(&block)
189
+ lift(Operations::BlockingEnumerable.new(:'all?', block))
190
+ end
191
+
192
+ def any?(&block)
193
+ lift(Operations::BlockingEnumerable.new(:'any?', block))
194
+ end
195
+
196
+ def none?(&block)
197
+ lift(Operations::BlockingEnumerable.new(:'none?', block))
198
+ end
199
+
200
+ def one?(&block)
201
+ lift(Operations::BlockingEnumerable.new(:'one?', block))
202
+ end
203
+
204
+ def sort(&block)
205
+ lift(Operations::BlockingEnumerable.new(:sort, block))
206
+ end
207
+
208
+ def sort_by(&block)
209
+ return self unless block_given?
210
+
211
+ lift(Operations::BlockingEnumerable.new(:sort_by, block))
212
+ end
213
+
214
+ def partition(executor: nil, &block)
215
+ return self unless block_given?
216
+
217
+ executor = retrieve_executor(executor)
218
+ executor = @executor if executor.nil?
219
+
220
+ lift(Operations::Partition.new(block, executor: executor))
221
+ end
222
+
223
+ def chunk(executor: nil, &block)
224
+ return self unless block_given?
225
+
226
+ executor = retrieve_executor(executor)
227
+ executor = @executor if executor.nil?
228
+
229
+ lift(Operations::Chunk.new(block, executor: executor))
230
+ end
231
+
232
+ def chunk_while(executor: nil, &block)
233
+ executor = retrieve_executor(executor)
234
+ executor = @executor if executor.nil?
235
+
236
+ lift(Operations::ChunkWhile.new(block, executor: executor))
237
+ end
238
+
239
+ def cycle(n = nil)
240
+ lift(Operations::Cycle.new(@behaviour, n))
241
+ end
242
+
243
+ def find(if_none = NO_VALUE, &block)
244
+ trackable = select(&block).first
245
+
246
+ if if_none != NO_VALUE
247
+ trackable = trackable.append(if_none, condition: :source_empty)
248
+ end
249
+
250
+ trackable
251
+ end
252
+
253
+ def find_index(value = NO_VALUE, &block)
254
+ predicate =
255
+ if value != NO_VALUE
256
+ -> (v) { value == v }
257
+ else
258
+ block
259
+ end
260
+ lift(Operations::FindIndex.new(predicate))
261
+ end
262
+
263
+ def count(value = NO_VALUE, &block)
264
+ source =
265
+ if value != NO_VALUE
266
+ select(&Behaviours.same_predicate(value))
267
+ elsif block_given?
268
+ select(&block)
269
+ else
270
+ self
271
+ end
272
+
273
+ source.map(1).inject(&:+).last
274
+ end
275
+
276
+ def each_cons(n, &block)
277
+ raise ArgumentError.new('invalid size') if n <= 0
278
+ return each(&block) if n == 1
279
+
280
+ reset_action = -> (current) { current[1..-1] }
281
+
282
+ trackable = lift(Operations::EachCollect.new(
283
+ n, reset_action: reset_action, on_error: NO_ACTION, on_close: NO_ACTION
284
+ ))
285
+ block_given? ? trackable.on(&block) : trackable
286
+ end
287
+
288
+ def each_slice(n, &block)
289
+ raise ArgumentError.new('invalid size') if n <= 0
290
+
291
+ trackable = lift(Operations::EachCollect.new(n))
292
+
293
+ block_given? ? trackable.on(&block) : trackable
294
+ end
295
+
296
+ def each_with_index(&block)
297
+ index = 0
298
+
299
+ collect_action = -> (val, collection) do
300
+ collection << val
301
+ collection << index
302
+ index += 1
303
+ end
304
+
305
+ trackable = lift(Operations::EachCollect.new(
306
+ 2, collect_action: collect_action, init_action: -> () { index = 0 },
307
+ on_error: NO_ACTION, on_close: NO_ACTION
308
+ ))
309
+
310
+ block_given? ? trackable.on(&block) : trackable
311
+ end
312
+
313
+ def entries(n = nil)
314
+ return [] if n && n.is_a?(Integer) && n <= 0
315
+
316
+ trackable = self
317
+ trackable = trackable.take(n) if n && n.is_a?(Integer) && n > 0
318
+
319
+ result = []
320
+ subscription = trackable.on(value: ->(v) { result << v })
321
+
322
+ trackable.await(subscription)
323
+
324
+ result
325
+ end
326
+
327
+ def to_a
328
+ entries
329
+ end
330
+
331
+ def to_h
332
+ to_a.to_h
333
+ end
334
+
335
+ def on(trackers = {}, &block)
336
+ trackers[:value] = block if block_given?
337
+
177
338
  unless (trackers.keys - TOPICS).empty?
178
339
  raise "This Trackable supports only #{TOPICS}, " \
179
340
  "but #{trackers.keys} were passed."
@@ -186,7 +347,9 @@ module Reacto
186
347
  # Clean-up logic
187
348
  end
188
349
 
189
- def track(notification_tracker)
350
+ def track(notification_tracker, &block)
351
+ return on(&block) if block_given?
352
+
190
353
  subscription =
191
354
  Subscriptions::TrackerSubscription.new(notification_tracker, self)
192
355
 
@@ -197,7 +360,7 @@ module Reacto
197
360
 
198
361
  def lift(operation = nil, &block)
199
362
  operation = block_given? ? block : operation
200
- self.class.new(nil, @executor) do |tracker_subscription|
363
+ create_lifted do |tracker_subscription|
201
364
  begin
202
365
  modified = operation.call(tracker_subscription)
203
366
  lift_behaviour(modified) unless modified == NOTHING
@@ -207,40 +370,126 @@ module Reacto
207
370
  end
208
371
  end
209
372
 
210
- def flat_map(transform = nil, &block)
211
- lift(Operations::FlatMap.new(block_given? ? block : transform))
373
+ def flat_map(transform = nil, label: nil, &block)
374
+ if label
375
+ lift(Operations::OperationOnLabeled.new(
376
+ label, block_given? ? block : transform, op: :flat_map
377
+ ))
378
+ else
379
+ lift(Operations::FlatMap.new(block_given? ? block : transform))
380
+ end
212
381
  end
213
382
 
214
383
  def flat_map_latest(transform = nil, &block)
215
384
  lift(Operations::FlatMapLatest.new(block_given? ? block : transform))
216
385
  end
217
386
 
218
- def map(mapping = nil, error: nil, close: nil, &block)
219
- lift(Operations::Map.new(
220
- block_given? ? block : mapping, error: error, close: close
221
- ))
387
+ def map(val = NO_VALUE, error: nil, close: nil, label: nil, &block)
388
+ action =
389
+ if block_given?
390
+ block
391
+ else
392
+ val == NO_VALUE ? IDENTITY_ACTION : Behaviours.constant(val)
393
+ end
394
+ if label
395
+ lift(Operations::OperationOnLabeled.new(
396
+ label, action, error: error, close: close
397
+ ))
398
+ else
399
+ lift(Operations::Map.new(action, error: error, close: close))
400
+ end
401
+ end
402
+
403
+ def max(&block)
404
+ lift(Operations::Extremums.new(action: block))
405
+ end
406
+
407
+ def max_by(&block)
408
+ lift(Operations::Extremums.new(by: block))
409
+ end
410
+
411
+ def min(&block)
412
+ lift(Operations::Extremums.new(action: block, type: :min))
413
+ end
414
+
415
+ def min_by(&block)
416
+ lift(Operations::Extremums.new(by: block, type: :min))
417
+ end
418
+
419
+ def minmax(&block)
420
+ lift(Operations::Extremums.new(action: block, type: :minmax))
421
+ end
422
+
423
+ def minmax_by(&block)
424
+ lift(Operations::Extremums.new(by: block, type: :minmax))
425
+ end
426
+
427
+ def grep(pattern, &block)
428
+ select_map(-> (v) { pattern === v }, &block)
429
+ end
430
+
431
+ def grep_v(pattern, &block)
432
+ select_map(-> (v) { !(pattern === v) }, &block)
433
+ end
434
+
435
+ def include?(obj)
436
+ lift(Operations::Include.new(obj))
437
+ end
438
+
439
+ def lazy
440
+ self # Just to comply with Enumerable
222
441
  end
223
442
 
224
443
  def wrap(**args)
225
444
  lift(Operations::Wrap.new(args))
226
445
  end
227
446
 
228
- def select(filter = nil, &block)
229
- lift(Operations::Select.new(block_given? ? block : filter))
447
+ def select(label: nil, &block)
448
+ return self unless block_given?
449
+
450
+ if label
451
+ lift(Operations::OperationOnLabeled.new(label, block, op: :select))
452
+ else
453
+ lift(Operations::Select.new(block))
454
+ end
230
455
  end
231
456
 
232
- def inject(initial = NO_VALUE, injector = nil, &block)
233
- lift(Operations::Inject.new(block_given? ? block : injector, initial))
457
+ def reject(&block)
458
+ select(&->(val) { !block.call(val)} )
234
459
  end
235
460
 
236
- def diff(initial = NO_VALUE, fn = Operations::Diff::DEFAULT_FN, &block)
237
- lift(Operations::Diff.new(block_given? ? block : fn, initial))
461
+ def inject(initial = NO_VALUE, label: nil, initial_value: NO_VALUE, &block)
462
+ return self unless block_given?
463
+
464
+ init = initial != NO_VALUE ? initial : initial_value
465
+ if label
466
+ lift(Operations::OperationOnLabeled.new(
467
+ label, block, op: :inject, initial_value: init
468
+ ))
469
+ else
470
+ lift(Operations::Inject.new(block, init))
471
+ end
472
+ end
473
+
474
+ def each_with_object(obj, &block)
475
+ lift(Operations::EachWithObject.new(block, obj))
476
+ end
477
+
478
+ def diff(initial = NO_VALUE, &block)
479
+ lift(Operations::Diff.new(
480
+ block_given? ? block : Operations::Diff::DEFAULT_FN, initial
481
+ ))
238
482
  end
239
483
 
240
484
  def drop(how_many_to_drop)
241
485
  lift(Operations::Drop.new(how_many_to_drop))
242
486
  end
243
487
 
488
+ def drop_while(&block)
489
+ predicate = block_given? ? block : FALSE_PREDICATE
490
+ lift(Operations::DropWhile.new(predicate))
491
+ end
492
+
244
493
  def drop_errors
245
494
  lift(Operations::DropErrors.new)
246
495
  end
@@ -249,6 +498,12 @@ module Reacto
249
498
  lift(Operations::Take.new(how_many_to_take))
250
499
  end
251
500
 
501
+ def take_while(&block)
502
+ return self unless block_given?
503
+
504
+ lift(Operations::TakeWhile.new(block))
505
+ end
506
+
252
507
  def uniq
253
508
  lift(Operations::Uniq.new)
254
509
  end
@@ -257,8 +512,10 @@ module Reacto
257
512
  lift(Operations::Flatten.new)
258
513
  end
259
514
 
260
- def first
261
- take(1)
515
+ def first(n = 1)
516
+ raise ArgumentError.new('negative array size') if n < 0
517
+
518
+ take(n)
262
519
  end
263
520
 
264
521
  def [](x)
@@ -273,12 +530,16 @@ module Reacto
273
530
  lift(Operations::Prepend.new(enumerable))
274
531
  end
275
532
 
533
+ def append(to_append, condition: nil)
534
+ lift(Operations::Append.new(to_append, condition: condition))
535
+ end
536
+
276
537
  def concat(trackable)
277
538
  lift(Operations::Concat.new(trackable))
278
539
  end
279
540
 
280
- def merge(trackable, delay_error: false)
281
- lift(Operations::Merge.new(trackable, delay_error: delay_error))
541
+ def merge(*trackables, delay_error: false)
542
+ lift(Operations::Merge.new(trackables, delay_error: delay_error))
282
543
  end
283
544
 
284
545
  def buffer(count: nil, delay: nil)
@@ -289,47 +550,131 @@ module Reacto
289
550
  buffer(delay: delay)
290
551
  end
291
552
 
292
- def throttle(delay)
293
- lift(Operations::Throttle.new(delay))
553
+ def delay_each(delay)
554
+ lift(Operations::DelayEach.new(delay))
294
555
  end
295
556
 
296
- def cache(type: :memory, **settings)
297
- settings ||= {}
298
- lift(Operations::Cache.new(type: type, **settings))
557
+ def throttle(delay)
558
+ lift(Operations::Throttle.new(delay))
299
559
  end
300
560
 
301
- def depend_on(trackable, key: :data, accumulator: nil, &block)
561
+ def depend_on(trackable, key: :data, &block)
302
562
  lift(Operations::DependOn.new(
303
- trackable, key: key, accumulator: (block_given? ? block : accumulator)
563
+ trackable, key: key, accumulator: block
304
564
  ))
305
565
  end
306
566
 
307
- def label(labeling_action = nil, executor: nil, &block)
308
- lift(Operations::Label.new(
309
- block_given? ? block : labeling_action, executor
310
- ))
567
+ def group_by_label(executor: nil, &block)
568
+ lift(Operations::GroupByLabel.new(block, executor))
311
569
  end
312
570
 
313
- def act(action = NO_ACTION, on: Operations::Act::ALL, &block)
314
- lift(Operations::Act.new(block_given? ? block : action, on))
571
+ def flatten_labeled(initial: NO_VALUE, &block)
572
+ lift(Operations::FlattenLabeled.new(block, initial))
573
+ end
574
+
575
+ def split_labeled(label, executor: nil, &block)
576
+ lift(Operations::SplitLabeled.new(label, block, executor))
577
+ end
578
+
579
+ def act(on: Operations::Act::ALL, &block)
580
+ lift(Operations::Act.new(block, on))
581
+ end
582
+
583
+ def retry(retries = 1)
584
+ lift(Operations::Retry.new(@behaviour, retries))
585
+ end
586
+
587
+ def retry_when(&block)
588
+ return self unless block_given?
589
+
590
+ lift(Operations::RetryWhen.new(@behaviour, block))
591
+ end
592
+
593
+ def rescue_and_replace_error(&block)
594
+ return self unless block_given?
595
+
596
+ lift(Operations::RescueAndReplaceError.new(block))
597
+ end
598
+
599
+ def rescue_and_replace_error_with(trackable)
600
+ rescue_and_replace_error { |_error| trackable }
601
+ end
602
+
603
+ def combine_last(*trackables, &block)
604
+ return self unless block_given?
605
+
606
+ self.class.combine_last(*([self] + trackables), &block)
607
+ end
608
+
609
+ def combine(*trackables, &block)
610
+ return self unless block_given?
611
+
612
+ self.class.combine(*([self] + trackables), &block)
613
+ end
614
+
615
+ def slice_after(pattern = NO_VALUE, &block)
616
+ slice(pattern, type: :after, &block)
617
+ end
618
+
619
+ def slice_before(pattern = NO_VALUE, &block)
620
+ slice(pattern, type: :before, &block)
621
+ end
622
+
623
+ def slice(pattern = NO_ACTION, type:, &block)
624
+ predicate =
625
+ if pattern != NO_VALUE
626
+ -> (val) { pattern === val }
627
+ else
628
+ block
629
+ end
630
+ lift(Operations::Slice.new(predicate, type: type))
631
+ end
632
+
633
+ def slice_when(&block)
634
+ lift(Operations::SliceWhen.new(block))
635
+ end
636
+
637
+ def zip(*trackables, &block)
638
+ self.class.zip(*([self] + trackables), &block)
315
639
  end
316
640
 
317
641
  def track_on(executor)
642
+ stored = EXECUTOR_ALIASES[executor]
643
+ executor = stored if stored
644
+
318
645
  lift(Operations::TrackOn.new(executor))
319
646
  end
320
647
 
321
648
  def execute_on(executor)
322
- self.class.new(@behaviour, executor)
649
+ stored = EXECUTOR_ALIASES[executor]
650
+ executor = stored if stored
651
+
652
+ self.class.new(executor, &@behaviour)
323
653
  end
324
654
 
325
655
  def await(subscription, timeout = nil)
656
+ return unless subscription.subscribed?
657
+
326
658
  latch = Concurrent::CountDownLatch.new(1)
327
659
  subscription.add(Subscriptions.on_close_and_error { latch.count_down })
660
+
328
661
  latch.wait(timeout)
662
+ rescue Exception => e
663
+ raise e unless e.message.include?('No live threads left')
329
664
  end
330
665
 
331
666
  alias_method :skip, :drop
332
667
  alias_method :skip_errors, :drop_errors
668
+ alias_method :collect, :map
669
+ alias_method :collect_concat, :flat_map
670
+ alias_method :detect, :find
671
+ alias_method :each, :on
672
+ alias_method :each_entry, :on
673
+ alias_method :combine_latest, :combine
674
+ alias_method :group_by, :group_by_label
675
+ alias_method :find_all, :select
676
+ alias_method :'member?', :'include?'
677
+ alias_method :reduce, :inject
333
678
 
334
679
  def do_track(subscription)
335
680
  if @executor
@@ -339,8 +684,28 @@ module Reacto
339
684
  end
340
685
  end
341
686
 
687
+ protected
688
+
689
+ def create_lifted(&block)
690
+ self.class.new(@executor, &block)
691
+ end
692
+
342
693
  private
343
694
 
695
+ def select_map(predicate, &block)
696
+ result = select(&predicate)
697
+ result = result.map(&block) if block_given?
698
+
699
+ result
700
+ end
701
+
702
+ def retrieve_executor(executor)
703
+ return nil if executor.nil?
704
+
705
+ stored = EXECUTOR_ALIASES[executor]
706
+ stored ? stored : executor
707
+ end
708
+
344
709
  def lift_behaviour(lifted_tracker_subscription)
345
710
  begin
346
711
  lifted_tracker_subscription.on_open