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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +19 -5
  3. data/.github/workflows/codeql-analysis.yml +4 -4
  4. data/.gitignore +2 -0
  5. data/.rubocop.yml +3 -0
  6. data/.rubocop_todo.yml +128 -0
  7. data/.ruby-version +1 -1
  8. data/AGENTS.md +56 -0
  9. data/CHANGELOG.md +11 -0
  10. data/CONTRIBUTING.md +68 -0
  11. data/Gemfile +4 -2
  12. data/README.md +195 -21
  13. data/callable_tree.gemspec +8 -2
  14. data/examples/builder/external-verbosify.rb +3 -2
  15. data/examples/builder/hooks.rb +2 -1
  16. data/examples/builder/identity.rb +3 -2
  17. data/examples/builder/internal-broadcastable.rb +2 -1
  18. data/examples/builder/internal-composable.rb +2 -1
  19. data/examples/builder/internal-seekable.rb +3 -2
  20. data/examples/builder/logging.rb +3 -2
  21. data/examples/class/external-verbosify.rb +3 -2
  22. data/examples/class/hooks.rb +2 -1
  23. data/examples/class/identity.rb +3 -2
  24. data/examples/class/internal-broadcastable.rb +2 -1
  25. data/examples/class/internal-composable.rb +2 -1
  26. data/examples/class/internal-seekable.rb +3 -2
  27. data/examples/class/logging.rb +3 -2
  28. data/examples/factory/external-verbosify.rb +69 -0
  29. data/examples/factory/hooks.rb +67 -0
  30. data/examples/factory/identity.rb +93 -0
  31. data/examples/factory/internal-broadcastable.rb +37 -0
  32. data/examples/factory/internal-composable.rb +37 -0
  33. data/examples/factory/internal-seekable.rb +71 -0
  34. data/examples/factory/logging.rb +121 -0
  35. data/lib/callable_tree/node/builder.rb +1 -0
  36. data/lib/callable_tree/node/external/pod.rb +91 -0
  37. data/lib/callable_tree/node/external/verbose.rb +1 -1
  38. data/lib/callable_tree/node/external.rb +1 -0
  39. data/lib/callable_tree/node/hooks/terminator.rb +1 -1
  40. data/lib/callable_tree/node/internal/pod.rb +91 -0
  41. data/lib/callable_tree/node/internal/{strategyable.rb → strategizable.rb} +4 -4
  42. data/lib/callable_tree/node/internal.rb +2 -2
  43. data/lib/callable_tree/version.rb +1 -1
  44. data/lib/callable_tree.rb +3 -1
  45. data/mise.toml +2 -0
  46. metadata +26 -15
  47. data/Gemfile.lock +0 -34
