callable_tree 0.1.0
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 +7 -0
- data/.github/workflows/build.yml +25 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +35 -0
- data/LICENSE.txt +21 -0
- data/README.md +436 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/callable_tree.gemspec +35 -0
- data/examples/docs/animals.json +6 -0
- data/examples/docs/animals.xml +1 -0
- data/examples/docs/fruits.json +6 -0
- data/examples/docs/fruits.xml +1 -0
- data/examples/example1.rb +101 -0
- data/examples/example2.rb +101 -0
- data/examples/example3.rb +122 -0
- data/examples/example4.rb +158 -0
- data/examples/example5.rb +35 -0
- data/lib/callable_tree.rb +14 -0
- data/lib/callable_tree/node.rb +45 -0
- data/lib/callable_tree/node/branch.rb +35 -0
- data/lib/callable_tree/node/external.rb +49 -0
- data/lib/callable_tree/node/external/verbose.rb +20 -0
- data/lib/callable_tree/node/hooks/call.rb +67 -0
- data/lib/callable_tree/node/internal.rb +91 -0
- data/lib/callable_tree/node/root.rb +14 -0
- data/lib/callable_tree/version.rb +5 -0
- metadata +81 -0
@@ -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
|
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: []
|