callable_tree 0.1.2 → 0.2.2

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: 822f8080ff2652895cd3cb7255d856eea72290564dbe7054d93c2fca01ac6149
4
- data.tar.gz: 783496e58325e0d5be2595b3c33b0acd1095c61400b89addc0ce9d8fc8e845db
3
+ metadata.gz: 8e5f087271cbaf840a0cb6557126331b67487c4f87e5843e3bcf900c54dc6e84
4
+ data.tar.gz: d1d1e0ac4a085dc26d1a2f6bec141ea92ea80efbfb43c8738e34d9ae9280836c
5
5
  SHA512:
6
- metadata.gz: '0381d33a9f1a82b524aa176574d24961e7585ebb4ea76185759be2d17d739b9c84612b0ed9f7f2323083a71515acbf6fa622b65528c5a903dd911147bebfb7c7'
7
- data.tar.gz: 9b715d2b2fd6ca25c1bf8c446574a2097271525facc306198695d01fbadffd2e7823866e16c58c83cb8a7f08dc8047909023ecc8256d083f07854b9c714a57ac
6
+ metadata.gz: 0a342b5fff3ce51c36bd4e4e1977f2023db7045bab0f19e794bc8a1c48442c184cedfcadad5a0b9956fe146a0904aaca584a6d23c5ac23c063dc53d8d260978a
7
+ data.tar.gz: 6fd3e33d25e549774488967d600ef236bff9e74bc750741eff9e5c6d3e5c59b15f2c956c1f25b97f5e9cd16becb7004e6df298adf04c01844aa300dc1e2e42b3
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.0.0
1
+ 3.0.2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.2] - 2021-10-24
4
+ - Add `CallableTree::Node::Internal#reject` to return a new node instance without rejected child nodes.
5
+ - Add `CallableTree::Node::Internal#reject!` to destructively reject child nodes.
6
+
7
+ ## [0.2.1] - 2021-07-24
8
+ - Add `CallableTree::Node#root?`.
9
+ - Add `CallableTree::Node::Internal#seek!` that make destructive change.
10
+ - Add `CallableTree::Node::Internal#broadcast!` that make destructive change.
11
+ - Add `CallableTree::Node::Internal#compose!` that make destructive change.
12
+
13
+ ## [0.2.0] - 2021-06-15
14
+ - Change `CallableTree::Node::Internal#append` to return a new instance.
15
+ To keep the same behavior as the older version, use `CallableTree::Node::External#append!` that make destructive change.
16
+ - Remove `CallableTree::Node::Internal#<<`. Use `CallableTree::Node::External#append!` instead.
17
+ - Change `CallableTree::Node::External#verbosify` to return a new instance.
18
+ To keep the same behavior as the older version, use `CallableTree::Node::External#verbosify!` that make destructive change.
19
+
20
+ ## [0.1.3] - 2021-06-12
21
+ - Minor improvements
22
+
3
23
  ## [0.1.2] - 2021-05-29
4
24
 
5
25
  - Add `CallableTree::Node::Internal#compose` (experimental)
data/Gemfile.lock CHANGED
@@ -1,13 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- callable_tree (0.1.2)
4
+ callable_tree (0.2.2)
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)
@@ -32,4 +32,4 @@ DEPENDENCIES
32
32
  rspec (~> 3.0)
33
33
 
34
34
  BUNDLED WITH
35
- 2.2.17
35
+ 2.2.22
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2021  
3
+ Copyright (c) 2021 jsmmr
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # CallableTree
2
2
 