@@ -0,0 +1,93 @@
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
+ # Identity example using Factory style with pre-defined procs
9
+ # Custom identity for each node - _node_: is used here
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
+ json_identifier = ->(_node_:) { _node_.object_id }
21
+
22
+ xml_matcher = ->(input, **) { File.extname(input) == '.xml' }
23
+ xml_caller = lambda do |input, **options, &block|
24
+ File.open(input) do |file|
25
+ block.call(REXML::Document.new(file), **options)
26
+ end
27
+ end
28
+ xml_identifier = ->(_node_:) { _node_.object_id }
29
+
30
+ animals_json_matcher = ->(input, **) { !input['animals'].nil? }
31
+ animals_json_caller = ->(input, **) { input['animals'].to_h { |e| [e['name'], e['emoji']] } }
32
+ animals_json_identifier = ->(_node_:) { _node_.object_id }
33
+
34
+ fruits_json_matcher = ->(input, **) { !input['fruits'].nil? }
35
+ fruits_json_caller = ->(input, **) { input['fruits'].to_h { |e| [e['name'], e['emoji']] } }
36
+ fruits_json_identifier = ->(_node_:) { _node_.object_id }
37
+
38
+ animals_xml_matcher = ->(input, **) { !input.get_elements('//animals').empty? }
39
+ animals_xml_caller = ->(input, **) { input.get_elements('//animals').first.to_h { |e| [e['name'], e['emoji']] } }
40
+ animals_xml_identifier = ->(_node_:) { _node_.object_id }
41
+
42
+ fruits_xml_matcher = ->(input, **) { !input.get_elements('//fruits').empty? }
43
+ fruits_xml_caller = ->(input, **) { input.get_elements('//fruits').first.to_h { |e| [e['name'], e['emoji']] } }
44
+ fruits_xml_identifier = ->(_node_:) { _node_.object_id }
45
+
46
+ terminator_true = ->(*) { true }
47
+
48
+ # === Tree Structure ===
49
+
50
+ tree = CallableTree::Node::Root.new.seekable.append(
51
+ CallableTree::Node::Internal.create(
52
+ matcher: json_matcher,
53
+ caller: json_caller,
54
+ terminator: terminator_true,
55
+ identifier: json_identifier
56
+ ).seekable.append(
57
+ CallableTree::Node::External.create(
58
+ matcher: animals_json_matcher,
59
+ caller: animals_json_caller,
60
+ identifier: animals_json_identifier
61
+ ).verbosify,
62
+ CallableTree::Node::External.create(
63
+ matcher: fruits_json_matcher,
64
+ caller: fruits_json_caller,
65
+ identifier: fruits_json_identifier
66
+ ).verbosify
67
+ ),
68
+ CallableTree::Node::Internal.create(
69
+ matcher: xml_matcher,
70
+ caller: xml_caller,
71
+ terminator: terminator_true,
72
+ identifier: xml_identifier
73
+ ).seekable.append(
74
+ CallableTree::Node::External.create(
75
+ matcher: animals_xml_matcher,
76
+ caller: animals_xml_caller,
77
+ identifier: animals_xml_identifier
78
+ ).verbosify,
79
+ CallableTree::Node::External.create(
80
+ matcher: fruits_xml_matcher,
81
+ caller: fruits_xml_caller,
82
+ identifier: fruits_xml_identifier
83
+ ).verbosify
84
+ )
85
+ )
86
+
87
+ # === Execution ===
88
+
89
+ Dir.glob("#{__dir__}/../docs/*") do |file|
90
+ options = { foo: :bar }
91
+ pp tree.call(file, **options)
92
+ puts '---'
93
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
5
+
6
+ # Broadcastable example using Factory style with pre-defined procs
7
+ # All matching child nodes are called and results are collected
8
+
9
+ # === Behavior Definitions ===
10
+
11
+ less_than_5_matcher = ->(input, **, &original) { original.call(input) && input < 5 }
12
+ less_than_10_matcher = ->(input, **, &original) { original.call(input) && input < 10 }
13
+
14
+ multiply_2_caller = ->(input, **) { input * 2 }
15
+ add_1_caller = ->(input, **) { input + 1 }
16
+ multiply_3_caller = ->(input, **) { input * 3 }
17
+ subtract_1_caller = ->(input, **) { input - 1 }
18
+
19
+ # === Tree Structure ===
20
+
21
+ tree = CallableTree::Node::Root.new.broadcastable.append(
22
+ CallableTree::Node::Internal.create(matcher: less_than_5_matcher).broadcastable.append(
23
+ CallableTree::Node::External.create(caller: multiply_2_caller),
24
+ CallableTree::Node::External.create(caller: add_1_caller)
25
+ ),
26
+ CallableTree::Node::Internal.create(matcher: less_than_10_matcher).broadcastable.append(
27
+ CallableTree::Node::External.create(caller: multiply_3_caller),
28
+ CallableTree::Node::External.create(caller: subtract_1_caller)
29
+ )
30
+ )
31
+
32
+ # === Execution ===
33
+
34
+ (0..10).each do |input|
35
+ output = tree.call(input)
36
+ puts "#{input} -> #{output}"
37
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/callable_tree'
4
+ # require 'callable_tree'
5
+
6
+ # Composable example using Factory style with pre-defined procs
7
+ # Output of one node becomes input to the next (pipeline)
8
+
9
+ # === Behavior Definitions ===
10
+
11
+ less_than_5_matcher = ->(input, **, &original) { original.call(input) && input < 5 }
12
+ less_than_10_matcher = ->(input, **, &original) { original.call(input) && input < 10 }
13
+
14
+ multiply_2_caller = ->(input, **) { input * 2 }
15
+ add_1_caller = ->(input, **) { input + 1 }
16
+ multiply_3_caller = ->(input, **) { input * 3 }
17
+ subtract_1_caller = ->(input, **) { input - 1 }
18
+
19
+ # === Tree Structure ===
20
+
21
+ tree = CallableTree::Node::Root.new.composable.append(
22
+ CallableTree::Node::Internal.create(matcher: less_than_5_matcher).composable.append(
23
+ CallableTree::Node::External.create(caller: multiply_2_caller),
24
+ CallableTree::Node::External.create(caller: add_1_caller)
25
+ ),
26
+ CallableTree::Node::Internal.create(matcher: less_than_10_matcher).composable.append(
27
+ CallableTree::Node::External.create(caller: multiply_3_caller),
28
+ CallableTree::Node::External.create(caller: subtract_1_caller)
29
+ )
30
+ )
31
+
32
+ # === Execution ===
33
+
34
+ (0..10).each do |input|
35
+ output = tree.call(input)
36
+ puts "#{input} -> #{output}"
37
+ end
@@ -0,0 +1,71 @@
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
+ # Seekable example using Factory style with pre-defined procs
9
+ # This demonstrates the key difference from Builder style:
10
+ # - Behaviors are defined as procs first
11
+ # - Tree structure is assembled separately, making it clearly visible
12
+
13
+ # === Behavior Definitions ===
14
+
15
+ json_matcher = ->(input, **) { File.extname(input) == '.json' }
16
+ json_caller = lambda do |input, **options, &original|
17
+ File.open(input) do |file|
18
+ json = JSON.parse(file.read)
19
+ original.call(json, **options)
20
+ end
21
+ end
22
+
23
+ xml_matcher = ->(input, **) { File.extname(input) == '.xml' }
24
+ xml_caller = lambda do |input, **options, &original|
25
+ File.open(input) do |file|
26
+ original.call(REXML::Document.new(file), **options)
27
+ end
28
+ end
29
+
30
+ animals_json_matcher = ->(input, **) { !input['animals'].nil? }
31
+ animals_json_caller = ->(input, **) { input['animals'].to_h { |e| [e['name'], e['emoji']] } }
32
+
33
+ fruits_json_matcher = ->(input, **) { !input['fruits'].nil? }
34
+ fruits_json_caller = ->(input, **) { input['fruits'].to_h { |e| [e['name'], e['emoji']] } }
35
+
36
+ animals_xml_matcher = ->(input, **) { !input.get_elements('//animals').empty? }
37
+ animals_xml_caller = ->(input, **) { input.get_elements('//animals').first.to_h { |e| [e['name'], e['emoji']] } }
38
+
39
+ fruits_xml_matcher = ->(input, **) { !input.get_elements('//fruits').empty? }
40
+ fruits_xml_caller = ->(input, **) { input.get_elements('//fruits').first.to_h { |e| [e['name'], e['emoji']] } }
41
+
42
+ terminator_true = ->(*) { true }
43
+
44
+ # === Tree Structure (clearly visible!) ===
45
+
46
+ tree = CallableTree::Node::Root.new.seekable.append(
47
+ CallableTree::Node::Internal.create(
48
+ matcher: json_matcher,
49
+ caller: json_caller,
50
+ terminator: terminator_true
51
+ ).seekable.append(
52
+ CallableTree::Node::External.create(matcher: animals_json_matcher, caller: animals_json_caller),
53
+ CallableTree::Node::External.create(matcher: fruits_json_matcher, caller: fruits_json_caller)
54
+ ),
55
+ CallableTree::Node::Internal.create(
56
+ matcher: xml_matcher,
57
+ caller: xml_caller,
58
+ terminator: terminator_true
59
+ ).seekable.append(
60
+ CallableTree::Node::External.create(matcher: animals_xml_matcher, caller: animals_xml_caller),
61
+ CallableTree::Node::External.create(matcher: fruits_xml_matcher, caller: fruits_xml_caller)
62
+ )
63
+ )
64
+
65
+ # === Execution ===
66
+
67
+ Dir.glob("#{__dir__}/../docs/*") do |file|
68
+ options = { foo: :bar }
69
+ pp tree.call(file, **options)
70
+ puts '---'
71
+ end
@@ -0,0 +1,121 @@
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
+ # Logging example using Factory style with pre-defined procs
9
+ # Uses hooks to add logging to the tree - _node_: is used in hooks
10
+
11
+ # === Behavior Definitions ===
12
+
13
+ json_matcher = ->(input, **) { File.extname(input) == '.json' }
14
+ json_caller = lambda do |input, **options, &original|
15
+ File.open(input) do |file|
16
+ json = JSON.parse(file.read)
17
+ original.call(json, **options)
18
+ end
19
+ end
20
+
21
+ xml_matcher = ->(input, **) { File.extname(input) == '.xml' }
22
+ xml_caller = lambda do |input, **options, &original|
23
+ File.open(input) do |file|
24
+ original.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
+ # === Logging Module (uses _node_:) ===
43
+
44
+ module Logging
45
+ INDENT_SIZE = 2
46
+ BLANK = ' '
47
+ LIST_STYLE = '*'
48
+ INPUT_LABEL = 'Input :'
49
+ OUTPUT_LABEL = 'Output:'
50
+
51
+ def self.loggable(node)
52
+ node.after_matcher! do |matched, _node_:, **|
53
+ prefix = LIST_STYLE.rjust((_node_.depth * INDENT_SIZE) - INDENT_SIZE + LIST_STYLE.length, BLANK)
54
+ puts "#{prefix} #{_node_.identity}: [matched: #{matched}]"
55
+ matched
56
+ end
57
+
58
+ return unless node.external?
59
+
60
+ node
61
+ .before_caller! do |input, *, _node_:, **|
62
+ input_prefix = INPUT_LABEL.rjust((_node_.depth * INDENT_SIZE) + INPUT_LABEL.length, BLANK)
63
+ puts "#{input_prefix} #{input}"
64
+ input
65
+ end
66
+ .after_caller! do |output, _node_:, **|
67
+ output_prefix = OUTPUT_LABEL.rjust((_node_.depth * INDENT_SIZE) + OUTPUT_LABEL.length, BLANK)
68
+ puts "#{output_prefix} #{output}"
69
+ output
70
+ end
71
+ end
72
+ end
73
+
74
+ loggable = Logging.method(:loggable)
75
+
76
+ # === Tree Structure ===
77
+
78
+ tree = CallableTree::Node::Root.new.seekable.append(
79
+ CallableTree::Node::Internal.create(
80
+ matcher: json_matcher,
81
+ caller: json_caller,
82
+ terminator: terminator_true,
83
+ hookable: true
84
+ ).tap(&loggable).seekable.append(
85
+ CallableTree::Node::External.create(
86
+ matcher: animals_json_matcher,
87
+ caller: animals_json_caller,
88
+ hookable: true
89
+ ).tap(&loggable).verbosify,
90
+ CallableTree::Node::External.create(
91
+ matcher: fruits_json_matcher,
92
+ caller: fruits_json_caller,
93
+ hookable: true
94
+ ).tap(&loggable).verbosify
95
+ ),
96
+ CallableTree::Node::Internal.create(
97
+ matcher: xml_matcher,
98
+ caller: xml_caller,
99
+ terminator: terminator_true,
100
+ hookable: true
101
+ ).tap(&loggable).seekable.append(
102
+ CallableTree::Node::External.create(
103
+ matcher: animals_xml_matcher,
104
+ caller: animals_xml_caller,
105
+ hookable: true
106
+ ).tap(&loggable).verbosify,
107
+ CallableTree::Node::External.create(
108
+ matcher: fruits_xml_matcher,
109
+ caller: fruits_xml_caller,
110
+ hookable: true
111
+ ).tap(&loggable).verbosify
112
+ )
113
+ )
114
+
115
+ # === Execution ===
116
+
117
+ Dir.glob("#{__dir__}/../docs/*") do |file|
118
+ options = { foo: :bar }
119
+ pp tree.call(file, **options)
120
+ puts '---'
121
+ end
@@ -50,6 +50,7 @@ module CallableTree
50
50
  ::Class
