callable_tree 0.3.5 → 0.3.8

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +17 -0
  5. data/Gemfile.lock +2 -2
  6. data/README.md +366 -108
  7. data/callable_tree.gemspec +1 -0
  8. data/examples/builder/hooks.rb +67 -0
  9. data/examples/builder/internal-seekable.rb +69 -73
  10. data/examples/builder/logging.rb +129 -0
  11. data/examples/{external-verbosify.rb → class/external-verbosify.rb} +1 -1
  12. data/examples/class/hooks.rb +70 -0
  13. data/examples/{identity.rb → class/identity.rb} +1 -1
  14. data/examples/{internal-broadcastable.rb → class/internal-broadcastable.rb} +0 -0
  15. data/examples/{internal-composable.rb → class/internal-composable.rb} +0 -0
  16. data/examples/{internal-seekable.rb → class/internal-seekable.rb} +1 -1
  17. data/examples/{logging.rb → class/logging.rb} +45 -43
  18. data/lib/callable_tree/node/builder.rb +20 -4
  19. data/lib/callable_tree/node/external/builder.rb +1 -1
  20. data/lib/callable_tree/node/external/verbose.rb +1 -1
  21. data/lib/callable_tree/node/external.rb +15 -0
  22. data/lib/callable_tree/node/hooks/caller.rb +110 -0
  23. data/lib/callable_tree/node/hooks/matcher.rb +101 -0
  24. data/lib/callable_tree/node/hooks/terminator.rb +99 -0
  25. data/lib/callable_tree/node/internal.rb +27 -12
  26. data/lib/callable_tree/node/root.rb +3 -1
  27. data/lib/callable_tree/node.rb +8 -0
  28. data/lib/callable_tree/version.rb +1 -1
  29. data/lib/callable_tree.rb +3 -1
  30. metadata +17 -12
  31. data/examples/builder/hooks-call.rb +0 -38
  32. data/examples/hooks-call.rb +0 -39
  33. data/lib/callable_tree/node/hooks/call.rb +0 -81
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'callable_tree'
4
+
5
+ HooksSample =
6
+ CallableTree::Node::Internal::Builder
7
+ .new
8
+ .hookable
9
+ .build
10
+
11
+ CallableTree::Node::Root.new.append(
12
+ HooksSample
13
+ .new
14
+ .append(
15
+ # anonymous external node
16
+ lambda do |input, **_options|
17
+ puts "external input: #{input}"
18
+ input * 2
19
+ end
20
+ )
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
@@ -4,89 +4,85 @@ require 'callable_tree'
4
4
  require 'json'
5
5
  require 'rexml/document'
6
6
 
7
- module JSONParser
8
- def self.build
9
- CallableTree::Node::Internal::Builder
10
- .new
11
- .matcher do |input, **_options|
12
- File.extname(input) == '.json'
13
- end
14
- .caller do |input, **options, &block|
15
- File.open(input) do |file|
16
- json = ::JSON.load(file)
17
- # The following block call is equivalent to calling `super` in the class style.
18
- block.call(json, **options)
19
- end
20
- end
21
- .terminator do
22
- true
23
- end
24
- .build
7
+ JSONParser =
8
+ CallableTree::Node::Internal::Builder
9
+ .new
10
+ .matcher do |input, **_options|
11
+ File.extname(input) == '.json'
25
12
  end
26
- end
27
-
28
- module XMLParser
29
- def self.build
30
- CallableTree::Node::Internal::Builder
31
- .new
32
- .matcher do |input, **_options|
33
- File.extname(input) == '.xml'
34
- end
35
- .caller do |input, **options, &block|
36
- File.open(input) do |file|
37
- # The following block call is equivalent to calling `super` in the class style.
38
- block.call(REXML::Document.new(file), **options)
39
- end
40
- end
41
- .terminator do
42
- true
43
- end
44
- .build
13
+ .caller do |input, **options, &block|
14
+ File.open(input) do |file|
15
+ json = ::JSON.load(file)
16
+ # The following block call is equivalent to calling `super` in the class style.
17
+ block.call(json, **options)
18
+ end
45
19
  end
46
- end
20
+ .terminator do
21
+ true
22
+ end
23
+ .build
47
24
 
48
- module JSONScraper
49
- def self.build(type)
50
- CallableTree::Node::External::Builder
51
- .new
52
- .matcher do |input, **_options|
53
- !!input[type.to_s]
54
- end
55
- .caller do |input, **_options|
56
- input[type.to_s]
57
- .map { |element| [element['name'], element['emoji']] }
58
- .to_h
59
- end
60
- .build
25
+ XMLParser =
26
+ CallableTree::Node::Internal::Builder
27
+ .new
28
+ .matcher do |input, **_options|
29
+ File.extname(input) == '.xml'
30
+ end
31
+ .caller do |input, **options, &block|
32
+ File.open(input) do |file|
33
+ # The following block call is equivalent to calling `super` in the class style.
34
+ block.call(REXML::Document.new(file), **options)
35
+ end
61
36
  end
37
+ .terminator do
38
+ true
39
+ end
40
+ .build
41
+
42
+ def build_json_scraper(type)
43
+ CallableTree::Node::External::Builder
44
+ .new
45
+ .matcher do |input, **_options|
46
+ !!input[type.to_s]
47
+ end
48
+ .caller do |input, **_options|
49
+ input[type.to_s]
50
+ .map { |element| [element['name'], element['emoji']] }
51
+ .to_h
52
+ end
53
+ .build
62
54
  end
63
55
 
64
- module XMLScraper
65
- def self.build(type)
66
- CallableTree::Node::External::Builder
67
- .new
68
- .matcher do |input, **_options|
69
- !input.get_elements("//#{type}").empty?
70
- end
71
- .caller do |input, **_options|
72
- input
73
- .get_elements("//#{type}")
74
- .first
75
- .map { |element| [element['name'], element['emoji']] }
76
- .to_h
77
- end
78
- .build
79
- end
56
+ AnimalsJSONScraper = build_json_scraper(:animals)
57
+ FruitsJSONScraper = build_json_scraper(:fruits)
58
+
59
+ def build_xml_scraper(type)
60
+ CallableTree::Node::External::Builder
61
+ .new
62
+ .matcher do |input, **_options|
63
+ !input.get_elements("//#{type}").empty?
64
+ end
65
+ .caller do |input, **_options|
66
+ input
67
+ .get_elements("//#{type}")
68
+ .first
69
+ .map { |element| [element['name'], element['emoji']] }
70
+ .to_h
71
+ end
72
+ .build
80
73
  end
81
74
 
75
+ AnimalsXMLScraper = build_xml_scraper(:animals)
76
+ FruitsXMLScraper = build_xml_scraper(:fruits)
77
+
82
78
  tree = CallableTree::Node::Root.new.seekable.append(
83
- JSONParser.build.new.seekable.append(
84
- JSONScraper.build(:animals).new,
85
- JSONScraper.build(:fruits).new
79
+ JSONParser.new.seekable.append(
80
+ AnimalsJSONScraper.new,
81
+ FruitsJSONScraper.new
86
82
  ),
87
- XMLParser.build.new.seekable.append(
88
- XMLScraper.build(:animals).new,
89
- XMLScraper.build(:fruits).new
83
+ XMLParser.new.seekable.append(
84
+ AnimalsXMLScraper.new,
85
+ FruitsXMLScraper.new
90
86
  )
91
87
  )
92
88
 
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'callable_tree'
4
+ require 'json'
5
+ require 'rexml/document'
6
+
7
+ JSONParser =
8
+ CallableTree::Node::Internal::Builder
9
+ .new
10
+ .matcher do |input, **_options|
11
+ File.extname(input) == '.json'
12
+ end
13
+ .caller do |input, **options, &block|
14
+ File.open(input) do |file|
15
+ json = ::JSON.load(file)
16
+ # The following block call is equivalent to calling `super` in the class style.
17
+ block.call(json, **options)
18
+ end
19
+ end
20
+ .terminator { true }
21
+ # .identifier { |_node_:| :as_you_like } # optional
22
+ .hookable
23
+ .build
24
+
25
+ XMLParser =
26
+ CallableTree::Node::Internal::Builder
27
+ .new
28
+ .matcher do |input, **_options|
29
+ File.extname(input) == '.xml'
30
+ end
31
+ .caller do |input, **options, &block|
32
+ File.open(input) do |file|
33
+ # The following block call is equivalent to calling `super` in the class style.
34
+ block.call(REXML::Document.new(file), **options)
35
+ end
36
+ end
37
+ .terminator { true }
38
+ # .identifier { |_node_:| :as_you_like } # optional
39
+ .hookable
40
+ .build
41
+
42
+ def build_json_scraper(type)
43
+ CallableTree::Node::External::Builder
44
+ .new
45
+ .matcher do |input, **_options|
46
+ !!input[type.to_s]
47
+ end
48
+ .caller do |input, **_options|
49
+ input[type.to_s]
50
+ .map { |element| [element['name'], element['emoji']] }
51
+ .to_h
52
+ end
53
+ # .identifier { |_node_:| :as_you_like } # optional
54
+ .hookable
55
+ .build
56
+ end
57
+
58
+ AnimalsJSONScraper = build_json_scraper(:animals)
59
+ FruitsJSONScraper = build_json_scraper(:fruits)
60
+
61
+ def build_xml_scraper(type)
62
+ CallableTree::Node::External::Builder
63
+ .new
64
+ .matcher do |input, **_options|
65
+ !input.get_elements("//#{type}").empty?
66
+ end
67
+ .caller do |input, **_options|
68
+ input
69
+ .get_elements("//#{type}")
70
+ .first
71
+ .map { |element| [element['name'], element['emoji']] }
72
+ .to_h
73
+ end
74
+ # .identifier { |_node_:| :as_you_like } # optional
75
+ .hookable
76
+ .build
77
+ end
78
+
79
+ AnimalsXMLScraper = build_xml_scraper(:animals)
80
+ FruitsXMLScraper = build_xml_scraper(:fruits)
81
+
82
+ module Logging
83
+ INDENT_SIZE = 2
84
+ BLANK = ' '
85
+ LIST_STYLE = '*'
86
+ INPUT_LABEL = 'Input :'
87
+ OUTPUT_LABEL = 'Output:'
88
+
89
+ def self.loggable(node)
90
+ node.after_matcher! do |matched, _node_:, **|
91
+ prefix = LIST_STYLE.rjust((_node_.depth * INDENT_SIZE) - INDENT_SIZE + LIST_STYLE.length, BLANK)
92
+ puts "#{prefix} #{_node_.identity}: [matched: #{matched}]"
93
+ matched
94
+ end
95
+
96
+ if node.external?
97
+ node
98
+ .before_caller! do |input, *, _node_:, **|
99
+ input_prefix = INPUT_LABEL.rjust((_node_.depth * INDENT_SIZE) + INPUT_LABEL.length, BLANK)
100
+ puts "#{input_prefix} #{input}"
101
+ input
102
+ end
103
+ .after_caller! do |output, _node_:, **|
104
+ output_prefix = OUTPUT_LABEL.rjust((_node_.depth * INDENT_SIZE) + OUTPUT_LABEL.length, BLANK)
105
+ puts "#{output_prefix} #{output}"
106
+ output
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ loggable = Logging.method(:loggable)
113
+
114
+ tree = CallableTree::Node::Root.new.seekable.append(
115
+ JSONParser.new.tap(&loggable).seekable.append(
116
+ AnimalsJSONScraper.new.tap(&loggable).verbosify,
117
+ FruitsJSONScraper.new.tap(&loggable).verbosify
118
+ ),
119
+ XMLParser.new.tap(&loggable).seekable.append(
120
+ AnimalsXMLScraper.new.tap(&loggable).verbosify,
121
+ FruitsXMLScraper.new.tap(&loggable).verbosify
122
+ )
123
+ )
124
+
125
+ Dir.glob("#{__dir__}/../docs/*") do |file|
126
+ options = { foo: :bar }
127
+ pp tree.call(file, **options)
128
+ puts '---'
129
+ end
@@ -96,7 +96,7 @@ tree = CallableTree::Node::Root.new.append(
96
96
  )
97
97
  )
98
98
 
99
- Dir.glob("#{__dir__}/docs/*") do |file|
99
+ Dir.glob("#{__dir__}/../docs/*") do |file|
100
100
  options = { foo: :bar }
101
101
  pp tree.call(file, **options)
102
102
  puts '---'
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'callable_tree'
4
+
5
+ module Node
6
+ class HooksSample
7
+ include CallableTree::Node::Internal
8
+ prepend CallableTree::Node::Hooks::Matcher
9
+ prepend CallableTree::Node::Hooks::Caller
10
+ prepend CallableTree::Node::Hooks::Terminator
11
+ end
12
+ end
13
+
14
+ CallableTree::Node::Root.new.append(
15
+ Node::HooksSample
16
+ .new
17
+ .append(
18
+ # anonymous external node
19
+ lambda do |input, **_options|
20
+ puts "external input: #{input}"
21
+ input * 2
22
+ end
23
+ )
24
+ .before_matcher do |input, **_options|
25
+ puts "before_matcher input: #{input}"
26
+ input + 1
27
+ end
28
+ .around_matcher do |input, **_options, &block|
29
+ puts "around_matcher input: #{input}"
30
+ matched = block.call
31
+ puts "around_matcher matched: #{matched}"
32
+ !matched
33
+ end
34
+ .after_matcher do |matched, **_options|
35
+ puts "after_matcher matched: #{matched}"
36
+ !matched
37
+ end
38
+ .before_caller do |input, **_options|
39
+ puts "before_caller input: #{input}"
40
+ input + 1
41
+ end
42
+ .around_caller do |input, **_options, &block|
43
+ puts "around_caller input: #{input}"
44
+ output = block.call
45
+ puts "around_caller output: #{output}"
46
+ output * input
47
+ end
48
+ .after_caller do |output, **_options|
49
+ puts "after_caller output: #{output}"
50
+ output * 2
51
+ end
52
+ .before_terminator do |output, *_inputs, **_options|
53
+ puts "before_terminator output: #{output}"
54
+ output + 1
55
+ end
56
+ .around_terminator do |output, *_inputs, **_options, &block|
57
+ puts "around_terminator output: #{output}"
58
+ terminated = block.call
59
+ puts "around_terminator terminated: #{terminated}"
60
+ !terminated
61
+ end
62
+ .after_terminator do |terminated, **_options|
63
+ puts "after_terminator terminated: #{terminated}"
64
+ !terminated
65
+ end
66
+ ).tap do |tree|
67
+ options = { foo: :bar }
68
+ output = tree.call(1, **options)
69
+ puts "result: #{output}"
70
+ end
@@ -117,7 +117,7 @@ tree = CallableTree::Node::Root.new.append(
117
117
  )
118
118
  )
119
119
 
120
- Dir.glob("#{__dir__}/docs/*") do |file|
120
+ Dir.glob("#{__dir__}/../docs/*") do |file|
121
121
  options = { foo: :bar }
122
122
  pp tree.call(file, **options)
123
123
  puts '---'
@@ -96,7 +96,7 @@ tree = CallableTree::Node::Root.new.seekable.append(
96
96
  )
97
97
  )
