callable_tree 0.1.1 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea52457dd99aca79ec45ac97716f53824b14dde4d479486b9d9fa6571a714c76
4
- data.tar.gz: 4fa5ee37a4a061b7c92f3373131b588bd30f12a351f344646dab2ec2d1089718
3
+ metadata.gz: 23417eaf997718c6d0e9b5fc52dea3fe4923ca9b426d9d41e57b5e27015d239a
4
+ data.tar.gz: 34be74baf1014e41efbe0073fedfdf76dc02444c927bea2b089d9457842c04b8
5
5
  SHA512:
6
- metadata.gz: 5f6e91b80e81f89d0e8f1c15ac5043de2b100245232de6b3584e900d1378952ad91442e4a55f80c59cfe8a01e5a9b34cf277b3a213290cf0b5aa8e0985edc7ca
7
- data.tar.gz: 2a89b0e60761fa826c1e083b34f24138693c9f44743c0c3c018f6dcbf330a6471481421dc9c99b4f4d5ae2f410aaeec293bed4ed46f10f28482280ea582f895a
6
+ metadata.gz: 686e18552c5decad326d7bd0775a2be0b3726245edea1ccf2dbdf0ca3e264ec88883bf79cea5af873b074feff1b63e49b59522b5f3b7fc00e06122d494277cea
7
+ data.tar.gz: 7db8a88a1bc19677f453237dd15137da05f3fdb2ddb25aeedd11df82e6afd7c769d3da3c61e9905c40285d35ea54059c6749b4f32a354fc542d7bd26fdd8992f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.1] - 2021-07-24
4
+ - Add `CallableTree::Node::root?`.
5
+ - Add `CallableTree::Node::Internal#seek!` that make destructive change.
6
+ - Add `CallableTree::Node::Internal#broadcast!` that make destructive change.
7
+ - Add `CallableTree::Node::Internal#compose!` that make destructive change.
8
+
9
+ ## [0.2.0] - 2021-06-15
10
+ - Change `CallableTree::Node::Internal#append` to return a new instance.
11
+ To keep the same behavior as the older version, use `CallableTree::Node::External#append!` that make destructive change.
12
+ - Remove `CallableTree::Node::Internal#<<`. Use `CallableTree::Node::External#append!` instead.
13
+ - Change `CallableTree::Node::External#verbosify` to return a new instance.
14
+ To keep the same behavior as the older version, use `CallableTree::Node::External#verbosify!` that make destructive change.
15
+
16
+ ## [0.1.3] - 2021-06-12
17
+ - Minor improvements
18
+
19
+ ## [0.1.2] - 2021-05-29
20
+
21
+ - Add `CallableTree::Node::Internal#compose` (experimental)
22
+
3
23
  ## [0.1.1] - 2021-05-27
4
24
 
5
25
  - Add `CallableTree::Node::Internal#broadcast` (experimental)