51
51
  .new do
52
52
  include node_type
53
+
53
54
  if hookable
54
55
  prepend Hooks::Matcher
55
56
  prepend Hooks::Caller
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ module Node
5
+ module External
6
+ # Pod: A node that can be instantiated directly with proc-based behavior.
7
+ # Provides an alternative to Builder style for inline node creation.
8
+ #
9
+ # Usage patterns:
10
+ # Constructor style: Pod.new(caller: ->(input, **) { input * 2 })
11
+ # Factory style: External.create(caller: ->(input, **) { input * 2 })
12
+ # Block style: External.create { |node| node.caller { |input, **| input * 2 } }
13
+ class Pod
14
+ include External
15
+
16
+ def initialize(matcher: nil, caller: nil, terminator: nil, identifier: nil)
17
+ @_matcher = matcher
18
+ @_caller = caller
19
+ @_terminator = terminator
20
+ @_identifier = identifier
21
+
22
+ yield self if block_given?
23
+ end
24
+
25
+ # DSL setters for block syntax
26
+ def matcher(proc = nil, &block)
27
+ @_matcher = proc || block
28
+ self
29
+ end
30
+
31
+ def caller(proc = nil, &block)
32
+ @_caller = proc || block
33
+ self
34
+ end
35
+
36
+ def terminator(proc = nil, &block)
37
+ @_terminator = proc || block
38
+ self
39
+ end
40
+
41
+ def identifier(proc = nil, &block)
42
+ @_identifier = proc || block
43
+ self
44
+ end
45
+
46
+ def match?(*inputs, **options)
47
+ return super unless @_matcher
48
+
49
+ @_matcher.call(*inputs, **options, _node_: self) do |*a, **o|
50
+ super(*a, **o)
51
+ end
52
+ end
53
+
54
+ def call(*inputs, **options)
55
+ raise ::CallableTree::Error, 'caller is not set' unless @_caller
56
+
57
+ @_caller.call(*inputs, **options, _node_: self) do |*a, **o|
58
+ super(*a, **o)
59
+ end
60
+ end
61
+
62
+ def terminate?(output, *inputs, **options)
63
+ return super unless @_terminator
64
+
65
+ @_terminator.call(output, *inputs, **options, _node_: self) do |o, *a, **opts|
66
+ super(o, *a, **opts)
67
+ end
68
+ end
69
+
70
+ def identity
71
+ return super unless @_identifier
72
+
73
+ @_identifier.call(_node_: self) { super }
74
+ end
75
+ end
76
+
77
+ # HookablePod: Pod with Hooks support (before/around/after callbacks).
78
+ class HookablePod < Pod
79
+ prepend Hooks::Matcher
80
+ prepend Hooks::Caller
81
+ prepend Hooks::Terminator
82
+ end
83
+
84
+ # Factory method
85
+ def self.create(matcher: nil, caller: nil, terminator: nil, identifier: nil, hookable: false, &block)
86
+ klass = hookable ? HookablePod : Pod
87
+ klass.new(matcher: matcher, caller: caller, terminator: terminator, identifier: identifier, &block)
88
+ end
89
+ end
90
+ end
91
+ end
@@ -11,7 +11,7 @@ module CallableTree
11
11
  end
