callable_tree 0.3.5 → 0.3.8

Sign up to get free protection for your applications and to get access to all the features.
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)