data/Gemfile.lock CHANGED
@@ -1,13 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- callable_tree (0.1.1)
4
+ callable_tree (0.2.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
9
  diff-lcs (1.4.4)
10
- rake (13.0.3)
10
+ rake (13.0.6)
11
11
  rspec (3.10.0)
12
12
  rspec-core (~> 3.10.0)
13
13
  rspec-expectations (~> 3.10.0)
data/README.md CHANGED
@@ -18,19 +18,22 @@ Or install it yourself as:
18
18
 
19
19
  ## Usage
20
20
 
21
+ Builds a tree by linking instances of the nodes. The `call` method of the node where the `match?` method returns a truthy value is called in a chain from the root node to the leaf node.
22
+
21
23
  - `CallableTree::Node::Internal`
22
- - This `module` is used to define a node that can have child nodes.
24
+ - This `module` is used to define a node that can have child nodes. An instance of this node has several strategies. The strategy can be changed by calling the method of the instance.
23
25
  - `CallableTree::Node::External`
24
26
  - This `module` is used to define a leaf node that cannot have child nodes.
25
27
  - `CallableTree::Node::Root`
26
28
  - This `class` includes `CallableTree::Node::Internal`. When there is no need to customize the internal node, use this `class`.
27
29
 
28
- Builds a tree by linking instances of the nodes. The `call` method of the node where the `match?` method returns a truthy value is called in a chain from the root node to the leaf node.
29
- If the `call` method returns a value other than `nil`, the next sibling node does not be called. This behavior is changeable by overriding the `terminate?` method.
30
-
31
30
  ### Basic
32
31
 
33
- `examples/example1.rb`:
32
+ #### `CallableTree::Node::Internal#seek` (default)
33
+
34
+ This strategy does not call the next sibling node if the `call` method of the current node returns a value other than `nil`. This behavior is changeable by overriding the `terminate?` method.
35
+
36
+ `examples/internal-seek.rb`:
34
37
  ```ruby
35
38
  module Node
36
39
  module JSON
@@ -123,16 +126,17 @@ module Node
123
126
  end
124
127
  end
125
128
 
129
+ # The `seek` method call can be omitted since it is the default strategy.
126
130
  tree = CallableTree::Node::Root.new.append(
127
131
  Node::JSON::Parser.new.append(
128
132
  Node::JSON::Scraper.new(type: :animals),
129
133
  Node::JSON::Scraper.new(type: :fruits)
130
- ),
134
+ ),#.seek,
131
135
  Node::XML::Parser.new.append(
132
136
  Node::XML::Scraper.new(type: :animals),
133
137
  Node::XML::Scraper.new(type: :fruits)
134
- )
135
- )
138
+ )#.seek
139
+ )#.seek
136
140
 
137
141
  Dir.glob(__dir__ + '/docs/*') do |file|
138
142
  options = { foo: :bar }
@@ -141,9 +145,9 @@ Dir.glob(__dir__ + '/docs/*') do |file|
141
145
  end
142
146
  ```
143
147
 
144
- Run `examples/example1.rb`:
148
+ Run `examples/internal-seek.rb`:
145
149
  ```sh
146
- % ruby examples/example1.rb
150
+ % ruby examples/internal-seek.rb
147
151
  {"Dog"=>"🐶", "Cat"=>"🐱"}
148
152
  ---
149
153
  {"Dog"=>"🐶", "Cat"=>"🐱"}
@@ -154,13 +158,121 @@ Run `examples/example1.rb`:
154
158
  ---
155
159
  ```
156
160
 
161
+ #### `CallableTree::Node::Internal#broadcast`
162
+
163
+ This strategy calls all child nodes of the internal node and ignores their `terminate?` methods, and then outputs their results as array.
164
+
165
+ `examples/internal-broadcast.rb`:
166
+ ```ruby
167
+ module Node
168
+ class LessThan
169
+ include CallableTree::Node::Internal
170
+
171
+ def initialize(num)
172
+ @num = num
173
+ end
174
+
175
+ def match?(input)
176
+ super && input < @num
177
+ end
178
+ end
179
+ end
180
+
181
+ tree = CallableTree::Node::Root.new.append(
182
+ Node::LessThan.new(5).append(
183
+ lambda { |input, **| input * 2 }, # anonymous external node
184
+ lambda { |input, **| input + 1 } # anonymous external node
185
+ ).broadcast,
186
+ Node::LessThan.new(10).append(
187
+ lambda { |input, **| input * 3 }, # anonymous external node
188
+ lambda { |input, **| input - 1 } # anonymous external node
189
+ ).broadcast
190
+ ).broadcast
191
+
192
+ (0..10).each do |input|
193
+ output = tree.call(input)
194
+ puts "#{input} -> #{output}"
195
+ end
196
+
197
+ ```
198
+
199
+ Run `examples/internal-broadcast.rb`:
200
+ ```sh
201
+ % ruby examples/internal-broadcast.rb
202
+ 0 -> [[0, 1], [0, -1]]
203
+ 1 -> [[2, 2], [3, 0]]
204
+ 2 -> [[4, 3], [6, 1]]
205
+ 3 -> [[6, 4], [9, 2]]
206
+ 4 -> [[8, 5], [12, 3]]
207
+ 5 -> [nil, [15, 4]]
208
+ 6 -> [nil, [18, 5]]
209
+ 7 -> [nil, [21, 6]]
210
+ 8 -> [nil, [24, 7]]
211
+ 9 -> [nil, [27, 8]]
212
+ 10 -> [nil, nil]
213
+ ```
214
+
215
+ #### `CallableTree::Node::Internal#compose`
216
+
217
+ This strategy calls all child nodes of the internal node in order to input the output of the previous node to the next node and ignores their `terminate?` methods, and then outputs a single result.
218
+
219
+ `examples/internal-compose.rb`:
220
+ ```ruby
221
+ module Node
222
+ class LessThan
223
+ include CallableTree::Node::Internal
224
+
225
+ def initialize(num)
226
+ @num = num
227
+ end
228
+
229
+ def match?(input)
230
+ super && input < @num
231
+ end
232
+ end
233
+ end
234
+
235
+ tree = CallableTree::Node::Root.new.append(
236
+ Node::LessThan.new(5).append(
237
+ proc { |input| input * 2 }, # anonymous external node
238
+ proc { |input| input + 1 } # anonymous external node
239
+ ).compose,
240
+ Node::LessThan.new(10).append(
241
+ proc { |input| input * 3 }, # anonymous external node
242
+ proc { |input| input - 1 } # anonymous external node
243
+ ).compose
244
+ ).compose
245
+
246
+ (0..10).each do |input|
247
+ output = tree.call(input)
248
+ puts "#{input} -> #{output}"
249
+ end
250
+
251
+ ```
252
+
253
+ Run `examples/internal-compose.rb`:
254
+ ```sh
255
+ % ruby examples/internal-compose.rb
256
+ 0 -> 2
257
+ 1 -> 8
258
+ 2 -> 14
259
+ 3 -> 20
260
+ 4 -> 26
261
+ 5 -> 14
262
+ 6 -> 17
263
+ 7 -> 20
264
+ 8 -> 23
265
+ 9 -> 26
266
+ 10 -> 10
267
+ ```
268
+
157
269
  ### Advanced
158
270
 
159
271
  #### `CallableTree::Node::External#verbosify`
160
272
 
161
- If you want verbose result, call it.
273
+ If you want verbose output results, call this method.
162
274
 
163
- `examples/example2.rb`:
275
+ `examples/external-verbosify.rb`:
164
276
  ```ruby
165
277
  ...
166
278
 
@@ -178,9 +290,9 @@ tree = CallableTree::Node::Root.new.append(
178
290
  ...
179
291
  ```
180
292
 
181
- Run `examples/example2.rb`:
293
+ Run `examples/external-verbosify.rb`:
182
294
  ```sh
183
- % ruby examples/example2.rb
295
+ % ruby examples/external-verbosify.rb
184
296
  #<struct CallableTree::Node::External::Output
185
297
  value={"Dog"=>"🐶", "Cat"=>"🐱"},
186
298
  options={:foo=>:bar},
@@ -208,9 +320,9 @@ You can work around it by overriding the `identity` method of the node.
208
320
 
209
321
  #### `CallableTree::Node#identity`
210
322
 
211
- If you want to customize the node identity, override it.
323
+ If you want to customize the node identity, override this method.
212
324
 
213
- `examples/example3.rb`:
325
+ `examples/identity.rb`:
214
326
  ```ruby
215
327
  module Node
216
328
  class Identity
@@ -266,9 +378,9 @@ end
266
378
  ...
267
379
  ```
268
380
 
269
- Run `examples/example3.rb`:
381
+ Run `examples/identity.rb`:
270
382
  ```sh
271
- % ruby examples/example3.rb
383
+ % ruby examples/identity.rb
272
384
  #<struct CallableTree::Node::External::Output
273
385
  value={"Dog"=>"🐶", "Cat"=>"🐱"},
274
386
  options={:foo=>:bar},
@@ -315,7 +427,7 @@ Run `examples/example3.rb`:
315
427
 
316
428
  This is an example of logging.
317
429
 
318
- `examples/example4.rb`:
430
+ `examples/logging.rb`:
319
431
  ```ruby
320
432
  module Node
321
433
  module Logging
@@ -388,9 +500,9 @@ end
388
500
  ...
389
501
  ```
390
502
 
391
- Run `examples/example4.rb`:
503
+ Run `examples/logging.rb`:
392
504
  ```sh
393
- % ruby examples/example4.rb
505
+ % ruby examples/logging.rb
394
506
  * Node::JSON::Parser: [matched: true]
395
507
  * Node::JSON::Scraper(animals): [matched: true]
396
508
  Input : {"animals"=>[{"name"=>"Dog", "emoji"=>"🐶"}, {"name"=>"Cat", "emoji"=>"🐱"}]}
@@ -455,7 +567,7 @@ Run `examples/example4.rb`:
455
567
 
456
568
  #### `CallableTree::Node::Hooks::Call` (experimental)
457
569
 
458
- `examples/example5.rb`:
570
+ `examples/hooks-call.rb`:
459
571
  ```ruby
460
572
  module Node
461
573
  class HooksSample
@@ -470,6 +582,7 @@ Node::HooksSample.new
470
582
  input + 1
471
583
  end
472
584
  .append(
585
+ # anonymous external node
473
586
  lambda do |input, **options|
474
587
  puts "external input: #{input}"
475
588
  input * 2
@@ -492,9 +605,9 @@ Node::HooksSample.new
492
605
  end
493
606
  ```
494
607
 
495
- Run `examples/example5.rb`:
608
+ Run `examples/hooks-call.rb`:
496
609
  ```sh
497
- % ruby examples/example5.rb
610
+ % ruby examples/hooks-call.rb
498
611
  before_call input: 1
499
612
  external input: 2
500
613
  around_call input: 2
@@ -503,41 +616,6 @@ after_call output: 8
503
616
  result: 16
504
617
  ```
505
618
 
506
- #### `CallableTree::Node::Internal#broadcast` (experimental)
507
-
508
- If you want to call and output all child nodes of the internal node, call it. Broadcast strategy ignores the `terminate?` method of the child node.
509
-
510
- `examples/example6.rb`:
511
- ```ruby
512
- ...
513
-
514
- tree = CallableTree::Node::Root.new.append(
515
- Node::JSON::Parser.new.append(
516
- Node::JSON::Scraper.new(type: :animals),
517
- Node::JSON::Scraper.new(type: :fruits)
518
- ).broadcast,
519
- Node::XML::Parser.new.append(
520
- Node::XML::Scraper.new(type: :animals),
521
- Node::XML::Scraper.new(type: :fruits)
522
- ).broadcast
523
- )
524
-
525
- ...
526
- ```
527
-
528
- Run `examples/example6.rb`:
529
- ```sh
530
- % ruby examples/example6.rb
531
- [{"Dog"=>"🐶", "Cat"=>"🐱"}, nil]
532
- ---
533
- [{"Dog"=>"🐶", "Cat"=>"🐱"}, nil]
534
- ---
535
- [nil, {"Red Apple"=>"🍎", "Green Apple"=>"🍏"}]
536
- ---
537
- [nil, {"Red Apple"=>"🍎", "Green Apple"=>"🍏"}]
538
- ---
539
- ```
540
-
541
619
  ## Contributing
542
620
 
543
621
  Bug reports and pull requests are welcome on GitHub at https://github.com/jsmmr/callable_tree.
@@ -8,15 +8,15 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ['jsmmr']
9
9
  spec.email = ['jsmmr@icloud.com']
10
10
 
11
- spec.summary = 'Builds a tree by linking callable nodes. The nodes that match the calling condition are called in a chain from the root node to the leaf node. These are like nested case statements.'
12
- spec.description = 'Builds a tree by linking callable nodes. The nodes that match the calling condition are called in a chain from the root node to the leaf node. These are like nested case statements.'
11
+ spec.summary = 'Builds a tree by linking callable nodes. The nodes that match the conditions are called in a chain from the root node to the leaf node. These are like nested `if` or `case` expressions.'
12
+ spec.description = 'Builds a tree by linking callable nodes. The nodes that match the conditions are called in a chain from the root node to the leaf node. These are like nested `if` or `case` expressions.'
13
13
  spec.homepage = 'https://github.com/jsmmr/ruby_callable_tree'
14
14
  spec.license = 'MIT'
15
15
  spec.required_ruby_version = Gem::Requirement.new('>= 2.4.0')
16
16
 
17
17
  spec.metadata['homepage_uri'] = spec.homepage
18
18
  spec.metadata['source_code_uri'] = 'https://github.com/jsmmr/ruby_callable_tree'
19
- spec.metadata['changelog_uri'] = 'https://github.com/jsmmr/ruby_callable_tree/blob/v#{version}/CHANGELOG.md'
19
+ spec.metadata['changelog_uri'] = "https://github.com/jsmmr/ruby_callable_tree/blob/v#{spec.version}/CHANGELOG.md"
20
20
 
21
21
  # Specify which files should be added to the gem when it is released.
22
22
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
File without changes
@@ -13,6 +13,7 @@ Node::HooksSample.new
13
13
  input + 1
14
14
  end
15
15
  .append(
16
+ # anonymous external node
16
17
  lambda do |input, **options|
17
18
  puts "external input: #{input}"
18
19
  input * 2
File without changes
@@ -0,0 +1,31 @@
1
+ require 'callable_tree'
2
+
3
+ module Node
4
+ class LessThan
5
+ include CallableTree::Node::Internal
6
+
7
+ def initialize(num)
8
+ @num = num
9
+ end
10
+
11
+ def match?(input)
12
+ super && input < @num
13
+ end
14
+ end
15
+ end
16
+
17
+ tree = CallableTree::Node::Root.new.append(
18
+ Node::LessThan.new(5).append(
19
+ lambda { |input, **| input * 2 }, # anonymous external node
20
+ lambda { |input, **| input + 1 } # anonymous external node
21
+ ).broadcast,
22
+ Node::LessThan.new(10).append(
23
+ lambda { |input, **| input * 3 }, # anonymous external node
24
+ lambda { |input, **| input - 1 } # anonymous external node
25
+ ).broadcast
26
+ ).broadcast
27
+
28
+ (0..10).each do |input|
29
+ output = tree.call(input)
30
+ puts "#{input} -> #{output}"
31
+ end
@@ -0,0 +1,31 @@
1
+ require 'callable_tree'
2
+
3
+ module Node
4
+ class LessThan
5
+ include CallableTree::Node::Internal
6
+
7
+ def initialize(num)
8
+ @num = num
9
+ end
10
+
11
+ def match?(input)
12
+ super && input < @num
13
+ end
14
+ end
15
+ end
16
+
17
+ tree = CallableTree::Node::Root.new.append(
18
+ Node::LessThan.new(5).append(
19
+ proc { |input| input * 2 }, # anonymous external node
20
+ proc { |input| input + 1 } # anonymous external node
21
+ ).compose,
22
+ Node::LessThan.new(10).append(
23
+ proc { |input| input * 3 }, # anonymous external node
24
+ proc { |input| input - 1 } # anonymous external node
25
+ ).compose
26
+ ).compose
27
+
28
+ (0..10).each do |input|
29
+ output = tree.call(input)
30
+ puts "#{input} -> #{output}"
31
+ end
File without changes
File without changes
data/lib/callable_tree.rb CHANGED
@@ -8,8 +8,9 @@ require 'forwardable'
8
8
  require_relative 'callable_tree/version'
9
9
  require_relative 'callable_tree/node'
10
10
  require_relative 'callable_tree/node/hooks/call'
11
- require_relative 'callable_tree/node/internal/broadcast'
12
- require_relative 'callable_tree/node/internal/seek'
11
+ require_relative 'callable_tree/node/internal/strategy/broadcast'
12
+ require_relative 'callable_tree/node/internal/strategy/seek'
13
+ require_relative 'callable_tree/node/internal/strategy/compose'
13
14
  require_relative 'callable_tree/node/external/verbose'
14
15
  require_relative 'callable_tree/node/external'
15
16
  require_relative 'callable_tree/node/internal'
@@ -4,12 +4,16 @@ module CallableTree
4
4
  module Node
5
5
  attr_reader :parent
6
6
 
7
+ def root?
8
+ parent.nil?
9
+ end
10
+
7
11
  def ancestors
8
12
  ::Enumerator.new do |y|
9
13
  node = self
10
14
  loop do
11
15
  y << node
12
- break unless node = node&.parent
16
+ break unless node = node.parent
13
17
  end
14
18
  end
15
19
  end
@@ -23,7 +27,7 @@ module CallableTree
23
27
  end
24
28
 
25
29
  def depth
26
- parent.nil? ? 0 : parent.depth + 1
30
+ root? ? 0 : parent.depth + 1
27
31
  end
28
32
 
29
33
  def match?(_input = nil, **_options)
@@ -9,38 +9,58 @@ module CallableTree
9
9
  Proxy.new(callable)
10
10
  end
11
11
 
12
- def self.proxified?(node)
13
- node.is_a?(Proxy)
12
+ def proxified?
13
+ false
14
14
  end
15
15
 
16
- def self.unproxify(node)
17
- node.callable
16
+ def verbosified?
17
+ false
18
18
  end
19
19
 
20
20
  def verbosify
21
+ clone.tap do |node|
22
+ node.extend Verbose
23
+ end
24
+ end
25
+
26
+ def verbosify!
21
27
  extend Verbose
22
28
  self
23
29
  end
24
30
 
25
31
  def identity
26
- if External.proxified?(self)
27
- External.unproxify(self)
32
+ if proxified?
33
+ unproxify
28
34
  else
29
35
  self
30
36
  end
31
37
  .class
32
38
  end
33
39
 
40
+ private
41
+
42
+ def initialize_copy(_node)
43
+ super
44
+ self.parent = nil
45
+ end
46
+
34
47
  class Proxy
35
48
  extend ::Forwardable
36
49
  include External
37
50
 
38
51
  def_delegators :@callable, :call
39
- attr_reader :callable
40
52
 
41
53
  def initialize(callable)
42
54
  @callable = callable
43
55
  end
56
+
57
+ def proxified?
58
+ true
59
+ end
60
+
61
+ def unproxify
62
+ @callable
63
+ end
44
64
  end
45
65
 
46
66
  private_constant :Proxy
@@ -6,6 +6,10 @@ module CallableTree
6
6
  Output = Struct.new(:value, :options, :routes)
7
7
 
8
8
  module Verbose
9
+ def verbosified?
10
+ true
11
+ end
12
+
9
13
  def call(input = nil, **options)
10
14
  output = super(input, **options)
11
15
  routes = self.routes
@@ -9,20 +9,17 @@ module CallableTree
9
9
  @children ||= []
10
10
  end
11
11
 
12
- def <<(callable)
13
- children <<
14
- if callable.is_a?(Node)
15
- callable.clone
16
- else
17
- External.proxify(callable)
18
- end
19
- .tap { |node| node.send(:parent=, self) }
20
-
21
- self
12
+ def append(*callables)
13
+ clone.tap do |node|
14
+ node.append!(*callables)
15
+ end
22
16
  end
23
17
 
24
- def append(*callables)
25
- callables.each { |callable| self.<<(callable) }
18
+ def append!(*callables)
19
+ callables
20
+ .map { |callable| nodeify(callable) }
21
+ .tap { |nodes| children.push(*nodes) } # Use Array#push for Ruby 2.4
22
+
26
23
  self
27
24
  end
28
25
 
@@ -35,35 +32,70 @@ module CallableTree
35
32
  end
36
33
 
37
34
  def seek
38
- if strategy.is_a?(Seek)
35
+ if strategy.is_a?(Strategy::Seek)
39
36
  self
40
37
  else
41
38
  clone.tap do |node|
42
- node.send(:strategy=, Seek.new)
39
+ node.send(:strategy=, Strategy::Seek.new)
43
40
  end
44
41
  end
45
42
  end
46
43
 
44
+ def seek!
45
+ self.strategy = Strategy::Seek.new unless strategy.is_a?(Strategy::Seek)
46
+ self
47
+ end
48
+
47
49
  def broadcast
48
- if strategy.is_a?(Broadcast)
50
+ if strategy.is_a?(Strategy::Broadcast)
49
51
  self
50
52
  else
51
53
  clone.tap do |node|
52
- node.send(:strategy=, Broadcast.new)
54
+ node.send(:strategy=, Strategy::Broadcast.new)
53
55
  end
54
56
  end
55
57
  end
56
58
 
59
+ def broadcast!
60
+ self.strategy = Strategy::Broadcast.new unless strategy.is_a?(Strategy::Broadcast)
61
+ self
62
+ end
63
+
64
+ def compose
65
+ if strategy.is_a?(Strategy::Compose)
66
+ self
67
+ else
68
+ clone.tap do |node|
69
+ node.send(:strategy=, Strategy::Compose.new)
70
+ end
71
+ end
72
+ end
73
+
74
+ def compose!
75
+ self.strategy = Strategy::Compose.new unless strategy.is_a?(Strategy::Compose)
76
+ self
77
+ end
78
+
57
79
  private
58
80
 
59
81
  attr_writer :children, :strategy
60
82
 
83
+ def nodeify(callable)
84
+ if callable.is_a?(Node)
85
+ callable.clone
86
+ else
87
+ External.proxify(callable)
88
+ end
89
+ .tap { |node| node.send(:parent=, self) }
90
+ end
91
+
61
92
  def strategy
62
- @strategy ||= Seek.new
93
+ @strategy ||= Strategy::Seek.new
63
94
  end
64
95
 
65
96
  def initialize_copy(_node)
66
97
  super
98
+ self.parent = nil
67
99
  self.children = children.map do |node|
68
100
  node.clone.tap { |new_node| new_node.send(:parent=, self) }
69
101
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ module Node
5
+ module Internal
6
+ module Strategy
7
+ class Broadcast
8
+ def call(nodes, input:, options:)
9
+ nodes.map do |node|
10
+ node.call(input, **options) if node.match?(input, **options)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ module Node
5
+ module Internal
6
+ module Strategy
7
+ class Compose
8
+ def call(nodes, input:, options:)
9
+ nodes.reduce(input) do |input, node|
10
+ if node.match?(input, **options)
11
+ node.call(input, **options)
12
+ else
13
+ input
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ module Node
5
+ module Internal
6
+ module Strategy
7
+ class Seek
8
+ def call(nodes, input:, options:)
9
+ nodes
10
+ .lazy
11
+ .select { |node| node.match?(input, **options) }
12
+ .map do |node|
13
+ output = node.call(input, **options)
14
+ terminated = node.terminate?(output, **options)
15
+ [output, terminated]
16
+ end
17
+ .select { |_output, terminated| terminated }
18
+ .map { |output, _terminated| output }
19
+ .first
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CallableTree
4
- VERSION = '0.1.1'
4
+ VERSION = '0.2.1'
5
5
  end
metadata CHANGED
@@ -1,18 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: callable_tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - jsmmr
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-27 00:00:00.000000000 Z
11
+ date: 2021-07-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Builds a tree by linking callable nodes. The nodes that match the calling
14
- condition are called in a chain from the root node to the leaf node. These are like
15
- nested case statements.
13
+ description: Builds a tree by linking callable nodes. The nodes that match the conditions
14
+ are called in a chain from the root node to the leaf node. These are like nested
15
+ `if` or `case` expressions.
16
16
  email:
17
17
  - jsmmr@icloud.com
18
18
  executables: []
@@ -36,20 +36,22 @@ files:
36
36
  - examples/docs/animals.xml
37
37
  - examples/docs/fruits.json
38
38
  - examples/docs/fruits.xml
39
- - examples/example1.rb
40
- - examples/example2.rb
41
- - examples/example3.rb
42
- - examples/example4.rb
43
- - examples/example5.rb
44
- - examples/example6.rb
39
+ - examples/external-verbosify.rb
40
+ - examples/hooks-call.rb
41
+ - examples/identity.rb
42
+ - examples/internal-broadcast.rb
43
+ - examples/internal-compose.rb
44
+ - examples/internal-seek.rb
45
+ - examples/logging.rb
45
46
  - lib/callable_tree.rb
46
47
  - lib/callable_tree/node.rb
47
48
  - lib/callable_tree/node/external.rb
48
49
  - lib/callable_tree/node/external/verbose.rb
49
50
  - lib/callable_tree/node/hooks/call.rb
50
51
  - lib/callable_tree/node/internal.rb
51
- - lib/callable_tree/node/internal/broadcast.rb
52
- - lib/callable_tree/node/internal/seek.rb
52
+ - lib/callable_tree/node/internal/strategy/broadcast.rb
53
+ - lib/callable_tree/node/internal/strategy/compose.rb
54
+ - lib/callable_tree/node/internal/strategy/seek.rb
53
55
  - lib/callable_tree/node/root.rb
54
56
  - lib/callable_tree/version.rb
55
57
  homepage: https://github.com/jsmmr/ruby_callable_tree
@@ -58,7 +60,7 @@ licenses:
58
60
  metadata:
59
61
  homepage_uri: https://github.com/jsmmr/ruby_callable_tree
60
62
  source_code_uri: https://github.com/jsmmr/ruby_callable_tree
61
- changelog_uri: https://github.com/jsmmr/ruby_callable_tree/blob/v#{version}/CHANGELOG.md
63
+ changelog_uri: https://github.com/jsmmr/ruby_callable_tree/blob/v0.2.1/CHANGELOG.md
62
64
  post_install_message:
63
65
  rdoc_options: []
64
66
  require_paths:
@@ -77,7 +79,7 @@ requirements: []
77
79
  rubygems_version: 3.2.16
78
80
  signing_key:
79
81
  specification_version: 4
80
- summary: Builds a tree by linking callable nodes. The nodes that match the calling
81
- condition are called in a chain from the root node to the leaf node. These are like
82
- nested case statements.
82
+ summary: Builds a tree by linking callable nodes. The nodes that match the conditions
83
+ are called in a chain from the root node to the leaf node. These are like nested
84
+ `if` or `case` expressions.
83
85
  test_files: []
data/examples/example6.rb DELETED
@@ -1,101 +0,0 @@
1
- require 'callable_tree'
2
- require 'json'
3
- require 'rexml/document'
4
-
5
- module Node
6
- module JSON
7
- class Parser
8
- include CallableTree::Node::Internal
9
-
10
- def match?(input, **options)
11
- File.extname(input) == '.json'
12
- end
13
-
14
- def call(input, **options)
15
- File.open(input) do |file|
16
- json = ::JSON.load(file)
17
- super(json, **options)
18
- end
19
- end
20
-
21
- def terminate?(_output, **)
22
- true
23
- end
24
- end
25
-
26
- class Scraper
27
- include CallableTree::Node::External
28
-
29
- def initialize(type:)
30
- @type = type
31
- end
32
-
33
- def match?(input, **options)
34
- !!input[@type.to_s]
35
- end
36
-
37
- def call(input, **options)
38
- input[@type.to_s]
39
- .map { |element| [element['name'], element['emoji']] }
40
- .to_h
41
- end
42
- end
43
- end
44
-
45
- module XML
46
- class Parser
47
- include CallableTree::Node::Internal
48
-
49
- def match?(input, **options)
50
- File.extname(input) == '.xml'
51
- end
52
-
53
- def call(input, **options)
54
- File.open(input) do |file|
55
- super(REXML::Document.new(file), **options)
56
- end
57
- end
58
-
59
- def terminate?(_output, **)
60
- true
61
- end
62
- end
63
-
64
- class Scraper
65
- include CallableTree::Node::External
66
-
67
- def initialize(type:)
68
- @type = type
69
- end
70
-
71
- def match?(input, **options)
72
- !input.get_elements("//#{@type}").empty?
73
- end
74
-
75
- def call(input, **options)
76
- input
77
- .get_elements("//#{@type}")
78
- .first
79
- .map { |element| [element['name'], element['emoji']] }
80
- .to_h
81
- end
82
- end
83
- end
84
- end
85
-
86
- tree = CallableTree::Node::Root.new.append(
87
- Node::JSON::Parser.new.append(
88
- Node::JSON::Scraper.new(type: :animals),
89
- Node::JSON::Scraper.new(type: :fruits)
90
- ).broadcast,
91
- Node::XML::Parser.new.append(
92
- Node::XML::Scraper.new(type: :animals),
93
- Node::XML::Scraper.new(type: :fruits)
94
- ).broadcast
95
- )
96
-
97
- Dir.glob(__dir__ + '/docs/*') do |file|
98
- options = { foo: :bar }
99
- pp tree.call(file, **options)
100
- puts '---'
101
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CallableTree
4
- module Node
5
- module Internal
6
- class Broadcast
7
- def call(nodes, input:, options:)
8
- nodes.map do |node|
9
- if node.match?(input, **options)
10
- node.call(input, **options)
11
- else
12
- nil
13
- end
14
- end
15
- end
16
- end
17
- end
18
- end
19
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CallableTree
4
- module Node
5
- module Internal
6
- class Seek
7
- def call(nodes, input:, options:)
8
- nodes
9
- .lazy
10
- .select { |node| node.match?(input, **options) }
11
- .map do |node|
12
- output = node.call(input, **options)
13
- terminated = node.terminate?(output, **options)
14
- [output, terminated]
15
- end
16
- .select { |_output, terminated| terminated }
17
- .map { |output, _terminated| output }
18
- .first
19
- end
20
- end
21
- end
22
- end
23
- end