12
12
 
13
13
  def call(*inputs, **options)
14
- output = super(*inputs, **options)
14
+ output = super
15
15
  options.delete(:_node_)
16
16
  routes = self.routes
17
17
 
@@ -32,6 +32,7 @@ module CallableTree
32
32
 
33
33
  def verbosify!
34
34
  extend Verbose
35
+
35
36
  self
36
37
  end
37
38
 
@@ -42,7 +42,7 @@ module CallableTree
42
42
 
43
43
  terminated =
44
44
  if around_terminator_callbacks.empty?
45
- super(output, *inputs, **options)
45
+ super
46
46
  else
47
47
  around_terminator_callbacks_head, *around_terminator_callbacks_tail = around_terminator_callbacks
48
48
  terminator = proc { super(output, *inputs, **options) }
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ module Node
5
+ module Internal
6
+ # Pod: A node that can be instantiated directly with proc-based behavior.
7
+ # Provides an alternative to Builder style for inline node creation.
8
+ #
9
+ # Usage patterns:
10
+ # Constructor style: Pod.new(caller: ->(input, **) { input * 2 })
11
+ # Factory style: Internal.create(caller: ->(input, **) { input * 2 })
12
+ # Block style: Internal.create { |node| node.caller { |input, **| input * 2 } }
13
+ class Pod
14
+ include Internal
15
+
16
+ def initialize(matcher: nil, caller: nil, terminator: nil, identifier: nil)
17
+ @_matcher = matcher
18
+ @_caller = caller
19
+ @_terminator = terminator
20
+ @_identifier = identifier
21
+
22
+ yield self if block_given?
23
+ end
24
+
25
+ # DSL setters for block syntax
26
+ def matcher(proc = nil, &block)
27
+ @_matcher = proc || block
28
+ self
29
+ end
30
+
31
+ def caller(proc = nil, &block)
32
+ @_caller = proc || block
33
+ self
34
+ end
35
+
36
+ def terminator(proc = nil, &block)
37
+ @_terminator = proc || block
38
+ self
39
+ end
40
+
41
+ def identifier(proc = nil, &block)
42
+ @_identifier = proc || block
43
+ self
44
+ end
45
+
46
+ def match?(*inputs, **options)
47
+ return super unless @_matcher
48
+
49
+ @_matcher.call(*inputs, **options, _node_: self) do |*a, **o|
50
+ super(*a, **o)
51
+ end
52
+ end
53
+
54
+ def call(*inputs, **options)
55
+ return super unless @_caller
56
+
57
+ @_caller.call(*inputs, **options, _node_: self) do |*a, **o|
58
+ super(*a, **o)
59
+ end
60
+ end
61
+
62
+ def terminate?(output, *inputs, **options)
63
+ return super unless @_terminator
64
+
65
+ @_terminator.call(output, *inputs, **options, _node_: self) do |o, *a, **opts|
66
+ super(o, *a, **opts)
67
+ end
68
+ end
69
+
70
+ def identity
71
+ return super unless @_identifier
72
+
73
+ @_identifier.call(_node_: self) { super }
74
+ end
75
+ end
76
+
77
+ # HookablePod: Pod with Hooks support (before/around/after callbacks).
78
+ class HookablePod < Pod
79
+ prepend Hooks::Matcher
80
+ prepend Hooks::Caller
81
+ prepend Hooks::Terminator
82
+ end
83
+
84
+ # Factory method
85
+ def self.create(matcher: nil, caller: nil, terminator: nil, identifier: nil, hookable: false, &block)
86
+ klass = hookable ? HookablePod : Pod
87
+ klass.new(matcher: matcher, caller: caller, terminator: terminator, identifier: identifier, &block)
88
+ end
89
+ end
90
+ end
91
+ end
@@ -3,7 +3,7 @@
3
3
  module CallableTree
