callable_tree 0.3.3 → 0.3.6

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.
@@ -0,0 +1,126 @@
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 do
21
+ true
22
+ end
23
+ .hookable
24
+ .build
25
+
26
+ XMLParser =
27
+ CallableTree::Node::Internal::Builder
28
+ .new
29
+ .matcher do |input, **_options|
30
+ File.extname(input) == '.xml'
31
+ end
32
+ .caller do |input, **options, &block|
33
+ File.open(input) do |file|
34
+ # The following block call is equivalent to calling `super` in the class style.
35
+ block.call(REXML::Document.new(file), **options)
36
+ end
37
+ end
38
+ .terminator do
39
+ true
40
+ end
41
+ .hookable
42
+ .build
43
+
44
+ def build_json_scraper(type)
45
+ CallableTree::Node::External::Builder
46
+ .new
47
+ .matcher do |input, **_options|
48
+ !!input[type.to_s]
49
+ end
50
+ .caller do |input, **_options|
51
+ input[type.to_s]
52
+ .map { |element| [element['name'], element['emoji']] }
53
+ .to_h
54
+ end
55
+ .hookable
56
+ .build
57
+ end
58
+
59
+ AnimalsJSONScraper = build_json_scraper(:animals)
60
+ FruitsJSONScraper = build_json_scraper(:fruits)
61
+
62
+ def build_xml_scraper(type)
63
+ CallableTree::Node::External::Builder
64
+ .new
65
+ .matcher do |input, **_options|
66
+ !input.get_elements("//#{type}").empty?
67
+ end
68
+ .caller do |input, **_options|
69
+ input
70
+ .get_elements("//#{type}")
71
+ .first
72
+ .map { |element| [element['name'], element['emoji']] }
73
+ .to_h
74
+ end
75
+ .hookable
76
+ .build
77
+ end
78
+
79
+ AnimalsXMLScraper = build_xml_scraper(:animals)
80
+ FruitsXMLScraper = build_xml_scraper(:fruits)
81
+
82
+ loggable = proc do |node|
83
+ indent_size = 2
84
+ blank = ' '
85
+ list_style = '*'
86
+
87
+ node.after_matcher! do |matched, _node_:, **|
88
+ prefix = list_style.rjust(_node_.depth * indent_size - indent_size + list_style.length, blank)
89
+ puts "#{prefix} #{_node_.identity}: [matched: #{matched}]"
90
+ matched
91
+ end
92
+
93
+ if node.is_a?(CallableTree::Node::External)
94
+ input_label = 'Input :'
95
+ output_label = 'Output:'
96
+
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
+
111
+ tree = CallableTree::Node::Root.new.seekable.append(
112
+ JSONParser.new.tap(&loggable).seekable.append(
113
+ AnimalsJSONScraper.new.tap(&loggable).verbosify,
114
+ FruitsJSONScraper.new.tap(&loggable).verbosify
115
+ ),
116
+ XMLParser.new.tap(&loggable).seekable.append(
117
+ AnimalsXMLScraper.new.tap(&loggable).verbosify,
118
+ FruitsXMLScraper.new.tap(&loggable).verbosify
119
+ )
120
+ )
121
+
122
+ Dir.glob("#{__dir__}/../docs/*") do |file|
123
+ options = { foo: :bar }
124
+ pp tree.call(file, **options)
125
+ puts '---'
126
+ end
@@ -5,14 +5,14 @@ require 'callable_tree'
5
5
  module Node
6
6
  class HooksSample
7
7
  include CallableTree::Node::Internal
8
- prepend CallableTree::Node::Hooks::Call
8
+ prepend CallableTree::Node::Hooks::Caller
9
9
  end
10
10
  end
11
11
 
12
12
  Node::HooksSample
13
13
  .new
14
- .before_call do |input, **_options|
15
- puts "before_call input: #{input}"
14
+ .before_caller do |input, **_options|
15
+ puts "before_caller input: #{input}"
16
16
  input + 1
17
17
  end
18
18
  .append(
@@ -22,14 +22,14 @@ Node::HooksSample
22
22
  input * 2
23
23
  end
24
24
  )
25
- .around_call do |input, **_options, &block|
26
- puts "around_call input: #{input}"
25
+ .around_caller do |input, **_options, &block|
26
+ puts "around_caller input: #{input}"
27
27
  output = block.call
28
- puts "around_call output: #{output}"
28
+ puts "around_caller output: #{output}"
29
29
  output * input
30
30
  end
31
- .after_call do |output, **_options|
32
- puts "after_call output: #{output}"
31
+ .after_caller do |output, **_options|
32
+ puts "after_caller output: #{output}"
33
33
  output * 2
34
34
  end
35
35
  .tap do |tree|