3
+ [![build](https://github.com/jsmmr/ruby_callable_tree/actions/workflows/build.yml/badge.svg)](https://github.com/jsmmr/ruby_callable_tree/actions/workflows/build.yml)
4
+
3
5
  ## Installation
4
6
 
5
7
  Add this line to your application's Gemfile:
@@ -18,19 +20,22 @@ Or install it yourself as:
18
20
 
19
21
  ## Usage
20
22
 
23
+ 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.
24
+
21
25
  - `CallableTree::Node::Internal`
22
- - This `module` is used to define a node that can have child nodes.
26
+ - 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
27
  - `CallableTree::Node::External`
24
28
  - This `module` is used to define a leaf node that cannot have child nodes.
25
29
  - `CallableTree::Node::Root`
26
30
  - This `class` includes `CallableTree::Node::Internal`. When there is no need to customize the internal node, use this `class`.
27
31
 
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
32
  ### Basic
32
33
 
33
- `examples/example1.rb`:
34
+ #### `CallableTree::Node::Internal#seek` (default)
35
+
36
+ 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.
37
+
38
+ `examples/internal-seek.rb`:
34
39
  ```ruby
35
40
  module Node
36
41
  module JSON
@@ -123,16 +128,17 @@ module Node
123
128
  end
124
129
  end
125
130
 
131
+ # The `seek` method call can be omitted since it is the default strategy.
126
132
  tree = CallableTree::Node::Root.new.append(
127
133
  Node::JSON::Parser.new.append(
128
134
  Node::JSON::Scraper.new(type: :animals),
129
135
  Node::JSON::Scraper.new(type: :fruits)
130
- ),
136
+ ),#.seek,
131
137
  Node::XML::Parser.new.append(
132
138
  Node::XML::Scraper.new(type: :animals),
133
139
  Node::XML::Scraper.new(type: :fruits)
134
- )
135
- )
140
+ )#.seek
141
+ )#.seek
136
142
 
137
143
  Dir.glob(__dir__ + '/docs/*') do |file|
138
144
  options = { foo: :bar }
@@ -141,9 +147,9 @@ Dir.glob(__dir__ + '/docs/*') do |file|
141
147
  end
142
148
  ```
143
149
 
144
- Run `examples/example1.rb`:
150
+ Run `examples/internal-seek.rb`:
145
151
  ```sh
146
- % ruby examples/example1.rb
152
+ % ruby examples/internal-seek.rb
147
153
  {"Dog"=>"🐶", "Cat"=>"🐱"}
148
154
  ---
149
155
  {"Dog"=>"🐶", "Cat"=>"🐱"}
@@ -154,13 +160,121 @@ Run `examples/example1.rb`:
154
160
  ---
155
161
  ```
156
162
 
163
+ #### `CallableTree::Node::Internal#broadcast`
164
+
165
+ This strategy calls all child nodes of the internal node and ignores their `terminate?` methods, and then outputs their results as array.
166
+
167
+ `examples/internal-broadcast.rb`:
168
+ ```ruby
169
+ module Node
170
+ class LessThan
171
+ include CallableTree::Node::Internal
172
+
173
+ def initialize(num)
174
+ @num = num
175
+ end
176
+
177
+ def match?(input)
178
+ super && input < @num
179
+ end
180
+ end
181
+ end
182
+
183
+ tree = CallableTree::Node::Root.new.append(
184
+ Node::LessThan.new(5).append(
185
+ lambda { |input, **| input * 2 }, # anonymous external node
186
+ lambda { |input, **| input + 1 } # anonymous external node
187
+ ).broadcast,
188
+ Node::LessThan.new(10).append(
189
+ lambda { |input, **| input * 3 }, # anonymous external node
190
+ lambda { |input, **| input - 1 } # anonymous external node
191
+ ).broadcast
192
+ ).broadcast
193
+
194
+ (0..10).each do |input|
195
+ output = tree.call(input)
196
+ puts "#{input} -> #{output}"
197
+ end
198
+
199
+ ```
200
+
201
+ Run `examples/internal-broadcast.rb`:
202
+ ```sh
203
+ % ruby examples/internal-broadcast.rb
204
+ 0 -> [[0, 1], [0, -1]]
205
+ 1 -> [[2, 2], [3, 0]]
206
+ 2 -> [[4, 3], [6, 1]]
207
+ 3 -> [[6, 4], [9, 2]]
208
+ 4 -> [[8, 5], [12, 3]]
209
+ 5 -> [nil, [15, 4]]
210
+ 6 -> [nil, [18, 5]]
211
+ 7 -> [nil, [21, 6]]
212
+ 8 -> [nil, [24, 7]]
213
+ 9 -> [nil, [27, 8]]
214
+ 10 -> [nil, nil]
215
+ ```
216
+
217
+ #### `CallableTree::Node::Internal#compose`
218
+
219
+ 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.
220
+
221
+ `examples/internal-compose.rb`:
222
+ ```ruby
223
+ module Node
224
+ class LessThan
225
+ include CallableTree::Node::Internal
226
+
227
+ def initialize(num)
228
+ @num = num
229
+ end
230
+
231
+ def match?(input)
232
+ super && input < @num
233
+ end
234
+ end
235
+ end
236
+
237
+ tree = CallableTree::Node::Root.new.append(
238
+ Node::LessThan.new(5).append(
239
+ proc { |input| input * 2 }, # anonymous external node
240
+ proc { |input| input + 1 } # anonymous external node
241
+ ).compose,
242
+ Node::LessThan.new(10).append(
243
+ proc { |input| input * 3 }, # anonymous external node
244
+ proc { |input| input - 1 } # anonymous external node
245
+ ).compose
246
+ ).compose
247
+
248
+ (0..10).each do |input|
249
+ output = tree.call(input)
250
+ puts "#{input} -> #{output}"
251
+ end
252
+
253
+ ```
254
+
255
+ Run `examples/internal-compose.rb`:
256
+ ```sh
257
+ % ruby examples/internal-compose.rb
258
+ 0 -> 2
259
+ 1 -> 8
260
+ 2 -> 14
261
+ 3 -> 20
262
+ 4 -> 26
263
+ 5 -> 14
264
+ 6 -> 17
265
+ 7 -> 20
266
+ 8 -> 23
267
+ 9 -> 26
268
+ 10 -> 10
269
+ ```
270
+
157
271
  ### Advanced
158
272
 
159
273
  #### `CallableTree::Node::External#verbosify`
160
274
 
161
- If you want verbose result, call it.
275
+ If you want verbose output results, call this method.
162
276
 
163
- `examples/example2.rb`:
277
+ `examples/external-verbosify.rb`:
164
278
  ```ruby
165
279
  ...
166
280
 
@@ -178,9 +292,9 @@ tree = CallableTree::Node::Root.new.append(
178
292
  ...
179
293
  ```
180
294
 
181
- Run `examples/example2.rb`:
295
+ Run `examples/external-verbosify.rb`:
182
296
  ```sh
183
- % ruby examples/example2.rb
297
+ % ruby examples/external-verbosify.rb
184
298
  #<struct CallableTree::Node::External::Output
185
299
  value={"Dog"=>"🐶", "Cat"=>"🐱"},
186
300
  options={:foo=>:bar},
@@ -208,9 +322,9 @@ You can work around it by overriding the `identity` method of the node.
208
322
 
209
323
  #### `CallableTree::Node#identity`
210
324
 
211
- If you want to customize the node identity, override it.
325
+ If you want to customize the node identity, override this method.
212
326
 
213
- `examples/example3.rb`:
327
+ `examples/identity.rb`:
214
328
  ```ruby
215
329
  module Node
216
330
  class Identity
@@ -266,9 +380,9 @@ end
266
380
  ...
267
381
  ```
268
382
 
269
- Run `examples/example3.rb`:
383
+ Run `examples/identity.rb`:
270
384
  ```sh
271
- % ruby examples/example3.rb
385
+ % ruby examples/identity.rb
272
386
  #<struct CallableTree::Node::External::Output
273
387
  value={"Dog"=>"🐶", "Cat"=>"🐱"},
274
388
  options={:foo=>:bar},
@@ -315,7 +429,7 @@ Run `examples/example3.rb`:
315
429
 
316
430
  This is an example of logging.
317
431
 
318
- `examples/example4.rb`:
432
+ `examples/logging.rb`:
319
433
  ```ruby
320
434
  module Node
321
435
  module Logging
@@ -388,9 +502,9 @@ end
388
502
  ...
389
503
  ```
390
504
 
391
- Run `examples/example4.rb`:
505
+ Run `examples/logging.rb`:
392
506
  ```sh
393
- % ruby examples/example4.rb
507
+ % ruby examples/logging.rb
394
508
  * Node::JSON::Parser: [matched: true]
395
509
  * Node::JSON::Scraper(animals): [matched: true]
396
510
  Input : {"animals"=>[{"name"=>"Dog", "emoji"=>"🐶"}, {"name"=>"Cat", "emoji"=>"🐱"}]}
@@ -455,7 +569,7 @@ Run `examples/example4.rb`:
455
569
 
456
570
  #### `CallableTree::Node::Hooks::Call` (experimental)
457
571
 
458
- `examples/example5.rb`:
572
+ `examples/hooks-call.rb`:
459
573
  ```ruby
460
574
  module Node
461
575
  class HooksSample
@@ -493,9 +607,9 @@ Node::HooksSample.new
493
607
  end
494
608
  ```
495
609
 
496
- Run `examples/example5.rb`:
610
+ Run `examples/hooks-call.rb`:
497
611
  ```sh
498
- % ruby examples/example5.rb
612
+ % ruby examples/hooks-call.rb
499
613
  before_call input: 1
500
614
  external input: 2
501
615
  around_call input: 2
@@ -504,95 +618,6 @@ after_call output: 8
504
618
  result: 16
505
619
  ```
506
620
 
507
- #### `CallableTree::Node::Internal#broadcast` (experimental)
508
-
509
- If you want to call all child nodes of the internal node in order to output their results as array, call it. The `broadcast` strategy ignores the `terminate?` method of the nodes.
510
-
511
- `examples/example6.rb`:
512
- ```ruby
513
- ...
514
-
515
- tree = CallableTree::Node::Root.new.append(
516
- Node::JSON::Parser.new.append(
517
- Node::JSON::Scraper.new(type: :animals),
518
- Node::JSON::Scraper.new(type: :fruits)
519
- ).broadcast,
520
- Node::XML::Parser.new.append(
521
- Node::XML::Scraper.new(type: :animals),
522
- Node::XML::Scraper.new(type: :fruits)
523
- ).broadcast
524
- )
525
-
526
- ...
527
- ```
528
-
529
- Run `examples/example6.rb`:
530
- ```sh
531
- % ruby examples/example6.rb
532
- [{"Dog"=>"🐶", "Cat"=>"🐱"}, nil]
533
- ---
534
- [{"Dog"=>"🐶", "Cat"=>"🐱"}, nil]
535
- ---
536
- [nil, {"Red Apple"=>"🍎", "Green Apple"=>"🍏"}]
537
- ---
538
- [nil, {"Red Apple"=>"🍎", "Green Apple"=>"🍏"}]
539
- ---
540
- ```
541
-
542
- #### `CallableTree::Node::Internal#compose` (experimental)
543
-
544
- If you want to call all child nodes of the internal node in order to input the output of the previous node to the next node and output a single result , call it. The `compose` strategy ignores the `terminate?` method of the nodes.
545
-
546
- `examples/example7.rb`:
547
- ```ruby
548
- module Node
549
- class LessThan
550
- include CallableTree::Node::Internal
551
-
552
- def initialize(num)
553
- @num = num
554
- end
555
-
556
- def match?(input)
557
- super && input < @num
558
- end
559
- end
560
- end
561
-
562
- tree = CallableTree::Node::Root.new.append(
563
- Node::LessThan.new(5).append(
564
- proc { |input| input * 2 }, # anonymous external node
565
- proc { |input| input + 1 } # anonymous external node
566
- ).compose,
567
- Node::LessThan.new(10).append(
568
- proc { |input| input * 3 }, # anonymous external node
569
- proc { |input| input - 1 } # anonymous external node
570
- ).compose
571
- ).compose
572
-
573
- (0..10).each do |input|
574
- output = tree.call(input)
575
- puts "#{input} -> #{output}"
576
- end
577
-
578
- ```
579
-
580
- Run `examples/example7.rb`:
581
- ```sh
582
- % ruby examples/example7.rb
583
- 0 -> 2
584
- 1 -> 8
585
- 2 -> 14
586
- 3 -> 20
587
- 4 -> 26
588
- 5 -> 14
589
- 6 -> 17
590
- 7 -> 20
591
- 8 -> 23
592
- 9 -> 26
593
- 10 -> 10
594
- ```
595
-
596
621
  ## Contributing
597
622
 
598
623
  Bug reports and pull requests are welcome on GitHub at https://github.com/jsmmr/callable_tree.
@@ -8,8 +8,8 @@ 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 `if` or `case` expressions.'
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 `if` or `case` expressions.'
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')
File without changes
File without changes
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
File without changes
File without changes
File without changes
@@ -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,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
@@ -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
@@ -6,75 +6,113 @@ module CallableTree
6
6
  include Node
7
7
 
8
8
  def children
9
- @children ||= []
9
+ # TODO: Change to return a new array instance.
10
+ child_nodes
10
11
  end
11
12
 
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) }
13
+ def append(*callables)
14
+ clone.tap do |node|
15
+ node.append!(*callables)
16
+ end
17
+ end
18
+
19
+ def append!(*callables)
20
+ callables
21
+ .map { |callable| nodeify(callable) }
22
+ .tap { |nodes| child_nodes.push(*nodes) } # Use Array#push for Ruby 2.4
20
23
 