98
98
 
99
- Dir.glob("#{__dir__}/docs/*") do |file|
99
+ Dir.glob("#{__dir__}/../docs/*") do |file|
100
100
  options = { foo: :bar }
101
101
  pp tree.call(file, **options)
102
102
  puts '---'
@@ -5,36 +5,6 @@ require 'json'
5
5
  require 'rexml/document'
6
6
 
7
7
  module Node
8
- module Logging
9
- INDENT_SIZE = 2
10
- BLANK = ' '
11
-
12
- module Match
13
- LIST_STYLE = '*'
14
-
15
- def match?(_input, **_options)
16
- super.tap do |matched|
17
- prefix = LIST_STYLE.rjust(depth * INDENT_SIZE - INDENT_SIZE + LIST_STYLE.length, BLANK)
18
- puts "#{prefix} #{identity}: [matched: #{matched}]"
19
- end
20
- end
21
- end
22
-
23
- module Call
24
- INPUT_LABEL = 'Input :'
25
- OUTPUT_LABEL = 'Output:'
26
-
27
- def call(input, **_options)
28
- super.tap do |output|
29
- input_prefix = INPUT_LABEL.rjust(depth * INDENT_SIZE + INPUT_LABEL.length, BLANK)
30
- puts "#{input_prefix} #{input}"
31
- output_prefix = OUTPUT_LABEL.rjust(depth * INDENT_SIZE + OUTPUT_LABEL.length, BLANK)
32
- puts "#{output_prefix} #{output}"
33
- end
34
- end
35
- end
36
- end
37
-
38
8
  class Identity
