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 +4 -4
- data/.ruby-version +1 -1
- data/CHANGELOG.md +20 -0
- data/Gemfile.lock +3 -3
- data/LICENSE.txt +1 -1
- data/README.md +138 -113
- data/callable_tree.gemspec +2 -2
- data/examples/{example2.rb → external-verbosify.rb} +0 -0
- data/examples/{example5.rb → hooks-call.rb} +0 -0
- data/examples/{example3.rb → identity.rb} +0 -0
- data/examples/internal-broadcast.rb +31 -0
- data/examples/{example7.rb → internal-compose.rb} +0 -0
- data/examples/{example1.rb → internal-seek.rb} +0 -0
- data/examples/{example4.rb → logging.rb} +0 -0
- data/lib/callable_tree/node/external/verbose.rb +4 -0
- data/lib/callable_tree/node/external.rb +27 -7
- data/lib/callable_tree/node/internal/strategy/broadcast.rb +17 -0
- data/lib/callable_tree/node/internal/strategy/compose.rb +21 -0
- data/lib/callable_tree/node/internal/strategy/seek.rb +25 -0
- data/lib/callable_tree/node/internal.rb +60 -22
- data/lib/callable_tree/node.rb +6 -2
- data/lib/callable_tree/version.rb +1 -1
- data/lib/callable_tree.rb +3 -3
- metadata +20 -20
- data/examples/example6.rb +0 -101
- data/lib/callable_tree/node/internal/broadcast.rb +0 -15
- data/lib/callable_tree/node/internal/compose.rb +0 -19
- data/lib/callable_tree/node/internal/seek.rb +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8e5f087271cbaf840a0cb6557126331b67487c4f87e5843e3bcf900c54dc6e84
|
4
|
+
data.tar.gz: d1d1e0ac4a085dc26d1a2f6bec141ea92ea80efbfb43c8738e34d9ae9280836c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0a342b5fff3ce51c36bd4e4e1977f2023db7045bab0f19e794bc8a1c48442c184cedfcadad5a0b9956fe146a0904aaca584a6d23c5ac23c063dc53d8d260978a
|
7
|
+
data.tar.gz: 6fd3e33d25e549774488967d600ef236bff9e74bc750741eff9e5c6d3e5c59b15f2c956c1f25b97f5e9cd16becb7004e6df298adf04c01844aa300dc1e2e42b3
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.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.
|
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.
|
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.
|
35
|
+
2.2.22
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# CallableTree
|
2
2
|
|
3
|
+
[](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
|
-
`
|
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/
|
150
|
+
Run `examples/internal-seek.rb`:
|
145
151
|
```sh
|
146
|
-
% ruby examples/
|
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
|
275
|
+
If you want verbose output results, call this method.
|
162
276
|
|
163
|
-
`examples/
|
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/
|
295
|
+
Run `examples/external-verbosify.rb`:
|
182
296
|
```sh
|
183
|
-
% ruby examples/
|
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
|
325
|
+
If you want to customize the node identity, override this method.
|
212
326
|
|
213
|
-
`examples/
|
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/
|
383
|
+
Run `examples/identity.rb`:
|
270
384
|
```sh
|
271
|
-
% ruby examples/
|
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/
|
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/
|
505
|
+
Run `examples/logging.rb`:
|
392
506
|
```sh
|
393
|
-
% ruby examples/
|
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/
|
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/
|
610
|
+
Run `examples/hooks-call.rb`:
|
497
611
|
```sh
|
498
|
-
% ruby examples/
|
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.
|
data/callable_tree.gemspec
CHANGED
@@ -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
|
12
|
-
spec.description = 'Builds a tree by linking callable nodes. The nodes that match the
|
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
|
@@ -9,38 +9,58 @@ module CallableTree
|
|
9
9
|
Proxy.new(callable)
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
13
|
-
|
12
|
+
def proxified?
|
13
|
+
false
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
17
|
-
|
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
|
27
|
-
|
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
|
-
|
9
|
+
# TODO: Change to return a new array instance.
|
10
|
+
child_nodes
|
10
11
|
end
|
11
12
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
25
|
-
|
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
|
-
!
|
39
|
+
!child_nodes.empty?
|
31
40
|
end
|
32
41
|
|
33
42
|
def call(input = nil, **options)
|
34
|
-
strategy.call(
|
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 :
|
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.
|
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
|
data/lib/callable_tree/node.rb
CHANGED
@@ -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
|
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
|
-
|
30
|
+
root? ? 0 : parent.depth + 1
|
27
31
|
end
|
28
32
|
|
29
33
|
def match?(_input = nil, **_options)
|
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.
|
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-
|
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
|
14
|
-
|
15
|
-
|
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/
|
40
|
-
- examples/
|
41
|
-
- examples/
|
42
|
-
- examples/
|
43
|
-
- examples/
|
44
|
-
- examples/
|
45
|
-
- examples/
|
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.
|
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.
|
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
|
83
|
-
|
84
|
-
|
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
|