21
24
  self
22
25
  end
23
26
 
24
- def append(*callables)
25
- callables.each { |callable| self.<<(callable) }
27
+ def reject(&block)
28
+ clone.tap do |node|
29
+ node.reject!(&block)
30
+ end
31
+ end
32
+
33
+ def reject!(&block)
34
+ child_nodes.reject!(&block)
26
35
  self
27
36
  end
28
37
 
29
38
  def match?(_input = nil, **_options)
30
- !children.empty?
39
+ !child_nodes.empty?
31
40
  end
32
41
 
33
42
  def call(input = nil, **options)
34
- strategy.call(children, input: input, options: options)
43
+ strategy.call(child_nodes, input: input, options: options)
35
44
  end
36
45
 
37
46
  def seek
38
- if strategy.is_a?(Seek)
47
+ if strategy.is_a?(Strategy::Seek)
39
48
  self
40
49
  else
41
50
  clone.tap do |node|
42
- node.send(:strategy=, Seek.new)
51
+ node.send(:strategy=, Strategy::Seek.new)
43
52
  end
44
53
  end
45
54
  end
46
55
 
56
+ def seek!
57
+ self.strategy = Strategy::Seek.new unless strategy.is_a?(Strategy::Seek)
58
+ self
59
+ end
60
+
47
61
  def broadcast