39
9
  attr_reader :klass, :type
40
10
 
@@ -51,7 +21,7 @@ module Node
51
21
  module JSON
52
22
  class Parser
53
23
  include CallableTree::Node::Internal
54
- prepend Logging::Match
24
+ prepend CallableTree::Node::Hooks::Matcher
55
25
 
56
26
  def match?(input, **_options)
57
27
  File.extname(input) == '.json'
@@ -71,8 +41,8 @@ module Node
71
41
 
72
42
  class Scraper
73
43
  include CallableTree::Node::External
74
- prepend Logging::Match
75
- prepend Logging::Call
44
+ prepend CallableTree::Node::Hooks::Matcher
45
+ prepend CallableTree::Node::Hooks::Caller
76
46
 
77
47
  def initialize(type:)
78
48
  @type = type
@@ -97,7 +67,7 @@ module Node
97
67
  module XML
98
68
  class Parser
99
69
  include CallableTree::Node::Internal
100
- prepend Logging::Match
70
+ prepend CallableTree::Node::Hooks::Matcher
101
71
 
102
72
  def match?(input, **_options)
103
73
  File.extname(input) == '.xml'
@@ -116,8 +86,8 @@ module Node
116
86
 
117
87
  class Scraper
118
88
  include CallableTree::Node::External
