callable_tree 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'callable_tree'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/callable_tree/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'callable_tree'
7
+ spec.version = CallableTree::VERSION
8
+ spec.authors = ['jsmmr']
9
+ spec.email = ['jsmmr@icloud.com']
10
+
11
+ spec.summary = 'Builds a tree by linking callable nodes. The nodes that match the calling condition are called in a chain from the root node to the leaf node. This is like nested case statements.'
12
+ spec.description = 'Builds a tree by linking callable nodes. The nodes that match the calling condition are called in a chain from the root node to the leaf node. This is like nested case statements.'
13
+ spec.homepage = 'https://github.com/jsmmr/ruby_callable_tree'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.4.0')
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = 'https://github.com/jsmmr/ruby_callable_tree'
19
+ spec.metadata['changelog_uri'] = 'https://github.com/jsmmr/ruby_callable_tree/CHANGELOG.md'
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
25
+ end
26
+ spec.bindir = 'exe'
27
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ['lib']
29
+
30
+ # Uncomment to register a new dependency of your gem
31
+ # spec.add_dependency "example-gem", "~> 1.0"
32
+
33
+ # For more information and examples about making a new gem, checkout our
34
+ # guide at: https://bundler.io/guides/creating_gem.html
35
+ end
@@ -0,0 +1,6 @@
1
+ {
2
+ "animals": [
3
+ { "name": "Dog", "emoji": "🐶" },
4
+ { "name": "Cat", "emoji": "🐱" }
5
+ ]
6
+ }
@@ -0,0 +1 @@
1
+ <root><animals><animal name="Dog" emoji="🐶" /><animal name="Cat" emoji="🐱" /></animals></root>
@@ -0,0 +1,6 @@
1
+ {
2
+ "fruits": [
3
+ { "name": "Red Apple", "emoji": "🍎" },
4
+ { "name": "Green Apple", "emoji": "🍏" }
5
+ ]
6
+ }
@@ -0,0 +1 @@
1
+ <root><fruits><fruit name="Red Apple" emoji="🍎" /><fruit name="Green Apple" emoji="🍏" /></fruits></root>
@@ -0,0 +1,101 @@
1
+ require 'callable_tree'
2
+ require 'json'
3
+ require 'rexml/document'
4
+
5
+ module Node
6
+ module JSON
7
+ class Parser
8
+ include CallableTree::Node::Internal
9
+
10
+ def match?(input, **options)
11
+ File.extname(input) == '.json'
12
+ end
13
+
14
+ def call(input, **options)
15
+ File.open(input) do |file|
16
+ json = ::JSON.load(file)
17
+ super(json, **options)
18
+ end
19
+ end
20
+
21
+ def terminate?(_output, **)
22
+ true
23
+ end
24
+ end
25
+
26
+ class Scraper
27
+ include CallableTree::Node::External
28
+
29
+ def initialize(type:)
30
+ @type = type
31
+ end
32
+
33
+ def match?(input, **options)
34
+ !!input[@type.to_s]
35
+ end
36
+
37
+ def call(input, **options)
38
+ input[@type.to_s]
39
+ .map { |element| [element['name'], element['emoji']] }
40
+ .to_h
41
+ end
42
+ end
43
+ end
44
+
45
+ module XML
46
+ class Parser
47
+ include CallableTree::Node::Internal
48
+
49
+ def match?(input, **options)
50
+ File.extname(input) == '.xml'
51
+ end
52
+
53
+ def call(input, **options)
54
+ File.open(input) do |file|
55
+ super(REXML::Document.new(file), **options)
56
+ end
57
+ end
58
+
59
+ def terminate?(_output, **)
60
+ true
61
+ end
62
+ end
63
+
64
+ class Scraper
65
+ include CallableTree::Node::External
66
+
67
+ def initialize(type:)
68
+ @type = type
69
+ end
70
+
71
+ def match?(input, **options)
72
+ !input.get_elements("//#{@type}").empty?
73
+ end
74
+
75
+ def call(input, **options)
76
+ input
77
+ .get_elements("//#{@type}")
78
+ .first
79
+ .map { |element| [element['name'], element['emoji']] }
80
+ .to_h
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ tree = CallableTree::Node::Root.new.append(
87
+ Node::JSON::Parser.new.append(
88
+ Node::JSON::Scraper.new(type: :animals),
89
+ Node::JSON::Scraper.new(type: :fruits)
90
+ ),
91
+ Node::XML::Parser.new.append(
92
+ Node::XML::Scraper.new(type: :animals),
93
+ Node::XML::Scraper.new(type: :fruits)
94
+ )
95
+ )
96
+
97
+ Dir.glob(__dir__ + '/docs/*') do |file|
98
+ options = { foo: :bar }
99
+ puts tree.call(file, **options)
100
+ puts '---'
101
+ end
@@ -0,0 +1,101 @@
1
+ require 'callable_tree'
2
+ require 'json'
3
+ require 'rexml/document'
4
+
5
+ module Node
6
+ module JSON
7
+ class Parser
8
+ include CallableTree::Node::Internal
9
+
10
+ def match?(input, **options)
11
+ File.extname(input) == '.json'
12
+ end
13
+
14
+ def call(input, **options)
15
+ File.open(input) do |file|
16
+ json = ::JSON.load(file)
17
+ super(json, **options)
18
+ end
19
+ end
20
+
21
+ def terminate?(_output, **)
22
+ true
23
+ end
24
+ end
25
+
26
+ class Scraper
27
+ include CallableTree::Node::External
28
+
29
+ def initialize(type:)
30
+ @type = type
31
+ end
32
+
33
+ def match?(input, **options)
34
+ !!input[@type.to_s]
35
+ end
36
+
37
+ def call(input, **options)
38
+ input[@type.to_s]
39
+ .map { |element| [element['name'], element['emoji']] }
40
+ .to_h
41
+ end
42
+ end
43
+ end
44
+
45
+ module XML
46
+ class Parser
47
+ include CallableTree::Node::Internal
48
+
49
+ def match?(input, **options)
50
+ File.extname(input) == '.xml'
51
+ end
52
+
53
+ def call(input, **options)
54
+ File.open(input) do |file|
55
+ super(REXML::Document.new(file), **options)
56
+ end
57
+ end
58
+
59
+ def terminate?(_output, **)
60
+ true
61
+ end
62
+ end
63
+
64
+ class Scraper
65
+ include CallableTree::Node::External
66
+
67
+ def initialize(type:)
68
+ @type = type
69
+ end
70
+
71
+ def match?(input, **options)
72
+ !input.get_elements("//#{@type}").empty?
73
+ end
74
+
75
+ def call(input, **options)
76
+ input
77
+ .get_elements("//#{@type}")
78
+ .first
79
+ .map { |element| [element['name'], element['emoji']] }
80
+ .to_h
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ tree = CallableTree::Node::Root.new.append(
87
+ Node::JSON::Parser.new.append(
88
+ Node::JSON::Scraper.new(type: :animals).verbosify,
89
+ Node::JSON::Scraper.new(type: :fruits).verbosify
90
+ ),
91
+ Node::XML::Parser.new.append(
92
+ Node::XML::Scraper.new(type: :animals).verbosify,
93
+ Node::XML::Scraper.new(type: :fruits).verbosify
94
+ )
95
+ )
96
+
97
+ Dir.glob(__dir__ + '/docs/*') do |file|
98
+ options = { foo: :bar }
99
+ puts tree.call(file, **options)
100
+ puts '---'
101
+ end
@@ -0,0 +1,122 @@
1
+ require 'callable_tree'
2
+ require 'json'
3
+ require 'rexml/document'
4
+
5
+ module Node
6
+ class Identity
7
+ attr_reader :klass, :type
8
+
9
+ def initialize(klass:, type:)
10
+ @klass = klass
11
+ @type = type
12
+ end
13
+
14
+ def to_s
15
+ "#{klass}(#{type})"
16
+ end
17
+ end
18
+
19
+ module JSON
20
+ class Parser
21
+ include CallableTree::Node::Internal
22
+
23
+ def match?(input, **options)
24
+ File.extname(input) == '.json'
25
+ end
26
+
27
+ def call(input, **options)
28
+ File.open(input) do |file|
29
+ json = ::JSON.load(file)
30
+ super(json, **options)
31
+ end
32
+ end
33
+
34
+ def terminate?(_output, **)
35
+ true
36
+ end
37
+ end
38
+
39
+ class Scraper
40
+ include CallableTree::Node::External
41
+
42
+ def initialize(type:)
43
+ @type = type
44
+ end
45
+
46
+ def identity
47
+ Identity.new(klass: super, type: @type)
48
+ end
49
+
50
+ def match?(input, **options)
51
+ !!input[@type.to_s]
52
+ end
53
+
54
+ def call(input, **options)
55
+ input[@type.to_s]
56
+ .map { |element| [element['name'], element['emoji']] }
57
+ .to_h
58
+ end
59
+ end
60
+ end
61
+
62
+ module XML
63
+ class Parser
64
+ include CallableTree::Node::Internal
65
+
66
+ def match?(input, **options)
67
+ File.extname(input) == '.xml'
68
+ end
69
+
70
+ def call(input, **options)
71
+ File.open(input) do |file|
72
+ super(REXML::Document.new(file), **options)
73
+ end
74
+ end
75
+
76
+ def terminate?(_output, **)
77
+ true
78
+ end
79
+ end
80
+
81
+ class Scraper
82
+ include CallableTree::Node::External
83
+
84
+ def initialize(type:)
85
+ @type = type
86
+ end
87
+
88
+ def identity
89
+ Identity.new(klass: super, type: @type)
90
+ end
91
+
92
+ def match?(input, **options)
93
+ !input.get_elements("//#{@type}").empty?
94
+ end
95
+
96
+ def call(input, **options)
97
+ input
98
+ .get_elements("//#{@type}")
99
+ .first
100
+ .map { |element| [element['name'], element['emoji']] }
101
+ .to_h
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ tree = CallableTree::Node::Root.new.append(
108
+ Node::JSON::Parser.new.append(
109
+ Node::JSON::Scraper.new(type: :animals).verbosify,
110
+ Node::JSON::Scraper.new(type: :fruits).verbosify
111
+ ),
112
+ Node::XML::Parser.new.append(
113
+ Node::XML::Scraper.new(type: :animals).verbosify,
114
+ Node::XML::Scraper.new(type: :fruits).verbosify
115
+ )
116
+ )
117
+
118
+ Dir.glob(__dir__ + '/docs/*') do |file|
119
+ options = { foo: :bar }
120
+ puts tree.call(file, **options)
121
+ puts '---'
122
+ end
@@ -0,0 +1,158 @@
1
+ require 'callable_tree'
2
+ require 'json'
3
+ require 'rexml/document'
4
+
5
+ module Node
6
+ module Logging
7
+ INDENT_SIZE = 2
8
+ BLANK = ' '.freeze
9
+
10
+ module Match
11
+ LIST_STYLE = '*'.freeze
12
+
13
+ def match?(_input, **)
14
+ super.tap do |matched|
15
+ prefix = LIST_STYLE.rjust(self.depth * INDENT_SIZE - INDENT_SIZE + LIST_STYLE.length, BLANK)
16
+ puts "#{prefix} #{self.identity}: [matched: #{matched}]"
17
+ end
18
+ end
19
+ end
20
+
21
+ module Call
22
+ INPUT_LABEL = 'Input :'.freeze
23
+ OUTPUT_LABEL = 'Output:'.freeze
24
+
25
+ def call(input, **)
26
+ super.tap do |output|
27
+ input_prefix = INPUT_LABEL.rjust(self.depth * INDENT_SIZE + INPUT_LABEL.length, BLANK)
28
+ puts "#{input_prefix} #{input}"
29
+ output_prefix = OUTPUT_LABEL.rjust(self.depth * INDENT_SIZE + OUTPUT_LABEL.length, BLANK)
30
+ puts "#{output_prefix} #{output}"
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ class Identity
37
+ attr_reader :klass, :type
38
+
39
+ def initialize(klass:, type:)
40
+ @klass = klass
41
+ @type = type
42
+ end
43
+
44
+ def to_s
45
+ "#{klass}(#{type})"
46
+ end
47
+ end
48
+
49
+ module JSON
50
+ class Parser
51
+ include CallableTree::Node::Internal
52
+ prepend Logging::Match
53
+
54
+ def match?(input, **options)
55
+ File.extname(input) == '.json'
56
+ end
57
+
58
+ def call(input, **options)
59
+ File.open(input) do |file|
60
+ json = ::JSON.load(file)
61
+ super(json, **options)
62
+ end
63
+ end
64
+
65
+ def terminate?(_output, **)
66
+ true
67
+ end
68
+ end
69
+
70
+ class Scraper
71
+ include CallableTree::Node::External
72
+ prepend Logging::Match
73
+ prepend Logging::Call
74
+
75
+ def initialize(type:)
76
+ @type = type
77
+ end
78
+
79
+ def identity
80
+ Identity.new(klass: super, type: @type)
81
+ end
82
+
83
+ def match?(input, **options)
84
+ !!input[@type.to_s]
85
+ end
86
+
87
+ def call(input, **options)
88
+ input[@type.to_s]
89
+ .map { |element| [element['name'], element['emoji']] }
90
+ .to_h
91
+ end
92
+ end
93
+ end
94
+
95
+ module XML
96
+ class Parser
97
+ include CallableTree::Node::Internal
98
+ prepend Logging::Match
99
+
100
+ def match?(input, **options)
101
+ File.extname(input) == '.xml'
102
+ end
103
+
104
+ def call(input, **options)
105
+ File.open(input) do |file|
106
+ super(REXML::Document.new(file), **options)
107
+ end
108
+ end
109
+
110
+ def terminate?(_output, **)
111
+ true
112
+ end
113
+ end
114
+
115
+ class Scraper
116
+ include CallableTree::Node::External
117
+ prepend Logging::Match
118
+ prepend Logging::Call
119
+
120
+ def initialize(type:)
121
+ @type = type
122
+ end
123
+
124
+ def identity
125
+ Identity.new(klass: super, type: @type)
126
+ end
127
+
128
+ def match?(input, **options)
129
+ !input.get_elements("//#{@type}").empty?
130
+ end
131
+
132
+ def call(input, **options)
133
+ input
134
+ .get_elements("//#{@type}")
135
+ .first
136
+ .map { |element| [element['name'], element['emoji']] }
137
+ .to_h
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+ tree = CallableTree::Node::Root.new.append(
144
+ Node::JSON::Parser.new.append(
145
+ Node::JSON::Scraper.new(type: :animals).verbosify,
146
+ Node::JSON::Scraper.new(type: :fruits).verbosify
147
+ ),
148
+ Node::XML::Parser.new.append(
149
+ Node::XML::Scraper.new(type: :animals).verbosify,
150
+ Node::XML::Scraper.new(type: :fruits).verbosify
151
+ )
152
+ )
153
+
154
+ Dir.glob(__dir__ + '/docs/*') do |file|
155
+ options = { foo: :bar }
156
+ puts tree.call(file, **options)
157
+ puts '---'
158
+ end