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