@@ -16,16 +16,16 @@ module Node
16
16
  end
17
17
  end
18
18
 
19
- tree = CallableTree::Node::Root.new.append(
20
- Node::LessThan.new(5).append(
19
+ tree = CallableTree::Node::Root.new.broadcastable.append(
20
+ Node::LessThan.new(5).broadcastable.append(
21
21
  ->(input) { input * 2 }, # anonymous external node
22
22
  ->(input) { input + 1 } # anonymous external node
23
- ).broadcast,
24
- Node::LessThan.new(10).append(
23
+ ),
24
+ Node::LessThan.new(10).broadcastable.append(
25
25
  ->(input) { input * 3 }, # anonymous external node
26
26
  ->(input) { input - 1 } # anonymous external node
27
- ).broadcast
28
- ).broadcast
27
+ )
28
+ )
29
29
 
30
30
  (0..10).each do |input|
31
31
  output = tree.call(input)
@@ -16,16 +16,16 @@ module Node
16
16
  end
17
17
  end
18
18
 
19
- tree = CallableTree::Node::Root.new.append(
20
- Node::LessThan.new(5).append(
19
+ tree = CallableTree::Node::Root.new.composable.append(
20
+ Node::LessThan.new(5).composable.append(
21
21
  proc { |input| input * 2 }, # anonymous external node
22
22
  proc { |input| input + 1 } # anonymous external node
23
- ).compose,
24
- Node::LessThan.new(10).append(
23
+ ),
24
+ Node::LessThan.new(10).composable.append(
25
25
  proc { |input| input * 3 }, # anonymous external node
26
26
  proc { |input| input - 1 } # anonymous external node
27
- ).compose
28
- ).compose
27
+ )
28
+ )
29
29
 
30
30
  (0..10).each do |input|
31
31
  output = tree.call(input)
@@ -85,12 +85,12 @@ module Node
85
85
  end
86
86
  end
87
87
 
