callable_tree 0.3.10 → 0.3.12
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/.github/workflows/build.yml +19 -5
- data/.github/workflows/codeql-analysis.yml +4 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +3 -0
- data/.rubocop_todo.yml +128 -0
- data/.ruby-version +1 -1
- data/AGENTS.md +56 -0
- data/CHANGELOG.md +11 -0
- data/CONTRIBUTING.md +68 -0
- data/Gemfile +4 -2
- data/README.md +195 -21
- data/callable_tree.gemspec +8 -2
- data/examples/builder/external-verbosify.rb +3 -2
- data/examples/builder/hooks.rb +2 -1
- data/examples/builder/identity.rb +3 -2
- data/examples/builder/internal-broadcastable.rb +2 -1
- data/examples/builder/internal-composable.rb +2 -1
- data/examples/builder/internal-seekable.rb +3 -2
- data/examples/builder/logging.rb +3 -2
- data/examples/class/external-verbosify.rb +3 -2
- data/examples/class/hooks.rb +2 -1
- data/examples/class/identity.rb +3 -2
- data/examples/class/internal-broadcastable.rb +2 -1
- data/examples/class/internal-composable.rb +2 -1
- data/examples/class/internal-seekable.rb +3 -2
- data/examples/class/logging.rb +3 -2
- data/examples/factory/external-verbosify.rb +69 -0
- data/examples/factory/hooks.rb +67 -0
- data/examples/factory/identity.rb +93 -0
- data/examples/factory/internal-broadcastable.rb +37 -0
- data/examples/factory/internal-composable.rb +37 -0
- data/examples/factory/internal-seekable.rb +71 -0
- data/examples/factory/logging.rb +121 -0
- data/lib/callable_tree/node/builder.rb +1 -0
- data/lib/callable_tree/node/external/pod.rb +91 -0
- data/lib/callable_tree/node/external/verbose.rb +1 -1
- data/lib/callable_tree/node/external.rb +1 -0
- data/lib/callable_tree/node/hooks/terminator.rb +1 -1
- data/lib/callable_tree/node/internal/pod.rb +91 -0
- data/lib/callable_tree/node/internal/{strategyable.rb → strategizable.rb} +4 -4
- data/lib/callable_tree/node/internal.rb +2 -2
- data/lib/callable_tree/version.rb +1 -1
- data/lib/callable_tree.rb +3 -1
- data/mise.toml +2 -0
- metadata +26 -15
- data/Gemfile.lock +0 -34
data/README.md
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
[](https://github.com/jsmmr/ruby_callable_tree/actions/workflows/build.yml)
|
|
4
4
|
[](https://github.com/jsmmr/ruby_callable_tree/actions/workflows/codeql-analysis.yml)
|
|
5
5
|
|
|
6
|
+
A framework for building tree-structured executable workflows in Ruby.
|
|
7
|
+
|
|
8
|
+
Construct trees of callable nodes to handle complex execution flows. Supports strategies to seek specific handlers, broadcast to multiple listeners, or compose processing pipelines. Nodes are matched against input and executed in a chain, offering a structured, modular alternative to complex conditional logic.
|
|
9
|
+
|
|
6
10
|
## Installation
|
|
7
11
|
|
|
8
12
|
Add this line to your application's Gemfile:
|
|
@@ -21,22 +25,22 @@ Or install it yourself as:
|
|
|
21
25
|
|
|
22
26
|
## Usage
|
|
23
27
|
|
|
24
|
-
Builds a tree
|
|
28
|
+
Builds a tree of `CallableTree` nodes. Invokes the `call` method on nodes where `match?` returns a truthy value, chaining execution from root to leaf.
|
|
25
29
|
|
|
26
30
|
- `CallableTree::Node::Internal`
|
|
27
|
-
-
|
|
31
|
+
- Defines a node that can have child nodes. Supports several strategies (`seekable`, `broadcastable`, `composable`).
|
|
28
32
|
- `CallableTree::Node::External`
|
|
29
|
-
-
|
|
33
|
+
- Defines a leaf node, which cannot have child nodes.
|
|
30
34
|
- `CallableTree::Node::Root`
|
|
31
|
-
-
|
|
35
|
+
- Includes `CallableTree::Node::Internal`. Use this class when customization of the internal node is not required.
|
|
32
36
|
|
|
33
37
|
### Basic
|
|
34
38
|
|
|
35
|
-
There are
|
|
39
|
+
There are three ways to define nodes: class style, builder style, and factory style.
|
|
36
40
|
|
|
37
41
|
#### `CallableTree::Node::Internal#seekable` (default strategy)
|
|
38
42
|
|
|
39
|
-
This strategy
|
|
43
|
+
This strategy stops processing subsequent sibling nodes if the current node's `call` method returns a non-nil value. This behavior is changeable by overriding the `terminate?` method.
|
|
40
44
|
|
|
41
45
|
##### Class style
|
|
42
46
|
|
|
@@ -51,8 +55,7 @@ module Node
|
|
|
51
55
|
File.extname(input) == '.json'
|
|
52
56
|
end
|
|
53
57
|
|
|
54
|
-
#
|
|
55
|
-
# child nodes, override the `call` method.
|
|
58
|
+
# Override `call` if you need to transform input values for child nodes.
|
|
56
59
|
def call(input, **options)
|
|
57
60
|
File.open(input) do |file|
|
|
58
61
|
json = ::JSON.load(file)
|
|
@@ -60,9 +63,7 @@ module Node
|
|
|
60
63
|
end
|
|
61
64
|
end
|
|
62
65
|
|
|
63
|
-
#
|
|
64
|
-
# but there is no need to call the sibling nodes,
|
|
65
|
-
# override the `terminate?` method to return `true`.
|
|
66
|
+
# Override `terminate?` to return `true` to stop processing sibling nodes even if `call` returns `nil`.
|
|
66
67
|
def terminate?(_output, *_inputs, **_options)
|
|
67
68
|
true
|
|
68
69
|
end
|
|
@@ -94,17 +95,14 @@ module Node
|
|
|
94
95
|
File.extname(input) == '.xml'
|
|
95
96
|
end
|
|
96
97
|
|
|
97
|
-
#
|
|
98
|
-
# child nodes, override the `call` method.
|
|
98
|
+
# Override `call` if you need to transform input values for child nodes.
|
|
99
99
|
def call(input, **options)
|
|
100
100
|
File.open(input) do |file|
|
|
101
101
|
super(REXML::Document.new(file), **options)
|
|
102
102
|
end
|
|
103
103
|
end
|
|
104
104
|
|
|
105
|
-
#
|
|
106
|
-
# but there is no need to call the sibling nodes,
|
|
107
|
-
# override the `terminate?` method to return `true`.
|
|
105
|
+
# Override `terminate?` to return `true` to stop processing sibling nodes even if `call` returns `nil`.
|
|
108
106
|
def terminate?(_output, *_inputs, **_options)
|
|
109
107
|
true
|
|
110
108
|
end
|
|
@@ -131,7 +129,7 @@ module Node
|
|
|
131
129
|
end
|
|
132
130
|
end
|
|
133
131
|
|
|
134
|
-
# The `seekable`
|
|
132
|
+
# The `seekable` call can be omitted as it is the default strategy.
|
|
135
133
|
tree = CallableTree::Node::Root.new.seekable.append(
|
|
136
134
|
Node::JSON::Parser.new.seekable.append(
|
|
137
135
|
Node::JSON::Scraper.new(type: :animals),
|
|
@@ -263,9 +261,87 @@ Run `examples/builder/internal-seekable.rb`:
|
|
|
263
261
|
---
|
|
264
262
|
```
|
|
265
263
|
|
|
264
|
+
##### Factory style
|
|
265
|
+
|
|
266
|
+
Factory style defines behaviors as procs first, then assembles the tree structure separately. This makes the tree structure clearly visible.
|
|
267
|
+
|
|
268
|
+
`examples/factory/internal-seekable.rb`:
|
|
269
|
+
```ruby
|
|
270
|
+
# === Behavior Definitions ===
|
|
271
|
+
|
|
272
|
+
json_matcher = ->(input, **) { File.extname(input) == '.json' }
|
|
273
|
+
json_caller = lambda do |input, **options, &original|
|
|
274
|
+
File.open(input) do |file|
|
|
275
|
+
json = JSON.parse(file.read)
|
|
276
|
+
original.call(json, **options)
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
xml_matcher = ->(input, **) { File.extname(input) == '.xml' }
|
|
281
|
+
xml_caller = lambda do |input, **options, &original|
|
|
282
|
+
File.open(input) do |file|
|
|
283
|
+
original.call(REXML::Document.new(file), **options)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
animals_json_matcher = ->(input, **) { !input['animals'].nil? }
|
|
288
|
+
animals_json_caller = ->(input, **) { input['animals'].to_h { |e| [e['name'], e['emoji']] } }
|
|
289
|
+
|
|
290
|
+
fruits_json_matcher = ->(input, **) { !input['fruits'].nil? }
|
|
291
|
+
fruits_json_caller = ->(input, **) { input['fruits'].to_h { |e| [e['name'], e['emoji']] } }
|
|
292
|
+
|
|
293
|
+
animals_xml_matcher = ->(input, **) { !input.get_elements('//animals').empty? }
|
|
294
|
+
animals_xml_caller = ->(input, **) { input.get_elements('//animals').first.to_h { |e| [e['name'], e['emoji']] } }
|
|
295
|
+
|
|
296
|
+
fruits_xml_matcher = ->(input, **) { !input.get_elements('//fruits').empty? }
|
|
297
|
+
fruits_xml_caller = ->(input, **) { input.get_elements('//fruits').first.to_h { |e| [e['name'], e['emoji']] } }
|
|
298
|
+
|
|
299
|
+
terminator_true = ->(*) { true }
|
|
300
|
+
|
|
301
|
+
# === Tree Structure (clearly visible!) ===
|
|
302
|
+
|
|
303
|
+
tree = CallableTree::Node::Root.new.seekable.append(
|
|
304
|
+
CallableTree::Node::Internal.create(
|
|
305
|
+
matcher: json_matcher,
|
|
306
|
+
caller: json_caller,
|
|
307
|
+
terminator: terminator_true
|
|
308
|
+
).seekable.append(
|
|
309
|
+
CallableTree::Node::External.create(matcher: animals_json_matcher, caller: animals_json_caller),
|
|
310
|
+
CallableTree::Node::External.create(matcher: fruits_json_matcher, caller: fruits_json_caller)
|
|
311
|
+
),
|
|
312
|
+
CallableTree::Node::Internal.create(
|
|
313
|
+
matcher: xml_matcher,
|
|
314
|
+
caller: xml_caller,
|
|
315
|
+
terminator: terminator_true
|
|
316
|
+
).seekable.append(
|
|
317
|
+
CallableTree::Node::External.create(matcher: animals_xml_matcher, caller: animals_xml_caller),
|
|
318
|
+
CallableTree::Node::External.create(matcher: fruits_xml_matcher, caller: fruits_xml_caller)
|
|
319
|
+
)
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
Dir.glob("#{__dir__}/../docs/*") do |file|
|
|
323
|
+
options = { foo: :bar }
|
|
324
|
+
pp tree.call(file, **options)
|
|
325
|
+
puts '---'
|
|
326
|
+
end
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Run `examples/factory/internal-seekable.rb`:
|
|
330
|
+
```sh
|
|
331
|
+
% ruby examples/factory/internal-seekable.rb
|
|
332
|
+
{"Dog"=>"🐶", "Cat"=>"🐱"}
|
|
333
|
+
---
|
|
334
|
+
{"Dog"=>"🐶", "Cat"=>"🐱"}
|
|
335
|
+
---
|
|
336
|
+
{"Red Apple"=>"🍎", "Green Apple"=>"🍏"}
|
|
337
|
+
---
|
|
338
|
+
{"Red Apple"=>"🍎", "Green Apple"=>"🍏"}
|
|
339
|
+
---
|
|
340
|
+
```
|
|
341
|
+
|
|
266
342
|
#### `CallableTree::Node::Internal#broadcastable`
|
|
267
343
|
|
|
268
|
-
This strategy broadcasts to
|
|
344
|
+
This strategy broadcasts input to all child nodes and returns their results as an array. It ignores child `terminate?` methods by default.
|
|
269
345
|
|
|
270
346
|
##### Class style
|
|
271
347
|
|
|
@@ -409,9 +485,58 @@ Run `examples/builder/internal-broadcastable.rb`:
|
|
|
409
485
|
10 -> [nil, nil]
|
|
410
486
|
```
|
|
411
487
|
|
|
488
|
+
##### Factory style
|
|
489
|
+
|
|
490
|
+
`examples/factory/internal-broadcastable.rb`:
|
|
491
|
+
```ruby
|
|
492
|
+
# === Behavior Definitions ===
|
|
493
|
+
|
|
494
|
+
less_than_5_matcher = ->(input, **, &original) { original.call(input) && input < 5 }
|
|
495
|
+
less_than_10_matcher = ->(input, **, &original) { original.call(input) && input < 10 }
|
|
496
|
+
|
|
497
|
+
multiply_2_caller = ->(input, **) { input * 2 }
|
|
498
|
+
add_1_caller = ->(input, **) { input + 1 }
|
|
499
|
+
multiply_3_caller = ->(input, **) { input * 3 }
|
|
500
|
+
subtract_1_caller = ->(input, **) { input - 1 }
|
|
501
|
+
|
|
502
|
+
# === Tree Structure ===
|
|
503
|
+
|
|
504
|
+
tree = CallableTree::Node::Root.new.broadcastable.append(
|
|
505
|
+
CallableTree::Node::Internal.create(matcher: less_than_5_matcher).broadcastable.append(
|
|
506
|
+
CallableTree::Node::External.create(caller: multiply_2_caller),
|
|
507
|
+
CallableTree::Node::External.create(caller: add_1_caller)
|
|
508
|
+
),
|
|
509
|
+
CallableTree::Node::Internal.create(matcher: less_than_10_matcher).broadcastable.append(
|
|
510
|
+
CallableTree::Node::External.create(caller: multiply_3_caller),
|
|
511
|
+
CallableTree::Node::External.create(caller: subtract_1_caller)
|
|
512
|
+
)
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
(0..10).each do |input|
|
|
516
|
+
output = tree.call(input)
|
|
517
|
+
puts "#{input} -> #{output}"
|
|
518
|
+
end
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
Run `examples/factory/internal-broadcastable.rb`:
|
|
522
|
+
```sh
|
|
523
|
+
% ruby examples/factory/internal-broadcastable.rb
|
|
524
|
+
0 -> [[0, 1], [0, -1]]
|
|
525
|
+
1 -> [[2, 2], [3, 0]]
|
|
526
|
+
2 -> [[4, 3], [6, 1]]
|
|
527
|
+
3 -> [[6, 4], [9, 2]]
|
|
528
|
+
4 -> [[8, 5], [12, 3]]
|
|
529
|
+
5 -> [nil, [15, 4]]
|
|
530
|
+
6 -> [nil, [18, 5]]
|
|
531
|
+
7 -> [nil, [21, 6]]
|
|
532
|
+
8 -> [nil, [24, 7]]
|
|
533
|
+
9 -> [nil, [27, 8]]
|
|
534
|
+
10 -> [nil, nil]
|
|
535
|
+
```
|
|
536
|
+
|
|
412
537
|
#### `CallableTree::Node::Internal#composable`
|
|
413
538
|
|
|
414
|
-
This strategy
|
|
539
|
+
This strategy chains child nodes, passing the output of the previous node as input to the next.
|
|
415
540
|
It also ignores their `terminate?` methods by default.
|
|
416
541
|
|
|
417
542
|
##### Class style
|
|
@@ -556,11 +681,60 @@ Run `examples/builder/internal-composable.rb`:
|
|
|
556
681
|
10 -> 10
|
|
557
682
|
```
|
|
558
683
|
|
|
684
|
+
##### Factory style
|
|
685
|
+
|
|
686
|
+
`examples/factory/internal-composable.rb`:
|
|
687
|
+
```ruby
|
|
688
|
+
# === Behavior Definitions ===
|
|
689
|
+
|
|
690
|
+
less_than_5_matcher = ->(input, **, &original) { original.call(input) && input < 5 }
|
|
691
|
+
less_than_10_matcher = ->(input, **, &original) { original.call(input) && input < 10 }
|
|
692
|
+
|
|
693
|
+
multiply_2_caller = ->(input, **) { input * 2 }
|
|
694
|
+
add_1_caller = ->(input, **) { input + 1 }
|
|
695
|
+
multiply_3_caller = ->(input, **) { input * 3 }
|
|
696
|
+
subtract_1_caller = ->(input, **) { input - 1 }
|
|
697
|
+
|
|
698
|
+
# === Tree Structure ===
|
|
699
|
+
|
|
700
|
+
tree = CallableTree::Node::Root.new.composable.append(
|
|
701
|
+
CallableTree::Node::Internal.create(matcher: less_than_5_matcher).composable.append(
|
|
702
|
+
CallableTree::Node::External.create(caller: multiply_2_caller),
|
|
703
|
+
CallableTree::Node::External.create(caller: add_1_caller)
|
|
704
|
+
),
|
|
705
|
+
CallableTree::Node::Internal.create(matcher: less_than_10_matcher).composable.append(
|
|
706
|
+
CallableTree::Node::External.create(caller: multiply_3_caller),
|
|
707
|
+
CallableTree::Node::External.create(caller: subtract_1_caller)
|
|
708
|
+
)
|
|
709
|
+
)
|
|
710
|
+
|
|
711
|
+
(0..10).each do |input|
|
|
712
|
+
output = tree.call(input)
|
|
713
|
+
puts "#{input} -> #{output}"
|
|
714
|
+
end
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
Run `examples/factory/internal-composable.rb`:
|
|
718
|
+
```sh
|
|
719
|
+
% ruby examples/factory/internal-composable.rb
|
|
720
|
+
0 -> 2
|
|
721
|
+
1 -> 8
|
|
722
|
+
2 -> 14
|
|
723
|
+
3 -> 20
|
|
724
|
+
4 -> 26
|
|
725
|
+
5 -> 14
|
|
726
|
+
6 -> 17
|
|
727
|
+
7 -> 20
|
|
728
|
+
8 -> 23
|
|
729
|
+
9 -> 26
|
|
730
|
+
10 -> 10
|
|
731
|
+
```
|
|
732
|
+
|
|
559
733
|
### Advanced
|
|
560
734
|
|
|
561
735
|
#### `CallableTree::Node::External#verbosify`
|
|
562
736
|
|
|
563
|
-
|
|
737
|
+
Use this method to enable verbose output.
|
|
564
738
|
|
|
565
739
|
`examples/builder/external-verbosify.rb`:
|
|
566
740
|
```ruby
|
|
@@ -739,7 +913,7 @@ Run `examples/builder/logging.rb`:
|
|
|
739
913
|
|
|
740
914
|
#### `CallableTree::Node#identity`
|
|
741
915
|
|
|
742
|
-
|
|
916
|
+
Specify an identifier to customize the node identity.
|
|
743
917
|
|
|
744
918
|
`examples/builder/identity.rb`:
|
|
745
919
|
```ruby
|
data/callable_tree.gemspec
CHANGED
|
@@ -8,8 +8,14 @@ Gem::Specification.new do |spec|
|
|
|
8
8
|
spec.authors = ['jsmmr']
|
|
9
9
|
spec.email = ['jsmmr@icloud.com']
|
|
10
10
|
|
|
11
|
-
spec.summary = 'Builds
|
|
12
|
-
spec.description
|
|
11
|
+
spec.summary = 'Builds executable trees of callable nodes with flexible strategies like seek, broadcast, and compose.'
|
|
12
|
+
spec.description = <<~DESC
|
|
13
|
+
CallableTree provides a framework for organizing complex logic into a tree of callable nodes.
|
|
14
|
+
It allows you to chain execution from a root node to leaf nodes based on matching conditions.
|
|
15
|
+
Key features include multiple traversal strategies: `seekable` (like nested `if`/`case`),
|
|
16
|
+
`broadcastable` (one-to-many execution), and `composable` (pipelined processing).
|
|
17
|
+
Supports class-based, builder-style and factory-style definitions.
|
|
18
|
+
DESC
|
|
13
19
|
spec.homepage = 'https://github.com/jsmmr/ruby_callable_tree'
|
|
14
20
|
spec.license = 'MIT'
|
|
15
21
|
spec.required_ruby_version = Gem::Requirement.new('>= 2.4.0')
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative '../../lib/callable_tree'
|
|
4
|
+
# require 'callable_tree'
|
|
4
5
|
require 'json'
|
|
5
6
|
require 'rexml/document'
|
|
6
7
|
|
|
@@ -12,7 +13,7 @@ JSONParser =
|
|
|
12
13
|
end
|
|
13
14
|
.caller do |input, **options, &block|
|
|
14
15
|
File.open(input) do |file|
|
|
15
|
-
json = JSON.
|
|
16
|
+
json = JSON.parse(file.read)
|
|
16
17
|
# The following block call is equivalent to calling `super` in the class style.
|
|
17
18
|
block.call(json, **options)
|
|
18
19
|
end
|
data/examples/builder/hooks.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative '../../lib/callable_tree'
|
|
4
|
+
# require 'callable_tree'
|
|
4
5
|
require 'json'
|
|
5
6
|
require 'rexml/document'
|
|
6
7
|
|
|
@@ -12,7 +13,7 @@ JSONParser =
|
|
|
12
13
|
end
|
|
13
14
|
.caller do |input, **options, &block|
|
|
14
15
|
File.open(input) do |file|
|
|
15
|
-
json = JSON.
|
|
16
|
+
json = JSON.parse(file.read)
|
|
16
17
|
# The following block call is equivalent to calling `super` in the class style.
|
|
17
18
|
block.call(json, **options)
|
|
18
19
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative '../../lib/callable_tree'
|
|
4
|
+
# require 'callable_tree'
|
|
4
5
|
require 'json'
|
|
5
6
|
require 'rexml/document'
|
|
6
7
|
|
|
@@ -12,7 +13,7 @@ JSONParser =
|
|
|
12
13
|
end
|
|
13
14
|
.caller do |input, **options, &original|
|
|
14
15
|
File.open(input) do |file|
|
|
15
|
-
json = JSON.
|
|
16
|
+
json = JSON.parse(file.read)
|
|
16
17
|
# The following block call is equivalent to calling `super` in the class style.
|
|
17
18
|
original.call(json, **options)
|
|
18
19
|
end
|
data/examples/builder/logging.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative '../../lib/callable_tree'
|
|
4
|
+
# require 'callable_tree'
|
|
4
5
|
require 'json'
|
|
5
6
|
require 'rexml/document'
|
|
6
7
|
|
|
@@ -12,7 +13,7 @@ JSONParser =
|
|
|
12
13
|
end
|
|
13
14
|
.caller do |input, **options, &original|
|
|
14
15
|
File.open(input) do |file|
|
|
15
|
-
json = JSON.
|
|
16
|
+
json = JSON.parse(file.read)
|
|
16
17
|
# The following block call is equivalent to calling `super` in the class style.
|
|
17
18
|
original.call(json, **options)
|
|
18
19
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative '../../lib/callable_tree'
|
|
4
|
+
# require 'callable_tree'
|
|
4
5
|
require 'json'
|
|
5
6
|
require 'rexml/document'
|
|
6
7
|
|
|
@@ -15,7 +16,7 @@ module Node
|
|
|
15
16
|
|
|
16
17
|
def call(input, **options)
|
|
17
18
|
File.open(input) do |file|
|
|
18
|
-
json = ::JSON.
|
|
19
|
+
json = ::JSON.parse(file.read)
|
|
19
20
|
super(json, **options)
|
|
20
21
|
end
|
|
21
22
|
end
|
data/examples/class/hooks.rb
CHANGED
data/examples/class/identity.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative '../../lib/callable_tree'
|
|
4
|
+
# require 'callable_tree'
|
|
4
5
|
require 'json'
|
|
5
6
|
require 'rexml/document'
|
|
6
7
|
|
|
@@ -28,7 +29,7 @@ module Node
|
|
|
28
29
|
|
|
29
30
|
def call(input, **options)
|
|
30
31
|
File.open(input) do |file|
|
|
31
|
-
json = ::JSON.
|
|
32
|
+
json = ::JSON.parse(file.read)
|
|
32
33
|
super(json, **options)
|
|
33
34
|
end
|
|
34
35
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative '../../lib/callable_tree'
|
|
4
|
+
# require 'callable_tree'
|
|
4
5
|
require 'json'
|
|
5
6
|
require 'rexml/document'
|
|
6
7
|
|
|
@@ -15,7 +16,7 @@ module Node
|
|
|
15
16
|
|
|
16
17
|
def call(input, **options)
|
|
17
18
|
File.open(input) do |file|
|
|
18
|
-
json = ::JSON.
|
|
19
|
+
json = ::JSON.parse(file.read)
|
|
19
20
|
super(json, **options)
|
|
20
21
|
end
|
|
21
22
|
end
|
data/examples/class/logging.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative '../../lib/callable_tree'
|
|
4
|
+
# require 'callable_tree'
|
|
4
5
|
require 'json'
|
|
5
6
|
require 'rexml/document'
|
|
6
7
|
|
|
@@ -29,7 +30,7 @@ module Node
|
|
|
29
30
|
|
|
30
31
|
def call(input, **options)
|
|
31
32
|
File.open(input) do |file|
|
|
32
|
-
json = ::JSON.
|
|
33
|
+
json = ::JSON.parse(file.read)
|
|
33
34
|
super(json, **options)
|
|
34
35
|
end
|
|
35
36
|
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../lib/callable_tree'
|
|
4
|
+
# require 'callable_tree'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'rexml/document'
|
|
7
|
+
|
|
8
|
+
# Verbosify example using Factory style with pre-defined procs
|
|
9
|
+
# Shows verbose output including route information
|
|
10
|
+
|
|
11
|
+
# === Behavior Definitions ===
|
|
12
|
+
|
|
13
|
+
json_matcher = ->(input, **) { File.extname(input) == '.json' }
|
|
14
|
+
json_caller = lambda do |input, **options, &block|
|
|
15
|
+
File.open(input) do |file|
|
|
16
|
+
json = JSON.parse(file.read)
|
|
17
|
+
block.call(json, **options)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
xml_matcher = ->(input, **) { File.extname(input) == '.xml' }
|
|
22
|
+
xml_caller = lambda do |input, **options, &block|
|
|
23
|
+
File.open(input) do |file|
|
|
24
|
+
block.call(REXML::Document.new(file), **options)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
animals_json_matcher = ->(input, **) { !input['animals'].nil? }
|
|
29
|
+
animals_json_caller = ->(input, **) { input['animals'].to_h { |e| [e['name'], e['emoji']] } }
|
|
30
|
+
|
|
31
|
+
fruits_json_matcher = ->(input, **) { !input['fruits'].nil? }
|
|
32
|
+
fruits_json_caller = ->(input, **) { input['fruits'].to_h { |e| [e['name'], e['emoji']] } }
|
|
33
|
+
|
|
34
|
+
animals_xml_matcher = ->(input, **) { !input.get_elements('//animals').empty? }
|
|
35
|
+
animals_xml_caller = ->(input, **) { input.get_elements('//animals').first.to_h { |e| [e['name'], e['emoji']] } }
|
|
36
|
+
|
|
37
|
+
fruits_xml_matcher = ->(input, **) { !input.get_elements('//fruits').empty? }
|
|
38
|
+
fruits_xml_caller = ->(input, **) { input.get_elements('//fruits').first.to_h { |e| [e['name'], e['emoji']] } }
|
|
39
|
+
|
|
40
|
+
terminator_true = ->(*) { true }
|
|
41
|
+
|
|
42
|
+
# === Tree Structure ===
|
|
43
|
+
|
|
44
|
+
tree = CallableTree::Node::Root.new.seekable.append(
|
|
45
|
+
CallableTree::Node::Internal.create(
|
|
46
|
+
matcher: json_matcher,
|
|
47
|
+
caller: json_caller,
|
|
48
|
+
terminator: terminator_true
|
|
49
|
+
).seekable.append(
|
|
50
|
+
CallableTree::Node::External.create(matcher: animals_json_matcher, caller: animals_json_caller).verbosify,
|
|
51
|
+
CallableTree::Node::External.create(matcher: fruits_json_matcher, caller: fruits_json_caller).verbosify
|
|
52
|
+
),
|
|
53
|
+
CallableTree::Node::Internal.create(
|
|
54
|
+
matcher: xml_matcher,
|
|
55
|
+
caller: xml_caller,
|
|
56
|
+
terminator: terminator_true
|
|
57
|
+
).seekable.append(
|
|
58
|
+
CallableTree::Node::External.create(matcher: animals_xml_matcher, caller: animals_xml_caller).verbosify,
|
|
59
|
+
CallableTree::Node::External.create(matcher: fruits_xml_matcher, caller: fruits_xml_caller).verbosify
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# === Execution ===
|
|
64
|
+
|
|
65
|
+
Dir.glob("#{__dir__}/../docs/*") do |file|
|
|
66
|
+
options = { foo: :bar }
|
|
67
|
+
pp tree.call(file, **options)
|
|
68
|
+
puts '---'
|
|
69
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../lib/callable_tree'
|
|
4
|
+
# require 'callable_tree'
|
|
5
|
+
|
|
6
|
+
# Hooks example using Factory style with pre-defined procs
|
|
7
|
+
# Demonstrates before/around/after callbacks on matcher, caller, and terminator
|
|
8
|
+
|
|
9
|
+
# === Behavior Definitions ===
|
|
10
|
+
|
|
11
|
+
external_caller = lambda do |input, **_options|
|
|
12
|
+
puts "external input: #{input}"
|
|
13
|
+
input * 2
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# === Tree Structure with Hooks ===
|
|
17
|
+
|
|
18
|
+
CallableTree::Node::Root.new.append(
|
|
19
|
+
CallableTree::Node::Internal.create(hookable: true)
|
|
20
|
+
.append(external_caller)
|
|
21
|
+
.before_matcher do |input, **_options|
|
|
22
|
+
puts "before_matcher input: #{input}"
|
|
23
|
+
input + 1
|
|
24
|
+
end
|
|
25
|
+
.around_matcher do |input, **_options, &block|
|
|
26
|
+
puts "around_matcher input: #{input}"
|
|
27
|
+
matched = block.call
|
|
28
|
+
puts "around_matcher matched: #{matched}"
|
|
29
|
+
!matched
|
|
30
|
+
end
|
|
31
|
+
.after_matcher do |matched, **_options|
|
|
32
|
+
puts "after_matcher matched: #{matched}"
|
|
33
|
+
!matched
|
|
34
|
+
end
|
|
35
|
+
.before_caller do |input, **_options|
|
|
36
|
+
puts "before_caller input: #{input}"
|
|
37
|
+
input + 1
|
|
38
|
+
end
|
|
39
|
+
.around_caller do |input, **_options, &block|
|
|
40
|
+
puts "around_caller input: #{input}"
|
|
41
|
+
output = block.call
|
|
42
|
+
puts "around_caller output: #{output}"
|
|
43
|
+
output * input
|
|
44
|
+
end
|
|
45
|
+
.after_caller do |output, **_options|
|
|
46
|
+
puts "after_caller output: #{output}"
|
|
47
|
+
output * 2
|
|
48
|
+
end
|
|
49
|
+
.before_terminator do |output, *_inputs, **_options|
|
|
50
|
+
puts "before_terminator output: #{output}"
|
|
51
|
+
output + 1
|
|
52
|
+
end
|
|
53
|
+
.around_terminator do |output, *_inputs, **_options, &block|
|
|
54
|
+
puts "around_terminator output: #{output}"
|
|
55
|
+
terminated = block.call
|
|
56
|
+
puts "around_terminator terminated: #{terminated}"
|
|
57
|
+
!terminated
|
|
58
|
+
end
|
|
59
|
+
.after_terminator do |terminated, **_options|
|
|
60
|
+
puts "after_terminator terminated: #{terminated}"
|
|
61
|
+
!terminated
|
|
62
|
+
end
|
|
63
|
+
).tap do |tree|
|
|
64
|
+
options = { foo: :bar }
|
|
65
|
+
output = tree.call(1, **options)
|
|
66
|
+
puts "result: #{output}"
|
|
67
|
+
end
|