reforge 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/ci.yml +76 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.rubocop.yml +13 -0
- data/.ruby-version +1 -0
- data/.tool-versions +1 -0
- data/CHANGELOG.md +0 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +89 -0
- data/INTRODUCTION.md +274 -0
- data/LICENSE.txt +21 -0
- data/README.md +44 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/reforge.rb +17 -0
- data/lib/reforge/configuration.rb +6 -0
- data/lib/reforge/transformation.rb +23 -0
- data/lib/reforge/transformation/dsl.rb +51 -0
- data/lib/reforge/transformation/transform.rb +43 -0
- data/lib/reforge/transformation/transform/factories.rb +61 -0
- data/lib/reforge/transformation/transform/memo.rb +46 -0
- data/lib/reforge/transformation/tree.rb +88 -0
- data/lib/reforge/transformation/tree/aggregate_node.rb +65 -0
- data/lib/reforge/transformation/tree/aggregate_node/array_node.rb +27 -0
- data/lib/reforge/transformation/tree/aggregate_node/factories.rb +32 -0
- data/lib/reforge/transformation/tree/aggregate_node/hash_node.rb +25 -0
- data/lib/reforge/transformation/tree/transform_node.rb +33 -0
- data/lib/reforge/zeitwerk.rb +16 -0
- data/reforge.gemspec +47 -0
- metadata +203 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reforge
|
4
|
+
class Transformation
|
5
|
+
class Tree
|
6
|
+
class AggregateNode
|
7
|
+
include Factories
|
8
|
+
|
9
|
+
attr_reader :implementation
|
10
|
+
|
11
|
+
def initialize(key_type)
|
12
|
+
@path = []
|
13
|
+
@implementation = implementation_from(key_type)
|
14
|
+
@key_type = key_type
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(source)
|
18
|
+
implementation.call(source)
|
19
|
+
end
|
20
|
+
|
21
|
+
def []=(key, node)
|
22
|
+
validate_node!(node)
|
23
|
+
validate_key!(key)
|
24
|
+
validate_no_redefinition!(key)
|
25
|
+
|
26
|
+
implementation.children[key] = node
|
27
|
+
node.update_path(@path + [key])
|
28
|
+
end
|
29
|
+
|
30
|
+
def [](key)
|
31
|
+
validate_key!(key)
|
32
|
+
|
33
|
+
implementation.children[key]
|
34
|
+
end
|
35
|
+
|
36
|
+
def update_path(path)
|
37
|
+
@path = path
|
38
|
+
implementation.update_path(path)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def validate_node!(node)
|
44
|
+
return if node.is_a?(AggregateNode) || node.is_a?(TransformNode)
|
45
|
+
|
46
|
+
raise ArgumentError, "The node must be an AggregateNode or TransformNode"
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate_key!(key)
|
50
|
+
return if key.is_a?(@key_type)
|
51
|
+
|
52
|
+
invalid_path = @path + [key]
|
53
|
+
raise ArgumentError, "Expected #{key.inspect} at node path #{invalid_path} to be of #{@key_type} type"
|
54
|
+
end
|
55
|
+
|
56
|
+
def validate_no_redefinition!(key)
|
57
|
+
return if implementation.children[key].nil?
|
58
|
+
|
59
|
+
invalid_path = @path + [key]
|
60
|
+
raise NodeRedefinitionError, "Node already exists at #{invalid_path}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reforge
|
4
|
+
class Transformation
|
5
|
+
class Tree
|
6
|
+
class AggregateNode
|
7
|
+
class ArrayNode
|
8
|
+
attr_reader :children
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@children = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(source)
|
15
|
+
# TRICKY: holes can be present, e.g. at key 1 after setting children at keys 0 and 2
|
16
|
+
children.map { |child| child&.call(source) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_path(path)
|
20
|
+
# TRICKY: holes can be present, e.g. at key 1 after setting children at keys 0 and 2
|
21
|
+
children.each_with_index { |child, index| child&.update_path(path + [index]) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reforge
|
4
|
+
class Transformation
|
5
|
+
class Tree
|
6
|
+
class AggregateNode
|
7
|
+
module Factories
|
8
|
+
IMPLEMENTATION_FACTORIES = {
|
9
|
+
Integer => -> { ArrayNode.new },
|
10
|
+
String => -> { HashNode.new },
|
11
|
+
Symbol => -> { HashNode.new }
|
12
|
+
}.freeze
|
13
|
+
IMPLEMENTATION_TYPES = IMPLEMENTATION_FACTORIES.keys.freeze
|
14
|
+
|
15
|
+
def implementation_from(key_type)
|
16
|
+
validate_key_type!(key_type)
|
17
|
+
|
18
|
+
IMPLEMENTATION_FACTORIES[key_type].call
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def validate_key_type!(key_type)
|
24
|
+
return if IMPLEMENTATION_TYPES.include?(key_type)
|
25
|
+
|
26
|
+
raise ArgumentError, "No AggregateNode implementation for key_type #{key_type}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reforge
|
4
|
+
class Transformation
|
5
|
+
class Tree
|
6
|
+
class AggregateNode
|
7
|
+
class HashNode
|
8
|
+
attr_reader :children
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@children = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(source)
|
15
|
+
children.transform_values { |child| child.call(source) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def update_path(path)
|
19
|
+
children.each { |key, child| child.update_path(path + [key]) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reforge
|
4
|
+
class Transformation
|
5
|
+
class Tree
|
6
|
+
class TransformNode
|
7
|
+
attr_reader :transform
|
8
|
+
|
9
|
+
def initialize(transform)
|
10
|
+
validate_transform!(transform)
|
11
|
+
|
12
|
+
@transform = transform
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(source)
|
16
|
+
transform.call(source)
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_path(path)
|
20
|
+
@path = path
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def validate_transform!(transform)
|
26
|
+
return if transform.is_a?(Transform)
|
27
|
+
|
28
|
+
raise ArgumentError, "The transform must be a Transform"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zeitwerk"
|
4
|
+
|
5
|
+
# TRICKY: Zeitwerk::Loader.for_gem would work if this were in lib/reforge.rb, but we have initialization here to
|
6
|
+
# keep loader logic consolidated. We need to reconstruct the work the work done by that method, and to do so correctly
|
7
|
+
# from any location we need to establish a path to our root module and its home directory
|
8
|
+
reforge_path = "#{__dir__}/../reforge.rb"
|
9
|
+
reforge_dir = File.dirname(File.realpath(reforge_path))
|
10
|
+
|
11
|
+
loader = Zeitwerk::Loader.new
|
12
|
+
loader.tag = File.basename(reforge_path)
|
13
|
+
loader.inflector = Zeitwerk::Inflector.new
|
14
|
+
loader.inflector.inflect("dsl" => "DSL")
|
15
|
+
loader.push_dir(reforge_dir)
|
16
|
+
loader.setup
|
data/reforge.gemspec
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "reforge"
|
5
|
+
spec.version = "0.1.0"
|
6
|
+
spec.authors = ["Nate Eizenga"]
|
7
|
+
spec.email = ["eizengan@gmail.com"]
|
8
|
+
|
9
|
+
spec.summary = "Simple DSL-driven data transformation for Ruby"
|
10
|
+
spec.homepage = "https://github.com/eizengan/reforge"
|
11
|
+
spec.license = "MIT"
|
12
|
+
|
13
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
14
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
15
|
+
if spec.respond_to?(:metadata)
|
16
|
+
# spec.metadata["allowed_push_host"] = "Set to 'http://mygemserver.com'"
|
17
|
+
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/eizengan/reforge/blob/main/CHANGELOG.md"
|
21
|
+
else
|
22
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
23
|
+
"public gem pushes."
|
24
|
+
end
|
25
|
+
|
26
|
+
# Specify which files should be added to the gem when it is released.
|
27
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
28
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
29
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
30
|
+
end
|
31
|
+
spec.bindir = "exe"
|
32
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
33
|
+
spec.require_paths = ["lib"]
|
34
|
+
|
35
|
+
spec.required_ruby_version = "~> 2.6.6"
|
36
|
+
|
37
|
+
spec.add_dependency "zeitwerk", "~> 2.4"
|
38
|
+
|
39
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
40
|
+
spec.add_development_dependency "pry-byebug", "~> 3.9"
|
41
|
+
spec.add_development_dependency "rake", "~> 12.0"
|
42
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
43
|
+
spec.add_development_dependency "rubocop", "~> 1.0"
|
44
|
+
spec.add_development_dependency "rubocop-performance", "~> 1.8"
|
45
|
+
spec.add_development_dependency "simplecov", "~> 0.17.1" # 0.18 breaks Code Climate. Ref: https://github.com/codeclimate/test-reporter/issues/413
|
46
|
+
spec.add_development_dependency "super_diff", "~> 0.5"
|
47
|
+
end
|
metadata
ADDED
@@ -0,0 +1,203 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: reforge
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nate Eizenga
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-01-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: zeitwerk
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.4'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.4'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry-byebug
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.9'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.9'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '12.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '12.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop-performance
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.8'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.8'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: simplecov
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.17.1
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 0.17.1
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: super_diff
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0.5'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0.5'
|
139
|
+
description:
|
140
|
+
email:
|
141
|
+
- eizengan@gmail.com
|
142
|
+
executables: []
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- ".github/workflows/ci.yml"
|
147
|
+
- ".gitignore"
|
148
|
+
- ".rspec"
|
149
|
+
- ".rubocop.yml"
|
150
|
+
- ".ruby-version"
|
151
|
+
- ".tool-versions"
|
152
|
+
- CHANGELOG.md
|
153
|
+
- CODE_OF_CONDUCT.md
|
154
|
+
- Gemfile
|
155
|
+
- Gemfile.lock
|
156
|
+
- INTRODUCTION.md
|
157
|
+
- LICENSE.txt
|
158
|
+
- README.md
|
159
|
+
- Rakefile
|
160
|
+
- bin/console
|
161
|
+
- bin/setup
|
162
|
+
- lib/reforge.rb
|
163
|
+
- lib/reforge/configuration.rb
|
164
|
+
- lib/reforge/transformation.rb
|
165
|
+
- lib/reforge/transformation/dsl.rb
|
166
|
+
- lib/reforge/transformation/transform.rb
|
167
|
+
- lib/reforge/transformation/transform/factories.rb
|
168
|
+
- lib/reforge/transformation/transform/memo.rb
|
169
|
+
- lib/reforge/transformation/tree.rb
|
170
|
+
- lib/reforge/transformation/tree/aggregate_node.rb
|
171
|
+
- lib/reforge/transformation/tree/aggregate_node/array_node.rb
|
172
|
+
- lib/reforge/transformation/tree/aggregate_node/factories.rb
|
173
|
+
- lib/reforge/transformation/tree/aggregate_node/hash_node.rb
|
174
|
+
- lib/reforge/transformation/tree/transform_node.rb
|
175
|
+
- lib/reforge/zeitwerk.rb
|
176
|
+
- reforge.gemspec
|
177
|
+
homepage: https://github.com/eizengan/reforge
|
178
|
+
licenses:
|
179
|
+
- MIT
|
180
|
+
metadata:
|
181
|
+
homepage_uri: https://github.com/eizengan/reforge
|
182
|
+
source_code_uri: https://github.com/eizengan/reforge
|
183
|
+
changelog_uri: https://github.com/eizengan/reforge/blob/main/CHANGELOG.md
|
184
|
+
post_install_message:
|
185
|
+
rdoc_options: []
|
186
|
+
require_paths:
|
187
|
+
- lib
|
188
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
189
|
+
requirements:
|
190
|
+
- - "~>"
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
version: 2.6.6
|
193
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
194
|
+
requirements:
|
195
|
+
- - ">="
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
requirements: []
|
199
|
+
rubygems_version: 3.0.3
|
200
|
+
signing_key:
|
201
|
+
specification_version: 4
|
202
|
+
summary: Simple DSL-driven data transformation for Ruby
|
203
|
+
test_files: []
|