88
- tree = CallableTree::Node::Root.new.append(
89
- Node::JSON::Parser.new.append(
88
+ tree = CallableTree::Node::Root.new.seekable.append(
89
+ Node::JSON::Parser.new.seekable.append(
90
90
  Node::JSON::Scraper.new(type: :animals),
91
91
  Node::JSON::Scraper.new(type: :fruits)
92
92
  ),
93
- Node::XML::Parser.new.append(
93
+ Node::XML::Parser.new.seekable.append(
94
94
  Node::XML::Scraper.new(type: :animals),
95
95
  Node::XML::Scraper.new(type: :fruits)
96
96
  )
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ module Node
5
+ module Builder
6
+ def matcher(&block)
7
+ @matcher = block
8
+ self
9
+ end
10
+
11
+ def caller(&block)
12
+ @caller = block
13
+ self
14
+ end
15
+
16
+ def terminater(&block)
17
+ warn 'Use CallableTree::Node::Internal::Builder#terminator instead.'
18
+ @terminator = block
19
+ self
20
+ end
21
+
22
+ def terminator(&block)
23
+ @terminator = block
24
+ self
25
+ end
26
+
27
+ def hookable(hookable = true)
28
+ @hookable = hookable
29
+ self
30
+ end
31
+
32
+ def build(node_type:)
33
+ matcher = @matcher
34
+ caller = @caller
35
+ terminator = @terminator
36
+ hookable = @hookable
37
+
38
+ validate(
39
+ matcher: matcher,
40
+ caller: caller,
41
+ terminator: terminator
42
+ )
43
+
44
+ ::Class
45
+ .new do
46
+ include node_type
47
+ if hookable
48
+ prepend Hooks::Matcher
49
+ prepend Hooks::Caller
50
+ end
51
+
52
+ if matcher
53
+ define_method(:match?) do |*inputs, **options|
54
+ matcher.call(*inputs, **options) do |*inputs, **options|
55
+ super(*inputs, **options)
56
+ end
57
+ end
58
+ end
59
+
60
+ if caller
61
+ define_method(:call) do |*inputs, **options|
62
+ caller.call(*inputs, **options) do |*inputs, **options|
63
+ super(*inputs, **options)
64
+ end
65
+ end
66
+ end
67
+
68
+ if terminator
69
+ define_method(:terminate?) do |output, *inputs, **options|
70
+ terminator.call(output, *inputs, **options) do |output, *inputs, **options|
71
+ super(output, *inputs, **options)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def validate(matcher:, caller:, terminator:)
81
+ raise ::CallableTree::Error, 'Not implemented'
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ module Node
5
+ module External
6
+ class Builder
7
+ include Node::Builder
8
+
9
+ class Error < StandardError; end
10
+
11
+ def build
12
+ super(node_type: External)
13
+ end
14
+
15
+ private
16
+
17
+ def validate(matcher:, caller:, terminator:)
18
+ raise Error, 'caller is required' unless caller
19
+ end
20
+ end
21
+ end
22
+ end
23
+ 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
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ module Node
5
+ module Hooks
6
+ module Caller
7
+ def self.included(_subclass)
8
+ raise ::CallableTree::Error, "#{self} must be prepended"
9
+ end
10
+
11
+ def before_call(&block)
12
+ clone.before_call!(&block)
13
+ end
14
+
15
+ def before_call!(&block)
16
+ before_caller_callbacks << block
17
+ self
18
+ end
19
+
20
+ def around_call(&block)
21
+ clone.around_call!(&block)
22
+ end
23
+
24
+ def around_call!(&block)
25
+ around_caller_callbacks << block
26
+ self
27
+ end
28
+
29
+ def after_call(&block)
30
+ clone.after_call!(&block)
31
+ end
32
+
33
+ def after_call!(&block)
34
+ after_caller_callbacks << block
35
+ self
36
+ end
37
+
38
+ alias before_caller before_call
39
+ alias before_caller! before_call!
40
+ alias around_caller around_call
41
+ alias around_caller! around_call!
42
+ alias after_caller after_call
43
+ alias after_caller! after_call!
44
+
45
+ def call(*inputs, **options)
46
+ input_head, *input_tail = inputs
47
+
48
+ input_head = before_caller_callbacks.reduce(input_head) do |input_head, callable|
49
+ callable.call(input_head, *input_tail, **options, _node_: self)
50
+ end
51
+
52
+ output =
53
+ if around_caller_callbacks.empty?
54
+ super(input_head, *input_tail, **options)
55
+ else
56
+ around_caller_callbacks_head, *around_caller_callbacks_tail = around_caller_callbacks
57
+ caller = proc { super(input_head, *input_tail, **options) }
58
+
59
+ output =
60
+ around_caller_callbacks_head
61
+ .call(
62
+ input_head,
63
+ *input_tail,
64
+ **options,
65
+ _node_: self
66
+ ) { caller.call }
67
+
68
+ around_caller_callbacks_tail.reduce(output) do |output, callable|
69
+ callable.call(
70
+ input_head,
71
+ *input_tail,
72
+ **options,
73
+ _node_: self
74
+ ) { output }
75
+ end
76
+ end
77
+
78
+ after_caller_callbacks.reduce(output) do |output, callable|
79
+ callable.call(output, **options, _node_: self)
80
+ end
81
+ end
82
+
83
+ def before_caller_callbacks
84
+ @before_caller_callbacks ||= []
85
+ end
86
+
87
+ def around_caller_callbacks
88
+ @around_caller_callbacks ||= []
89
+ end
90
+
91
+ def after_caller_callbacks
92
+ @after_caller_callbacks ||= []
93
+ end
94
+
95
+ private
96
+
97
+ attr_writer :before_caller_callbacks, :around_caller_callbacks, :after_caller_callbacks
98
+
99
+ def initialize_copy(_node)
100
+ super
101
+ self.before_caller_callbacks = before_caller_callbacks.map(&:itself)
102
+ self.around_caller_callbacks = around_caller_callbacks.map(&:itself)
103
+ self.after_caller_callbacks = after_caller_callbacks.map(&:itself)
104
+ end
105
+ end
106
+
107
+ Call = Caller # backward compatibility
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ module Node
5
+ module Hooks
6
+ module Matcher
7
+ def self.included(_subclass)
8
+ raise ::CallableTree::Error, "#{self} must be prepended"
9
+ end
10
+
11
+ def before_matcher(&block)
12
+ clone.before_matcher!(&block)
13
+ end
14
+
15
+ def before_matcher!(&block)
16
+ before_matcher_callbacks << block
17
+ self
18
+ end
19
+
20
+ def around_matcher(&block)
21
+ clone.around_matcher!(&block)
22
+ end
23
+
24
+ def around_matcher!(&block)
25
+ around_matcher_callbacks << block
26
+ self
27
+ end
28
+
29
+ def after_matcher(&block)
30
+ clone.after_matcher!(&block)
31
+ end
32
+
33
+ def after_matcher!(&block)
34
+ after_matcher_callbacks << block
35
+ self
36
+ end
37
+
38
+ def match?(*inputs, **options)
39
+ input_head, *input_tail = inputs
40
+
41
+ input_head = before_matcher_callbacks.reduce(input_head) do |input_head, callable|
42
+ callable.call(input_head, *input_tail, **options, _node_: self)
43
+ end
44
+
45
+ matched =
46
+ if around_matcher_callbacks.empty?
47
+ super(input_head, *input_tail, **options)
48
+ else
49
+ around_matcher_callbacks_head, *around_matcher_callbacks_tail = around_matcher_callbacks
50
+ matcher = proc { super(input_head, *input_tail, **options) }
51
+
52
+ matched =
53
+ around_matcher_callbacks_head
54
+ .call(
55
+ input_head,
56
+ *input_tail,
57
+ **options,
58
+ _node_: self
59
+ ) { matcher.call }
60
+
61
+ around_matcher_callbacks_tail.reduce(matched) do |matched, callable|
62
+ callable.call(
63
+ input_head,
64
+ *input_tail,
65
+ **options,
66
+ _node_: self
67
+ ) { matched }
68
+ end
69
+ end
70
+
71
+ after_matcher_callbacks.reduce(matched) do |matched, callable|
72
+ callable.call(matched, **options, _node_: self)
73
+ end
74
+ end
75
+
76
+ def before_matcher_callbacks
77
+ @before_matcher_callbacks ||= []
78
+ end
79
+
80
+ def around_matcher_callbacks
81
+ @around_matcher_callbacks ||= []
82
+ end
83
+
84
+ def after_matcher_callbacks
85
+ @after_matcher_callbacks ||= []
86
+ end
87
+
88
+ private
89
+
90
+ attr_writer :before_matcher_callbacks, :around_matcher_callbacks, :after_matcher_callbacks
91
+
92
+ def initialize_copy(_node)
93
+ super
94
+ self.before_matcher_callbacks = before_matcher_callbacks.map(&:itself)
95
+ self.around_matcher_callbacks = around_matcher_callbacks.map(&:itself)
96
+ self.after_matcher_callbacks = after_matcher_callbacks.map(&:itself)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ module Node
5
+ module Internal
6
+ class Builder
7
+ include Node::Builder
8
+
9
+ def build
10
+ super(node_type: Internal)
11
+ end
12
+
13
+ private
14
+
15
+ def validate(matcher:, caller:, terminator:)
16
+ # noop
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -98,6 +98,10 @@ module CallableTree
98
98
  end
99
99
  end
100
100
 
101
+ alias seekable? seek?
102
+ alias seekable seek
103
+ alias seekable! seek!
104
+
101
105
  def broadcast?
102
106
  strategy.is_a?(Strategy::Broadcast)
103
107
  end
@@ -118,6 +122,10 @@ module CallableTree
118
122
  end
119
123
  end
120
124
 
125
+ alias broadcastable? broadcast?
126
+ alias broadcastable broadcast
127
+ alias broadcastable! broadcast!
128
+
121
129
  def compose?
122
130
  strategy.is_a?(Strategy::Compose)
123
131
  end
@@ -138,6 +146,10 @@ module CallableTree
138
146
  end
139
147
  end
140
148
 
149
+ alias composable? compose?
150
+ alias composable compose
151
+ alias composable! compose!
152
+
141
153
  def outline(&block)
142
154
  key = block ? block.call(self) : identity
143
155
  value = child_nodes.reduce({}) { |memo, node| memo.merge!(node.outline(&block)) }
@@ -4,7 +4,8 @@ module CallableTree
4
4
  module Node
5
5
  class Root
6
6
  include Internal
7
- prepend Hooks::Call
7
+ prepend Hooks::Matcher
8
+ prepend Hooks::Caller
8
9
 
9
10
  def self.inherited(subclass)
10
11
  raise ::CallableTree::Error, "#{subclass} cannot inherit #{self}"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CallableTree
4
- VERSION = '0.3.3'
4
+ VERSION = '0.3.6'
5
5
  end
data/lib/callable_tree.rb CHANGED
@@ -7,7 +7,8 @@ end
7
7
  require 'forwardable'
8
8
  require_relative 'callable_tree/version'
9
9
  require_relative 'callable_tree/node'
10
- require_relative 'callable_tree/node/hooks/call'
10
+ require_relative 'callable_tree/node/hooks/matcher'
11
+ require_relative 'callable_tree/node/hooks/caller'
11
12
  require_relative 'callable_tree/node/internal/strategy'
12
13
  require_relative 'callable_tree/node/internal/strategy/broadcast'
13
14
  require_relative 'callable_tree/node/internal/strategy/seek'
@@ -15,4 +16,7 @@ require_relative 'callable_tree/node/internal/strategy/compose'
15
16
  require_relative 'callable_tree/node/external/verbose'
16
17
  require_relative 'callable_tree/node/external'
17
18
  require_relative 'callable_tree/node/internal'
19
+ require_relative 'callable_tree/node/builder'
20
+ require_relative 'callable_tree/node/internal/builder'
21
+ require_relative 'callable_tree/node/external/builder'
18
22
  require_relative 'callable_tree/node/root'