callable_tree 0.1.1 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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