callable_tree 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,35 @@
1
+ require 'callable_tree'
2
+
3
+ module Node
4
+ class HooksSample
5
+ include CallableTree::Node::Internal
6
+ prepend CallableTree::Node::Hooks::Call
7
+ end
8
+ end
9
+
10
+ Node::HooksSample.new
11
+ .before_call do |input, **options|
12
+ puts "before_call input: #{input}";
13
+ input + 1
14
+ end
15
+ .append(
16
+ lambda do |input, **options|
17
+ puts "external input: #{input}"
18
+ input * 2
19
+ end
20
+ )
21
+ .around_call do |input, **options, &block|
22
+ puts "around_call input: #{input}"
23
+ output = block.call
24
+ puts "around_call output: #{output}"
25
+ output * input
26
+ end
27
+ .after_call do |output, **options|
28
+ puts "after_call output: #{output}"
29
+ output * 2
30
+ end
31
+ .tap do |tree|
32
+ options = { foo: :bar }
33
+ output = tree.call(1, **options)
34
+ puts "result: #{output}"
35
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ class Error < StandardError; end
5
+ end
6
+
7
+ require 'forwardable'
8
+ require_relative 'callable_tree/version'
9
+ require_relative 'callable_tree/node'
10
+ require_relative 'callable_tree/node/external/verbose'
11
+ require_relative 'callable_tree/node/external'
12
+ require_relative 'callable_tree/node/hooks/call'
13
+ require_relative 'callable_tree/node/internal'
14
+ require_relative 'callable_tree/node/root'
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ module Node
5
+ attr_reader :parent
6
+
7
+ def ancestors
8
+ ::Enumerator.new do |y|
9
+ node = self
10
+ loop do
11
+ y << node
12
+ break unless node = node&.parent
13
+ end
14
+ end
15
+ end
16
+
17
+ def routes
18
+ ancestors.map(&:identity)
19
+ end
20
+
21
+ def identity
22
+ self.class
23
+ end
24
+
25
+ def depth
26
+ parent.nil? ? 0 : parent.depth + 1
27
+ end
28
+
29
+ def match?(_input = nil, **_options)
30
+ true
31
+ end
32
+
33
+ def call(_input = nil, **_options)
34
+ raise ::CallableTree::Error, 'Not implemented'
35
+ end
36
+
37
+ def terminate?(output = nil, **_options)
38
+ !output.nil?
39
+ end
40
+
41
+ private
42
+
43
+ attr_writer :parent
44
+ end
45
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ module Node
5
+ module Branch
6
+ attr_reader :parent
7
+
8
+ def ancestors
9
+ ::Enumerator.new do |y|
10
+ node = self
11
+ while node = node&.parent
12
+ y << node
13
+ end
14
+ end
15
+ end
16
+
17
+ def routes
18
+ ::Enumerator.new do |y|
19
+ y << self.class
20
+ ancestors.each { |node| y << node.class }
21
+ end
22
+ end
23
+
24
+ def depth
25
+ parent.nil? ? 0 : parent.depth + 1
26
+ end
27
+
28
+ private
29
+
30
+ attr_writer :parent
31
+ end
32
+
33
+ private_constant :Branch
34
+ end
35
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ module Node
5
+ module External
6
+ include Node
7
+
8
+ def self.proxify(callable)
9
+ Proxy.new(callable)
10
+ end
11
+
12
+ def self.proxified?(node)
13
+ node.is_a?(Proxy)
14
+ end
15
+
16
+ def self.unproxify(node)
17
+ node.callable
18
+ end
19
+
20
+ def verbosify
21
+ extend Verbose
22
+ self
23
+ end
24
+
25
+ def identity
26
+ if External.proxified?(self)
27
+ External.unproxify(self)
28
+ else
29
+ self
30
+ end
31
+ .class
32
+ end
33
+
34
+ class Proxy
35
+ extend ::Forwardable
36
+ include External
37
+
38
+ def_delegators :@callable, :call
39
+ attr_reader :callable
40
+
41
+ def initialize(callable)
42
+ @callable = callable
43
+ end
44
+ end
45
+
46
+ private_constant :Proxy
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ module Node
5
+ module External
6
+ Output = Struct.new(:value, :options, :routes)
7
+
8
+ module Verbose
9
+ def call(input = nil, **options)
10
+ output = super(input, **options)
11
+ routes = self.routes
12
+
13
+ Output.new(output, options, routes)
14
+ end
15
+ end
16
+
17
+ private_constant :Verbose
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ module Node
5
+ module Hooks
6
+ module Call
7
+ def self.included(_subclass)
8
+ raise ::CallableTree::Error, "#{self} must be prepended"
9
+ end
10
+
11
+ def before_call(&block)
12
+ before_callbacks << block
13
+ self
14
+ end
15
+
16
+ def around_call(&block)
17
+ around_callbacks << block
18
+ self
19
+ end
20
+
21
+ def after_call(&block)
22
+ after_callbacks << block
23
+ self
24
+ end
25
+
26
+ def call(input = nil, **options)
27
+ input = before_callbacks.reduce(input) do |input, callable|
28
+ callable.call(input, self, **options)
29
+ end
30
+
31
+ output = super(input, **options)
32
+
33
+ output = around_callbacks.reduce(output) do |output, callable|
34
+ callable.call(input, self, **options) { output }
35
+ end
36
+
37
+ after_callbacks.reduce(output) do |output, callable|
38
+ callable.call(output, self, **options)
39
+ end
40
+ end
41
+
42
+ def before_callbacks
43
+ @before_callbacks ||= []
44
+ end
45
+
46
+ def around_callbacks
47
+ @around_callbacks ||= []
48
+ end
49
+
50
+ def after_callbacks
51
+ @after_callbacks ||= []
52
+ end
53
+
54
+ private
55
+
56
+ attr_writer :before_callbacks, :around_callbacks, :after_callbacks
57
+
58
+ def initialize_copy(_node)
59
+ super
60
+ self.before_callbacks = before_callbacks.map(&:itself)
61
+ self.around_callbacks = around_callbacks.map(&:itself)
62
+ self.after_callbacks = after_callbacks.map(&:itself)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ module Node
5
+ module Internal
6
+ include Node
7
+
8
+ def children
9
+ @children ||= []
10
+ end
11
+
12
+ def <<(callable)
13
+ children <<
14
+ if callable.is_a?(Node)
15
+ callable.clone
16
+ else
17
+ External.proxify(callable)
18
+ end
19
+ .tap { |node| node.send(:parent=, self) }
20
+
21
+ self
22
+ end
23
+
24
+ def append(*callables)
25
+ callables.each { |callable| self.<<(callable) }
26
+ self
27
+ end
28
+
29
+ def match?(_input = nil, **_options)
30
+ !children.empty?
31
+ end
32
+
33
+ def call(input = nil, **options)
34
+ children
35
+ .lazy
36
+ .map { |node| Input.new(input, options, node) }
37
+ .select { |input| input.valid? }
38
+ .map { |input| input.call }
39
+ .select { |output| output.valid? }
40
+ .map { |output| output.call }
41
+ .first
42
+ end
43
+
44
+ class Input < BasicObject
45
+ def initialize(value, options, node)
46
+ @value = value
47
+ @options = options
48
+ @node = node
49
+ end
50
+
51
+ def valid?
52
+ @node.match?(@value, **@options)
53
+ end
54
+
55
+ def call
56
+ value = @node.call(@value, **@options)
57
+ Output.new(value, @options, @node)
58
+ end
59
+ end
60
+
61
+ class Output < BasicObject
62
+ def initialize(value, options, node)
63
+ @value = value
64
+ @options = options
65
+ @node = node
66
+ end
67
+
68
+ def valid?
69
+ @node.terminate?(@value, **@options)
70
+ end
71
+
72
+ def call
73
+ @value
74
+ end
75
+ end
76
+
77
+ private_constant :Input, :Output
78
+
79
+ private
80
+
81
+ attr_writer :children
82
+
83
+ def initialize_copy(_node)
84
+ super
85
+ self.children = children.map do |node|
86
+ node.clone.tap { |new_node| new_node.send(:parent=, self) }
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ module Node
5
+ class Root
6
+ include Internal
7
+ prepend Hooks::Call
8
+
9
+ def self.inherited(subclass)
10
+ raise ::CallableTree::Error, "#{subclass} cannot inherit #{self}"
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CallableTree
4
+ VERSION = '0.1.0'
5
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: callable_tree
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - jsmmr
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-05-19 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Builds a tree by linking callable nodes. The nodes that match the calling
14
+ condition are called in a chain from the root node to the leaf node. This is like
15
+ nested case statements.
16
+ email:
17
+ - jsmmr@icloud.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - ".github/workflows/build.yml"
23
+ - ".gitignore"
24
+ - ".rspec"
25
+ - ".ruby-version"
26
+ - CHANGELOG.md
27
+ - Gemfile
28
+ - Gemfile.lock
29
+ - LICENSE.txt
30
+ - README.md
31
+ - Rakefile
32
+ - bin/console
33
+ - bin/setup
34
+ - callable_tree.gemspec
35
+ - examples/docs/animals.json
36
+ - examples/docs/animals.xml
37
+ - examples/docs/fruits.json
38
+ - examples/docs/fruits.xml
39
+ - examples/example1.rb
40
+ - examples/example2.rb
41
+ - examples/example3.rb
42
+ - examples/example4.rb
43
+ - examples/example5.rb
44
+ - lib/callable_tree.rb
45
+ - lib/callable_tree/node.rb
46
+ - lib/callable_tree/node/branch.rb
47
+ - lib/callable_tree/node/external.rb
48
+ - lib/callable_tree/node/external/verbose.rb
49
+ - lib/callable_tree/node/hooks/call.rb
50
+ - lib/callable_tree/node/internal.rb
51
+ - lib/callable_tree/node/root.rb
52
+ - lib/callable_tree/version.rb
53
+ homepage: https://github.com/jsmmr/ruby_callable_tree
54
+ licenses:
55
+ - MIT
56
+ metadata:
57
+ homepage_uri: https://github.com/jsmmr/ruby_callable_tree
58
+ source_code_uri: https://github.com/jsmmr/ruby_callable_tree
59
+ changelog_uri: https://github.com/jsmmr/ruby_callable_tree/CHANGELOG.md
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 2.4.0
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 3.2.16
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: Builds a tree by linking callable nodes. The nodes that match the calling
79
+ condition are called in a chain from the root node to the leaf node. This is like
80
+ nested case statements.
81
+ test_files: []