48
- if strategy.is_a?(Broadcast)
62
+ if strategy.is_a?(Strategy::Broadcast)
49
63
  self
50
64
  else
51
65
  clone.tap do |node|
52
- node.send(:strategy=, Broadcast.new)
66
+ node.send(:strategy=, Strategy::Broadcast.new)
53
67
  end
54
68
  end
55
69
  end
56
70
 
71
+ def broadcast!
72
+ self.strategy = Strategy::Broadcast.new unless strategy.is_a?(Strategy::Broadcast)
73
+ self
74
+ end
75
+
57
76
  def compose
58
- if strategy.is_a?(Compose)
77
+ if strategy.is_a?(Strategy::Compose)
59
78
  self
60
79
  else
61
80
  clone.tap do |node|
62
- node.send(:strategy=, Compose.new)
81
+ node.send(:strategy=, Strategy::Compose.new)
63
82
  end
64
83
  end
65
84
  end
66
85
 
86
+ def compose!
87
+ self.strategy = Strategy::Compose.new unless strategy.is_a?(Strategy::Compose)
88
+ self
89
+ end
90
+
67
91
  private
68
92
 
69
- attr_writer :children, :strategy
93
+ attr_writer :child_nodes, :strategy
94
+
95
+ def child_nodes
96
+ @child_nodes ||= []
97
+ end
98
+
99
+ def nodeify(callable)
100
+ if callable.is_a?(Node)
101
+ callable.clone
102
+ else
103
+ External.proxify(callable)
104
+ end
105
+ .tap { |node| node.send(:parent=, self) }
106
+ end
70
107
 
