callable_tree 0.1.2 → 0.2.2

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: 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