119
- prepend Logging::Match
120
- prepend Logging::Call
89
+ prepend CallableTree::Node::Hooks::Matcher
90
+ prepend CallableTree::Node::Hooks::Caller
121
91
 
122
92
  def initialize(type:)
123
93
  @type = type
@@ -142,18 +112,50 @@ module Node
142
112
  end
143
113
  end
144
114
 
115
+ module Logging
116
+ INDENT_SIZE = 2
117
+ BLANK = ' '
118
+ LIST_STYLE = '*'
119
+ INPUT_LABEL = 'Input :'
120
+ OUTPUT_LABEL = 'Output:'
121
+
122
+ def self.loggable(node)
123
+ node.after_matcher! do |matched, _node_:, **|
124
+ prefix = LIST_STYLE.rjust((_node_.depth * INDENT_SIZE) - INDENT_SIZE + LIST_STYLE.length, BLANK)
125
+ puts "#{prefix} #{_node_.identity}: [matched: #{matched}]"
126
+ matched
127
+ end
128
+
129
+ if node.external?
130
+ node
131
+ .before_caller! do |input, *, _node_:, **|
132
+ input_prefix = INPUT_LABEL.rjust((_node_.depth * INDENT_SIZE) + INPUT_LABEL.length, BLANK)
133
+ puts "#{input_prefix} #{input}"
134
+ input
135
+ end
136
+ .after_caller! do |output, _node_:, **|
137
+ output_prefix = OUTPUT_LABEL.rjust((_node_.depth * INDENT_SIZE) + OUTPUT_LABEL.length, BLANK)
138
+ puts "#{output_prefix} #{output}"
139
+ output
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ loggable = Logging.method(:loggable)
146
+
145
147
  tree = CallableTree::Node::Root.new.append(
146
- Node::JSON::Parser.new.append(
147
- Node::JSON::Scraper.new(type: :animals).verbosify,
148
- Node::JSON::Scraper.new(type: :fruits).verbosify
148
+ Node::JSON::Parser.new.tap(&loggable).append(
149
+ Node::JSON::Scraper.new(type: :animals).tap(&loggable).verbosify,
150
+ Node::JSON::Scraper.new(type: :fruits).tap(&loggable).verbosify
149
151
  ),
150
- Node::XML::Parser.new.append(
151
- Node::XML::Scraper.new(type: :animals).verbosify,
152
- Node::XML::Scraper.new(type: :fruits).verbosify
152
+ Node::XML::Parser.new.tap(&loggable).append(
153
+ Node::XML::Scraper.new(type: :animals).tap(&loggable).verbosify,
154
+ Node::XML::Scraper.new(type: :fruits).tap(&loggable).verbosify
153
155
  )
154
156
  )
155
157
 
156
- Dir.glob("#{__dir__}/docs/*") do |file|
158
+ Dir.glob("#{__dir__}/../docs/*") do |file|
157
159
  options = { foo: :bar }
158
160
  pp tree.call(file, **options)
159
161
  puts '---'
@@ -24,6 +24,11 @@ module CallableTree
24
24
  self
25
25
  end
26
26
 
27
+ def identifier(&block)
28
+ @identifier = block
29
+ self
30
+ end
31
+
27
32
  def hookable(hookable = true)
28
33
  @hookable = hookable
29
34
  self
@@ -33,6 +38,7 @@ module CallableTree
33
38
  matcher = @matcher
34
39
  caller = @caller
35
40
  terminator = @terminator
41
+ identifier = @identifier
36
42
  hookable = @hookable
37
43
 
38
44
  validate(
@@ -44,11 +50,15 @@ module CallableTree
44
50
  ::Class
45
51
  .new do
46
52
  include node_type
47
- prepend Hooks::Call if hookable
53
+ if hookable
54
+ prepend Hooks::Matcher
55
+ prepend Hooks::Caller
56
+ prepend Hooks::Terminator
57
+ end
48
58
 
49
59
  if matcher
50
60
  define_method(:match?) do |*inputs, **options|
51
- matcher.call(*inputs, **options) do |*inputs, **options|
61
+ matcher.call(*inputs, **options, _node_: self) do |*inputs, **options|
52
62
  super(*inputs, **options)
53
63
  end
54
64
  end
@@ -56,7 +66,7 @@ module CallableTree
56
66
 
57
67
  if caller
58
68
  define_method(:call) do |*inputs, **options|
59
- caller.call(*inputs, **options) do |*inputs, **options|
69
+ caller.call(*inputs, **options, _node_: self) do |*inputs, **options|
60
70
  super(*inputs, **options)
61
71
  end
62
72
  end
@@ -64,11 +74,17 @@ module CallableTree
64
74
 
65
75
  if terminator
66
76
  define_method(:terminate?) do |output, *inputs, **options|
67
- terminator.call(output, *inputs, **options) do |output, *inputs, **options|
77
+ terminator.call(output, *inputs, **options, _node_: self) do |output, *inputs, **options|
68
78
  super(output, *inputs, **options)
69
79
  end
70
80
  end
71
81
  end
82
+
83
+ if identifier
84
+ define_method(:identity) do
85
+ identifier.call(_node_: self) { super() }
86
+ end
87
+ end
72
88
  end
73
89
  end
74
90
 
@@ -15,7 +15,7 @@ module CallableTree
15
15
  private
16
16
 
17
17
  def validate(matcher:, caller:, terminator:)
18
- raise Error 'caller is required' unless caller
18
+ raise Error, 'caller is required' unless caller
19
19
  end
20
20
  end
21
21
  end
@@ -3,7 +3,6 @@
3
3
  module CallableTree
4
4
  module Node
5
5
  module External
6
- # TODO: Add :inputs
7
6
  Output = Struct.new(:value, :options, :routes)
8
7
 
9
8
  module Verbose
@@ -13,6 +12,7 @@ module CallableTree
13
12
 
14
13
  def call(*inputs, **options)
15
14
  output = super(*inputs, **options)
15
+ options.delete(:_node_)
16
16
  routes = self.routes
17
17
 
18
18
  Output.new(output, options, routes)