4
4
  module Node
5
5
  module Internal
6
- module Strategyable
6
+ module Strategizable
7
7
  def self.included(mod)
8
8
  mod.extend ClassMethods
9
9
  end
@@ -70,8 +70,8 @@ module CallableTree
70
70
  key = key.to_sym
71
71
  config[:alias] = key unless config[:alias]
72
72
  config[:factory] = DEFAUTL_FACTORY unless config[:factory]
73
- Strategyable.__send__(:strategy_configs)[key] = config
74
- Strategyable.__send__(:define_strategy_methods, key, config)
73
+ Strategizable.__send__(:strategy_configs)[key] = config
74
+ Strategizable.__send__(:define_strategy_methods, key, config)
75
75
  end
76
76
  end
77
77
 
@@ -95,7 +95,7 @@ module CallableTree
95
95
  private
96
96
 
97
97
  def strategize(name, *args, matchable:, terminable:, **kwargs)
98
- clone.strategy!(name, *args, matchable: matchable, terminable: terminable, **kwargs)
98
+ clone.strategize!(name, *args, matchable: matchable, terminable: terminable, **kwargs)
99
99
  end
100
100
 
101
101
  def strategize!(name, *args, matchable:, terminable:, **kwargs)
@@ -5,7 +5,7 @@ module CallableTree
5
5
  module Internal
