callable_tree 0.3.8 → 0.3.9
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/dependabot.yml +7 -0
- data/.github/workflows/build.yml +3 -5
- data/.github/workflows/codeql-analysis.yml +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +3 -3
- data/README.md +183 -257
- data/examples/builder/external-verbosify.rb +87 -0
- data/examples/builder/identity.rb +91 -0
- data/examples/builder/internal-broadcastable.rb +11 -11
- data/examples/builder/internal-composable.rb +11 -11
- data/examples/builder/internal-seekable.rb +9 -15
- data/examples/builder/logging.rb +7 -13
- data/examples/class/external-verbosify.rb +2 -4
- data/examples/class/identity.rb +2 -4
- data/examples/class/internal-seekable.rb +2 -4
- data/examples/class/logging.rb +2 -4
- data/lib/callable_tree/node/internal/strategy/broadcast.rb +19 -2
- data/lib/callable_tree/node/internal/strategy/compose.rb +15 -3
- data/lib/callable_tree/node/internal/strategy/seek.rb +11 -1
- data/lib/callable_tree/node/internal/strategy.rb +10 -2
- data/lib/callable_tree/node/internal.rb +22 -28
- data/lib/callable_tree/version.rb +1 -1
- metadata +6 -3
@@ -0,0 +1,87 @@
|
|
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
|
+
.build
|
22
|
+
|
23
|
+
XMLParser =
|
24
|
+
CallableTree::Node::Internal::Builder
|
25
|
+
.new
|
26
|
+
.matcher do |input, **_options|
|
27
|
+
File.extname(input) == '.xml'
|
28
|
+
end
|
29
|
+
.caller do |input, **options, &block|
|
30
|
+
File.open(input) do |file|
|
31
|
+
# The following block call is equivalent to calling `super` in the class style.
|
32
|
+
block.call(REXML::Document.new(file), **options)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
.terminator { true }
|
36
|
+
.build
|
37
|
+
|
38
|
+
def build_json_scraper(type)
|
39
|
+
CallableTree::Node::External::Builder
|
40
|
+
.new
|
41
|
+
.matcher do |input, **_options|
|
42
|
+
!input[type.to_s].nil?
|
43
|
+
end
|
44
|
+
.caller do |input, **_options|
|
45
|
+
input[type.to_s]
|
46
|
+
.to_h { |element| [element['name'], element['emoji']] }
|
47
|
+
end
|
48
|
+
.build
|
49
|
+
end
|
50
|
+
|
51
|
+
AnimalsJSONScraper = build_json_scraper(:animals)
|
52
|
+
FruitsJSONScraper = build_json_scraper(:fruits)
|
53
|
+
|
54
|
+
def build_xml_scraper(type)
|
55
|
+
CallableTree::Node::External::Builder
|
56
|
+
.new
|
57
|
+
.matcher do |input, **_options|
|
58
|
+
!input.get_elements("//#{type}").empty?
|
59
|
+
end
|
60
|
+
.caller do |input, **_options|
|
61
|
+
input
|
62
|
+
.get_elements("//#{type}")
|
63
|
+
.first
|
64
|
+
.to_h { |element| [element['name'], element['emoji']] }
|
65
|
+
end
|
66
|
+
.build
|
67
|
+
end
|
68
|
+
|
69
|
+
AnimalsXMLScraper = build_xml_scraper(:animals)
|
70
|
+
FruitsXMLScraper = build_xml_scraper(:fruits)
|
71
|
+
|
72
|
+
tree = CallableTree::Node::Root.new.seekable.append(
|
73
|
+
JSONParser.new.seekable.append(
|
74
|
+
AnimalsJSONScraper.new.verbosify,
|
75
|
+
FruitsJSONScraper.new.verbosify
|
76
|
+
),
|
77
|
+
XMLParser.new.seekable.append(
|
78
|
+
AnimalsXMLScraper.new.verbosify,
|
79
|
+
FruitsXMLScraper.new.verbosify
|
80
|
+
)
|
81
|
+
)
|
82
|
+
|
83
|
+
Dir.glob("#{__dir__}/../docs/*") do |file|
|
84
|
+
options = { foo: :bar }
|
85
|
+
pp tree.call(file, **options)
|
86
|
+
puts '---'
|
87
|
+
end
|
@@ -0,0 +1,91 @@
|
|
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_:| _node_.object_id }
|
22
|
+
.build
|
23
|
+
|
24
|
+
XMLParser =
|
25
|
+
CallableTree::Node::Internal::Builder
|
26
|
+
.new
|
27
|
+
.matcher do |input, **_options|
|
28
|
+
File.extname(input) == '.xml'
|
29
|
+
end
|
30
|
+
.caller do |input, **options, &block|
|
31
|
+
File.open(input) do |file|
|
32
|
+
# The following block call is equivalent to calling `super` in the class style.
|
33
|
+
block.call(REXML::Document.new(file), **options)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
.terminator { true }
|
37
|
+
.identifier { |_node_:| _node_.object_id }
|
38
|
+
.build
|
39
|
+
|
40
|
+
def build_json_scraper(type)
|
41
|
+
CallableTree::Node::External::Builder
|
42
|
+
.new
|
43
|
+
.matcher do |input, **_options|
|
44
|
+
!input[type.to_s].nil?
|
45
|
+
end
|
46
|
+
.caller do |input, **_options|
|
47
|
+
input[type.to_s]
|
48
|
+
.to_h { |element| [element['name'], element['emoji']] }
|
49
|
+
end
|
50
|
+
.identifier { |_node_:| _node_.object_id }
|
51
|
+
.build
|
52
|
+
end
|
53
|
+
|
54
|
+
AnimalsJSONScraper = build_json_scraper(:animals)
|
55
|
+
FruitsJSONScraper = build_json_scraper(:fruits)
|
56
|
+
|
57
|
+
def build_xml_scraper(type)
|
58
|
+
CallableTree::Node::External::Builder
|
59
|
+
.new
|
60
|
+
.matcher do |input, **_options|
|
61
|
+
!input.get_elements("//#{type}").empty?
|
62
|
+
end
|
63
|
+
.caller do |input, **_options|
|
64
|
+
input
|
65
|
+
.get_elements("//#{type}")
|
66
|
+
.first
|
67
|
+
.to_h { |element| [element['name'], element['emoji']] }
|
68
|
+
end
|
69
|
+
.identifier { |_node_:| _node_.object_id }
|
70
|
+
.build
|
71
|
+
end
|
72
|
+
|
73
|
+
AnimalsXMLScraper = build_xml_scraper(:animals)
|
74
|
+
FruitsXMLScraper = build_xml_scraper(:fruits)
|
75
|
+
|
76
|
+
tree = CallableTree::Node::Root.new.seekable.append(
|
77
|
+
JSONParser.new.seekable.append(
|
78
|
+
AnimalsJSONScraper.new.verbosify,
|
79
|
+
FruitsJSONScraper.new.verbosify
|
80
|
+
),
|
81
|
+
XMLParser.new.seekable.append(
|
82
|
+
AnimalsXMLScraper.new.verbosify,
|
83
|
+
FruitsXMLScraper.new.verbosify
|
84
|
+
)
|
85
|
+
)
|
86
|
+
|
87
|
+
Dir.glob("#{__dir__}/../docs/*") do |file|
|
88
|
+
options = { foo: :bar }
|
89
|
+
pp tree.call(file, **options)
|
90
|
+
puts '---'
|
91
|
+
end
|
@@ -2,57 +2,57 @@
|
|
2
2
|
|
3
3
|
require 'callable_tree'
|
4
4
|
|
5
|
-
less_than
|
5
|
+
def less_than(num)
|
6
6
|
# The following block call is equivalent to calling `super` in the class style.
|
7
|
-
proc { |input, &
|
7
|
+
proc { |input, &original| original.call(input) && input < num }
|
8
8
|
end
|
9
9
|
|
10
10
|
LessThan5 =
|
11
11
|
CallableTree::Node::Internal::Builder
|
12
12
|
.new
|
13
|
-
.matcher(&less_than.call(5))
|
13
|
+
.matcher(&method(:less_than).call(5))
|
14
14
|
.build
|
15
15
|
|
16
16
|
LessThan10 =
|
17
17
|
CallableTree::Node::Internal::Builder
|
18
18
|
.new
|
19
|
-
.matcher(&less_than.call(10))
|
19
|
+
.matcher(&method(:less_than).call(10))
|
20
20
|
.build
|
21
21
|
|
22
|
-
add
|
22
|
+
def add(num)
|
23
23
|
proc { |input| input + num }
|
24
24
|
end
|
25
25
|
|
26
26
|
Add1 =
|
27
27
|
CallableTree::Node::External::Builder
|
28
28
|
.new
|
29
|
-
.caller(&add.call(1))
|
29
|
+
.caller(&method(:add).call(1))
|
30
30
|
.build
|
31
31
|
|
32
|
-
subtract
|
32
|
+
def subtract(num)
|
33
33
|
proc { |input| input - num }
|
34
34
|
end
|
35
35
|
|
36
36
|
Subtract1 =
|
37
37
|
CallableTree::Node::External::Builder
|
38
38
|
.new
|
39
|
-
.caller(&subtract.call(1))
|
39
|
+
.caller(&method(:subtract).call(1))
|
40
40
|
.build
|
41
41
|
|
42
|
-
multiply
|
42
|
+
def multiply(num)
|
43
43
|
proc { |input| input * num }
|
44
44
|
end
|
45
45
|
|
46
46
|
Multiply2 =
|
47
47
|
CallableTree::Node::External::Builder
|
48
48
|
.new
|
49
|
-
.caller(&multiply.call(2))
|
49
|
+
.caller(&method(:multiply).call(2))
|
50
50
|
.build
|
51
51
|
|
52
52
|
Multiply3 =
|
53
53
|
CallableTree::Node::External::Builder
|
54
54
|
.new
|
55
|
-
.caller(&multiply.call(3))
|
55
|
+
.caller(&method(:multiply).call(3))
|
56
56
|
.build
|
57
57
|
|
58
58
|
tree = CallableTree::Node::Root.new.broadcastable.append(
|
@@ -2,57 +2,57 @@
|
|
2
2
|
|
3
3
|
require 'callable_tree'
|
4
4
|
|
5
|
-
less_than
|
5
|
+
def less_than(num)
|
6
6
|
# The following block call is equivalent to calling `super` in the class style.
|
7
|
-
proc { |input, &
|
7
|
+
proc { |input, &original| original.call(input) && input < num }
|
8
8
|
end
|
9
9
|
|
10
10
|
LessThan5 =
|
11
11
|
CallableTree::Node::Internal::Builder
|
12
12
|
.new
|
13
|
-
.matcher(&less_than.call(5))
|
13
|
+
.matcher(&method(:less_than).call(5))
|
14
14
|
.build
|
15
15
|
|
16
16
|
LessThan10 =
|
17
17
|
CallableTree::Node::Internal::Builder
|
18
18
|
.new
|
19
|
-
.matcher(&less_than.call(10))
|
19
|
+
.matcher(&method(:less_than).call(10))
|
20
20
|
.build
|
21
21
|
|
22
|
-
add
|
22
|
+
def add(num)
|
23
23
|
proc { |input| input + num }
|
24
24
|
end
|
25
25
|
|
26
26
|
Add1 =
|
27
27
|
CallableTree::Node::External::Builder
|
28
28
|
.new
|
29
|
-
.caller(&add.call(1))
|
29
|
+
.caller(&method(:add).call(1))
|
30
30
|
.build
|
31
31
|
|
32
|
-
subtract
|
32
|
+
def subtract(num)
|
33
33
|
proc { |input| input - num }
|
34
34
|
end
|
35
35
|
|
36
36
|
Subtract1 =
|
37
37
|
CallableTree::Node::External::Builder
|
38
38
|
.new
|
39
|
-
.caller(&subtract.call(1))
|
39
|
+
.caller(&method(:subtract).call(1))
|
40
40
|
.build
|
41
41
|
|
42
|
-
multiply
|
42
|
+
def multiply(num)
|
43
43
|
proc { |input| input * num }
|
44
44
|
end
|
45
45
|
|
46
46
|
Multiply2 =
|
47
47
|
CallableTree::Node::External::Builder
|
48
48
|
.new
|
49
|
-
.caller(&multiply.call(2))
|
49
|
+
.caller(&method(:multiply).call(2))
|
50
50
|
.build
|
51
51
|
|
52
52
|
Multiply3 =
|
53
53
|
CallableTree::Node::External::Builder
|
54
54
|
.new
|
55
|
-
.caller(&multiply.call(3))
|
55
|
+
.caller(&method(:multiply).call(3))
|
56
56
|
.build
|
57
57
|
|
58
58
|
tree = CallableTree::Node::Root.new.composable.append(
|
@@ -10,16 +10,14 @@ JSONParser =
|
|
10
10
|
.matcher do |input, **_options|
|
11
11
|
File.extname(input) == '.json'
|
12
12
|
end
|
13
|
-
.caller do |input, **options, &
|
13
|
+
.caller do |input, **options, &original|
|
14
14
|
File.open(input) do |file|
|
15
15
|
json = ::JSON.load(file)
|
16
16
|
# The following block call is equivalent to calling `super` in the class style.
|
17
|
-
|
17
|
+
original.call(json, **options)
|
18
18
|
end
|
19
19
|
end
|
20
|
-
.terminator
|
21
|
-
true
|
22
|
-
end
|
20
|
+
.terminator { true }
|
23
21
|
.build
|
24
22
|
|
25
23
|
XMLParser =
|
@@ -28,27 +26,24 @@ XMLParser =
|
|
28
26
|
.matcher do |input, **_options|
|
29
27
|
File.extname(input) == '.xml'
|
30
28
|
end
|
31
|
-
.caller do |input, **options, &
|
29
|
+
.caller do |input, **options, &original|
|
32
30
|
File.open(input) do |file|
|
33
31
|
# The following block call is equivalent to calling `super` in the class style.
|
34
|
-
|
32
|
+
original.call(REXML::Document.new(file), **options)
|
35
33
|
end
|
36
34
|
end
|
37
|
-
.terminator
|
38
|
-
true
|
39
|
-
end
|
35
|
+
.terminator { true }
|
40
36
|
.build
|
41
37
|
|
42
38
|
def build_json_scraper(type)
|
43
39
|
CallableTree::Node::External::Builder
|
44
40
|
.new
|
45
41
|
.matcher do |input, **_options|
|
46
|
-
|
42
|
+
!input[type.to_s].nil?
|
47
43
|
end
|
48
44
|
.caller do |input, **_options|
|
49
45
|
input[type.to_s]
|
50
|
-
.
|
51
|
-
.to_h
|
46
|
+
.to_h { |element| [element['name'], element['emoji']] }
|
52
47
|
end
|
53
48
|
.build
|
54
49
|
end
|
@@ -66,8 +61,7 @@ def build_xml_scraper(type)
|
|
66
61
|
input
|
67
62
|
.get_elements("//#{type}")
|
68
63
|
.first
|
69
|
-
.
|
70
|
-
.to_h
|
64
|
+
.to_h { |element| [element['name'], element['emoji']] }
|
71
65
|
end
|
72
66
|
.build
|
73
67
|
end
|
data/examples/builder/logging.rb
CHANGED
@@ -10,15 +10,14 @@ JSONParser =
|
|
10
10
|
.matcher do |input, **_options|
|
11
11
|
File.extname(input) == '.json'
|
12
12
|
end
|
13
|
-
.caller do |input, **options, &
|
13
|
+
.caller do |input, **options, &original|
|
14
14
|
File.open(input) do |file|
|
15
15
|
json = ::JSON.load(file)
|
16
16
|
# The following block call is equivalent to calling `super` in the class style.
|
17
|
-
|
17
|
+
original.call(json, **options)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
.terminator { true }
|
21
|
-
# .identifier { |_node_:| :as_you_like } # optional
|
22
21
|
.hookable
|
23
22
|
.build
|
24
23
|
|
@@ -28,14 +27,13 @@ XMLParser =
|
|
28
27
|
.matcher do |input, **_options|
|
29
28
|
File.extname(input) == '.xml'
|
30
29
|
end
|
31
|
-
.caller do |input, **options, &
|
30
|
+
.caller do |input, **options, &original|
|
32
31
|
File.open(input) do |file|
|
33
32
|
# The following block call is equivalent to calling `super` in the class style.
|
34
|
-
|
33
|
+
original.call(REXML::Document.new(file), **options)
|
35
34
|
end
|
36
35
|
end
|
37
36
|
.terminator { true }
|
38
|
-
# .identifier { |_node_:| :as_you_like } # optional
|
39
37
|
.hookable
|
40
38
|
.build
|
41
39
|
|
@@ -43,14 +41,12 @@ def build_json_scraper(type)
|
|
43
41
|
CallableTree::Node::External::Builder
|
44
42
|
.new
|
45
43
|
.matcher do |input, **_options|
|
46
|
-
|
44
|
+
!input[type.to_s].nil?
|
47
45
|
end
|
48
46
|
.caller do |input, **_options|
|
49
47
|
input[type.to_s]
|
50
|
-
.
|
51
|
-
.to_h
|
48
|
+
.to_h { |element| [element['name'], element['emoji']] }
|
52
49
|
end
|
53
|
-
# .identifier { |_node_:| :as_you_like } # optional
|
54
50
|
.hookable
|
55
51
|
.build
|
56
52
|
end
|
@@ -68,10 +64,8 @@ def build_xml_scraper(type)
|
|
68
64
|
input
|
69
65
|
.get_elements("//#{type}")
|
70
66
|
.first
|
71
|
-
.
|
72
|
-
.to_h
|
67
|
+
.to_h { |element| [element['name'], element['emoji']] }
|
73
68
|
end
|
74
|
-
# .identifier { |_node_:| :as_you_like } # optional
|
75
69
|
.hookable
|
76
70
|
.build
|
77
71
|
end
|
@@ -38,8 +38,7 @@ module Node
|
|
38
38
|
|
39
39
|
def call(input, **_options)
|
40
40
|
input[@type.to_s]
|
41
|
-
.
|
42
|
-
.to_h
|
41
|
+
.to_h { |element| [element['name'], element['emoji']] }
|
43
42
|
end
|
44
43
|
end
|
45
44
|
end
|
@@ -78,8 +77,7 @@ module Node
|
|
78
77
|
input
|
79
78
|
.get_elements("//#{@type}")
|
80
79
|
.first
|
81
|
-
.
|
82
|
-
.to_h
|
80
|
+
.to_h { |element| [element['name'], element['emoji']] }
|
83
81
|
end
|
84
82
|
end
|
85
83
|
end
|
data/examples/class/identity.rb
CHANGED
@@ -55,8 +55,7 @@ module Node
|
|
55
55
|
|
56
56
|
def call(input, **_options)
|
57
57
|
input[@type.to_s]
|
58
|
-
.
|
59
|
-
.to_h
|
58
|
+
.to_h { |element| [element['name'], element['emoji']] }
|
60
59
|
end
|
61
60
|
end
|
62
61
|
end
|
@@ -99,8 +98,7 @@ module Node
|
|
99
98
|
input
|
100
99
|
.get_elements("//#{@type}")
|
101
100
|
.first
|
102
|
-
.
|
103
|
-
.to_h
|
101
|
+
.to_h { |element| [element['name'], element['emoji']] }
|
104
102
|
end
|
105
103
|
end
|
106
104
|
end
|
@@ -38,8 +38,7 @@ module Node
|
|
38
38
|
|
39
39
|
def call(input, **_options)
|
40
40
|
input[@type.to_s]
|
41
|
-
.
|
42
|
-
.to_h
|
41
|
+
.to_h { |element| [element['name'], element['emoji']] }
|
43
42
|
end
|
44
43
|
end
|
45
44
|
end
|
@@ -78,8 +77,7 @@ module Node
|
|
78
77
|
input
|
79
78
|
.get_elements("//#{@type}")
|
80
79
|
.first
|
81
|
-
.
|
82
|
-
.to_h
|
80
|
+
.to_h { |element| [element['name'], element['emoji']] }
|
83
81
|
end
|
84
82
|
end
|
85
83
|
end
|
data/examples/class/logging.rb
CHANGED
@@ -58,8 +58,7 @@ module Node
|
|
58
58
|
|
59
59
|
def call(input, **_options)
|
60
60
|
input[@type.to_s]
|
61
|
-
.
|
62
|
-
.to_h
|
61
|
+
.to_h { |element| [element['name'], element['emoji']] }
|
63
62
|
end
|
64
63
|
end
|
65
64
|
end
|
@@ -105,8 +104,7 @@ module Node
|
|
105
104
|
input
|
106
105
|
.get_elements("//#{@type}")
|
107
106
|
.first
|
108
|
-
.
|
109
|
-
.to_h
|
107
|
+
.to_h { |element| [element['name'], element['emoji']] }
|
110
108
|
end
|
111
109
|
end
|
112
110
|
end
|
@@ -7,9 +7,26 @@ module CallableTree
|
|
7
7
|
class Broadcast
|
8
8
|
include Strategy
|
9
9
|
|
10
|
+
def initialize(terminable: false)
|
11
|
+
self.terminable = terminable
|
12
|
+
@terminator =
|
13
|
+
if terminable
|
14
|
+
proc { |node, output, *inputs, **options| node.terminate?(output, *inputs, **options) }
|
15
|
+
else
|
16
|
+
proc { false }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
10
20
|
def call(nodes, *inputs, **options)
|
11
|
-
nodes.
|
12
|
-
node.call(*inputs, **options) if node.match?(*inputs, **options)
|
21
|
+
nodes.reduce([]) do |outputs, node|
|
22
|
+
output = (node.call(*inputs, **options) if node.match?(*inputs, **options))
|
23
|
+
outputs << output
|
24
|
+
|
25
|
+
if @terminator.call(node, output, *inputs, **options)
|
26
|
+
break outputs
|
27
|
+
else
|
28
|
+
outputs
|
29
|
+
end
|
13
30
|
end
|
14
31
|
end
|
15
32
|
end
|
@@ -7,12 +7,24 @@ module CallableTree
|
|
7
7
|
class Compose
|
8
8
|
include Strategy
|
9
9
|
|
10
|
+
def initialize(terminable: false)
|
11
|
+
self.terminable = terminable
|
12
|
+
@terminator =
|
13
|
+
if terminable
|
14
|
+
proc { |node, output, *inputs, **options| node.terminate?(output, *inputs, **options) }
|
15
|
+
else
|
16
|
+
proc { false }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
10
20
|
def call(nodes, *inputs, **options)
|
11
21
|
head, *tail = inputs
|
12
22
|
nodes.reduce(head) do |input, node|
|
13
|
-
|
14
|
-
|
15
|
-
|
23
|
+
if node.match?(input, *tail, **options)
|
24
|
+
output = node.call(input, *tail, **options)
|
25
|
+
break output if @terminator.call(node, output, input, *tail, **options)
|
26
|
+
|
27
|
+
output
|
16
28
|
else
|
17
29
|
input
|
18
30
|
end
|
@@ -7,13 +7,23 @@ module CallableTree
|
|
7
7
|
class Seek
|
8
8
|
include Strategy
|
9
9
|
|
10
|
+
def initialize(terminable: true)
|
11
|
+
self.terminable = terminable
|
12
|
+
@terminator =
|
13
|
+
if terminable
|
14
|
+
proc { |node, output, *inputs, **options| node.terminate?(output, *inputs, **options) }
|
15
|
+
else
|
16
|
+
proc { false }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
10
20
|
def call(nodes, *inputs, **options)
|
11
21
|
nodes
|
12
22
|
.lazy
|
13
23
|
.select { |node| node.match?(*inputs, **options) }
|
14
24
|
.map do |node|
|
15
25
|
output = node.call(*inputs, **options)
|
16
|
-
terminated =
|
26
|
+
terminated = @terminator.call(node, output, *inputs, **options)
|
17
27
|
[output, terminated]
|
18
28
|
end
|
19
29
|
.select { |_output, terminated| terminated }
|
@@ -13,7 +13,7 @@ module CallableTree
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def ==(other)
|
16
|
-
name == other.name
|
16
|
+
name == other.name && terminable? == other.terminable?
|
17
17
|
end
|
18
18
|
|
19
19
|
def eql?(other)
|
@@ -21,8 +21,16 @@ module CallableTree
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def hash
|
24
|
-
self.class.name.hash
|
24
|
+
[self.class.name, terminable].join('-').hash
|
25
25
|
end
|
26
|
+
|
27
|
+
def terminable?
|
28
|
+
terminable
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_accessor :terminable
|
26
34
|
end
|
27
35
|
end
|
28
36
|
end
|