callable_tree 0.3.3 → 0.3.6

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