71
108
  def strategy
72
- @strategy ||= Seek.new
109
+ @strategy ||= Strategy::Seek.new
73
110
  end
74
111
 
75
112
  def initialize_copy(_node)
76
113
  super
77
- self.children = children.map do |node|
114
+ self.parent = nil
115
+ self.child_nodes = child_nodes.map do |node|
78
116
  node.clone.tap { |new_node| new_node.send(:parent=, self) }
79
117
  end
80
118
  end
@@ -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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CallableTree
4
- VERSION = '0.1.2'
4
+ VERSION = '0.2.2'
5
5
  end
data/lib/callable_tree.rb CHANGED
@@ -8,9 +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'
13
- require_relative 'callable_tree/node/internal/compose'
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'
14
14
  require_relative 'callable_tree/node/external/verbose'
15
15
  require_relative 'callable_tree/node/external'
16
16
  require_relative 'callable_tree/node/internal'
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.2
4
+ version: 0.2.2
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-29 00:00:00.000000000 Z
11
+ date: 2021-10-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 `if` or `case` expressions.
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,22 +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
45
- - examples/example7.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
46
46
  - lib/callable_tree.rb
47
47
  - lib/callable_tree/node.rb
48
48
  - lib/callable_tree/node/external.rb
49
49
  - lib/callable_tree/node/external/verbose.rb