6
6
  extend ::Forwardable
7
7
  include Node
8
- include Strategyable
8
+ include Strategizable
9
9
 
10
10
  def self.included(mod)
11
11
  return unless mod.include?(External)
@@ -46,7 +46,7 @@ module CallableTree
46
46
  .lazy
47
47
  .select(&:internal?)
48
48
  .map { |node| node.find(recursive: true, &block) }
49
- .reject(&:nil?)
49
+ .reject(&:nil?) # rubocop:disable Style/CollectionCompact
50
50
  .first
51
51
  end
52
52
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CallableTree
4
- VERSION = '0.3.10'
4
+ VERSION = '0.3.12'
5
5
  end
data/lib/callable_tree.rb CHANGED
@@ -14,10 +14,12 @@ require_relative 'callable_tree/node/internal/strategy'
14
14
  require_relative 'callable_tree/node/internal/strategy/broadcast'
15
15
  require_relative 'callable_tree/node/internal/strategy/seek'
16
16
  require_relative 'callable_tree/node/internal/strategy/compose'
17
- require_relative 'callable_tree/node/internal/strategyable'
17
+ require_relative 'callable_tree/node/internal/strategizable'
18
18
  require_relative 'callable_tree/node/external/verbose'
19
19
  require_relative 'callable_tree/node/external'
20
20
  require_relative 'callable_tree/node/internal'
21
+ require_relative 'callable_tree/node/external/pod'
22
+ require_relative 'callable_tree/node/internal/pod'
21
23
  require_relative 'callable_tree/node/builder'
22
24
  require_relative 'callable_tree/node/internal/builder'
23
25
  require_relative 'callable_tree/node/external/builder'
data/mise.toml ADDED
@@ -0,0 +1,2 @@
1
+ [tools]
2
+ ruby = "4.0.0"