50
50
  - lib/callable_tree/node/hooks/call.rb
51
51
  - lib/callable_tree/node/internal.rb
52
- - lib/callable_tree/node/internal/broadcast.rb
53
- - lib/callable_tree/node/internal/compose.rb
54
- - 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
55
55
  - lib/callable_tree/node/root.rb
56
56
  - lib/callable_tree/version.rb
57
57
  homepage: https://github.com/jsmmr/ruby_callable_tree
@@ -60,7 +60,7 @@ licenses:
60
60
  metadata:
61
61
  homepage_uri: https://github.com/jsmmr/ruby_callable_tree
62
62
  source_code_uri: https://github.com/jsmmr/ruby_callable_tree
63
- changelog_uri: https://github.com/jsmmr/ruby_callable_tree/blob/v0.1.2/CHANGELOG.md
63
+ changelog_uri: https://github.com/jsmmr/ruby_callable_tree/blob/v0.2.2/CHANGELOG.md
64
64
  post_install_message:
65
65
  rdoc_options: []
66
66
  require_paths:
@@ -76,10 +76,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
76
76
  - !ruby/object:Gem::Version
77
77
  version: '0'
78
78
  requirements: []
79
- rubygems_version: 3.2.16
79
+ rubygems_version: 3.2.22
80
80
  signing_key:
81
81
  specification_version: 4
82
- summary: Builds a tree by linking callable nodes. The nodes that match the calling
83
- condition are called in a chain from the root node to the leaf node. These are like
84
- nested `if` or `case` expressions.
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.
85
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,15 +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
- node.call(input, **options) if node.match?(input, **options)
10
- end
11
- end
12
- end
13
- end
14
- end
15
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CallableTree
4
- module Node
5
- module Internal
6
- class Compose
7
- def call(nodes, input:, options:)
8
- nodes.reduce(input) do |input, node|
9
- if node.match?(input, **options)
10
- node.call(input, **options)
11
- else
12
- input
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