hashcraft 1.0.0.pre.alpha.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -2
- data/.rubocop.yml +4 -3
- data/CHANGELOG.md +1 -1
- data/README.md +46 -10
- data/hashcraft.gemspec +2 -2
- data/lib/hashcraft.rb +1 -6
- data/lib/hashcraft/base.rb +49 -20
- data/lib/hashcraft/dsl.rb +48 -5
- data/lib/hashcraft/generic/dictionary.rb +5 -3
- data/lib/hashcraft/generic/registry.rb +2 -6
- data/lib/hashcraft/mutators.rb +45 -0
- data/lib/hashcraft/option.rb +25 -22
- data/lib/hashcraft/transformers.rb +40 -0
- data/lib/hashcraft/version.rb +1 -1
- metadata +10 -18
- data/lib/hashcraft/compiler.rb +0 -80
- data/lib/hashcraft/core_ext/hash.rb +0 -21
- data/lib/hashcraft/mutator_registry.rb +0 -26
- data/lib/hashcraft/mutators/array.rb +0 -25
- data/lib/hashcraft/mutators/hash.rb +0 -25
- data/lib/hashcraft/mutators/property.rb +0 -24
- data/lib/hashcraft/transformer_registry.rb +0 -26
- data/lib/hashcraft/transformers/camel_case.rb +0 -27
- data/lib/hashcraft/transformers/pascal_case.rb +0 -25
- data/lib/hashcraft/transformers/pass_thru.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 38c94cd212c01aa72103257b3d053e3dd9beeb190641fcb82df4ac1896db27ba
|
4
|
+
data.tar.gz: 21de2c24f3a98e759b68e4783b29d9c8e11f45cc3e71b71bd779e0319ba52986
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ab0f421744e572638f75ccf04508471f7df8131f105b8e55ae806b90255340f8b45193f72c94dfb84ee1b6037e836e8d29050c8963417d41484c2f83efe97b5
|
7
|
+
data.tar.gz: 3c2e2f61c4eeea3af9fdc95d493461bd727e9b49134c60a8577b5a7c149ae3a4da1796c05f3db6257afed23b2abd29bf1ef146359dc3d5726185ec73b7c6a221
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.5
|
3
|
+
NewCops: enable
|
4
|
+
|
1
5
|
Layout/LineLength:
|
2
6
|
Max: 100
|
3
7
|
Exclude:
|
@@ -15,9 +19,6 @@ Metrics/BlockLength:
|
|
15
19
|
Metrics/MethodLength:
|
16
20
|
Max: 30
|
17
21
|
|
18
|
-
AllCops:
|
19
|
-
TargetRubyVersion: 2.5
|
20
|
-
|
21
22
|
Metrics/AbcSize:
|
22
23
|
Max: 16
|
23
24
|
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -147,7 +147,7 @@ This means there is an available method called api_url that can be called to set
|
|
147
147
|
* **eager**: always assign a value. When true it will always assign the key a value.
|
148
148
|
* **key**: allows for aliasing keys. If omitted, the key will be the option's method name (api_url as noted above).
|
149
149
|
* **meta**: used to store any arbitrary data that can be accessed with transformers.
|
150
|
-
* **mutator**: defines the type of
|
150
|
+
* **mutator**: defines the *type of update* to be made to the underlying value, defaulting to `property`. When the default, `property`, is used then it will simply assign the passed in value. Some other options are: `hash` and `array`. When hash is used then the passed in value will be merged onto the key's value. When array is used then the passed in value will be pushed onto the key's value. For other types see the `Hashcraft::MutatorRegistry` file.
|
151
151
|
|
152
152
|
### Internationalization Support
|
153
153
|
|
@@ -155,7 +155,7 @@ There is currently no first-class support for internationalization, but you can
|
|
155
155
|
|
156
156
|
### Transformers
|
157
157
|
|
158
|
-
Transformers are optional but come into play when you need any additional/special processing of keys and values. By default, keys and values use the pass-thru transformer
|
158
|
+
Transformers are optional but come into play when you need any additional/special processing of keys and values. By default, keys and values use the pass-thru transformer, `Hashcraft::Transformers::PassThru`, but can be explicitly passed any object that responds to `#transform(value, option)`.
|
159
159
|
|
160
160
|
#### Key Transformer Example
|
161
161
|
|
@@ -171,12 +171,35 @@ Say, for example, we wanted to transform all keys to camel case. We could creat
|
|
171
171
|
end
|
172
172
|
````
|
173
173
|
|
174
|
-
We can then use this when deriving hashes (
|
174
|
+
We can then use this when deriving hashes (building on the Grid example above):
|
175
175
|
|
176
176
|
````ruby
|
177
|
+
class Content < Hashcraft::Base
|
178
|
+
option :property
|
179
|
+
end
|
180
|
+
|
181
|
+
class Column < Hashcraft::Base
|
182
|
+
option :header
|
183
|
+
|
184
|
+
option :content, craft: Content,
|
185
|
+
mutator: :array,
|
186
|
+
key: :contents
|
187
|
+
end
|
188
|
+
|
189
|
+
class Grid < Hashcraft::Base
|
190
|
+
key_transformer CamelCase.new
|
191
|
+
|
192
|
+
option :api_url,
|
193
|
+
:name
|
194
|
+
|
195
|
+
option :column, craft: Column,
|
196
|
+
mutator: :array,
|
197
|
+
key: :columns
|
198
|
+
end
|
199
|
+
|
177
200
|
config = Grid.new do
|
178
201
|
api_url '/patients'
|
179
|
-
end.to_h
|
202
|
+
end.to_h
|
180
203
|
````
|
181
204
|
|
182
205
|
The resulting `config` value will now be:
|
@@ -190,12 +213,23 @@ The resulting `config` value will now be:
|
|
190
213
|
Note that this library ships with some basic transformers like the one mentioned above. If you want to use this then you can simply do this:
|
191
214
|
|
192
215
|
````ruby
|
216
|
+
class Grid < Hashcraft::Base
|
217
|
+
key_transformer :camel_case
|
218
|
+
|
219
|
+
option :api_url,
|
220
|
+
:name
|
221
|
+
|
222
|
+
option :column, craft: Column,
|
223
|
+
mutator: :array,
|
224
|
+
key: :columns
|
225
|
+
end
|
226
|
+
|
193
227
|
config = Grid.new do
|
194
228
|
api_url '/patients'
|
195
|
-
end.to_h
|
229
|
+
end.to_h
|
196
230
|
````
|
197
231
|
|
198
|
-
See Hashcraft::TransformerRegistry for a full list of provided transformers.
|
232
|
+
See the `Hashcraft::TransformerRegistry` file for a full list of provided transformers.
|
199
233
|
|
200
234
|
#### Value Transformer Example
|
201
235
|
|
@@ -213,10 +247,12 @@ class Localizer
|
|
213
247
|
end
|
214
248
|
````
|
215
249
|
|
216
|
-
Building on our Grid example, we could enhance the Column object
|
250
|
+
Building on our Grid example, we could enhance the Column object:
|
217
251
|
|
218
252
|
````ruby
|
219
253
|
class Column < Hashcraft::Base
|
254
|
+
value_transformer Localizer.new
|
255
|
+
|
220
256
|
option :header, meta: { localize: true }
|
221
257
|
|
222
258
|
option :content, craft: Content,
|
@@ -225,7 +261,7 @@ class Column < Hashcraft::Base
|
|
225
261
|
end
|
226
262
|
````
|
227
263
|
|
228
|
-
We can then use the new value transformer
|
264
|
+
We can then use the new value transformer, `Localizer`, when deriving hashes (building on the above Grid and updated Column example):
|
229
265
|
|
230
266
|
````yaml
|
231
267
|
en:
|
@@ -237,10 +273,10 @@ en:
|
|
237
273
|
config = Grid.new do
|
238
274
|
column header: :id
|
239
275
|
column header: :first
|
240
|
-
end.to_h
|
276
|
+
end.to_h
|
241
277
|
````
|
242
278
|
|
243
|
-
Assuming our en.yml looks like the above example and our locale is set to:en then the resulting `config` value will now be:
|
279
|
+
Assuming our en.yml looks like the above example and our locale is set to :en then the resulting `config` value will now be:
|
244
280
|
|
245
281
|
````ruby
|
246
282
|
{
|
data/hashcraft.gemspec
CHANGED
@@ -32,7 +32,7 @@ Gem::Specification.new do |s|
|
|
32
32
|
s.add_development_dependency('pry', '~>0')
|
33
33
|
s.add_development_dependency('rake', '~> 13')
|
34
34
|
s.add_development_dependency('rspec')
|
35
|
-
s.add_development_dependency('rubocop', '~>0.
|
36
|
-
s.add_development_dependency('simplecov', '~>0.
|
35
|
+
s.add_development_dependency('rubocop', '~>0.88.0')
|
36
|
+
s.add_development_dependency('simplecov', '~>0.18.5')
|
37
37
|
s.add_development_dependency('simplecov-console', '~>0.6.0')
|
38
38
|
end
|
data/lib/hashcraft.rb
CHANGED
@@ -10,13 +10,8 @@
|
|
10
10
|
require 'forwardable'
|
11
11
|
require 'singleton'
|
12
12
|
|
13
|
-
# Monkey-patching core libraries
|
14
|
-
require_relative 'hashcraft/core_ext/hash'
|
15
|
-
Hash.include Hashcraft::CoreExt::Hash
|
16
|
-
|
17
13
|
# General tooling
|
18
14
|
require_relative 'hashcraft/generic'
|
19
15
|
|
16
|
+
# Main Entrypoint(s)
|
20
17
|
require_relative 'hashcraft/base'
|
21
|
-
require_relative 'hashcraft/mutator_registry'
|
22
|
-
require_relative 'hashcraft/transformer_registry'
|
data/lib/hashcraft/base.rb
CHANGED
@@ -7,8 +7,8 @@
|
|
7
7
|
# LICENSE file in the root directory of this source tree.
|
8
8
|
#
|
9
9
|
|
10
|
-
require_relative 'compiler'
|
11
10
|
require_relative 'dsl'
|
11
|
+
require_relative 'transformers'
|
12
12
|
|
13
13
|
module Hashcraft
|
14
14
|
# Super-class for craftable objects.
|
@@ -17,13 +17,15 @@ module Hashcraft
|
|
17
17
|
extend Forwardable
|
18
18
|
|
19
19
|
def_delegators :'self.class',
|
20
|
-
:option?,
|
21
20
|
:option_set,
|
22
|
-
:find_option
|
21
|
+
:find_option,
|
22
|
+
:key_transformer_to_use,
|
23
|
+
:value_transformer_to_use
|
23
24
|
|
24
25
|
def initialize(opts = {}, &block)
|
25
|
-
@data =
|
26
|
+
@data = {}
|
26
27
|
|
28
|
+
load_default_data
|
27
29
|
load_opts(opts)
|
28
30
|
|
29
31
|
return unless block_given?
|
@@ -35,36 +37,63 @@ module Hashcraft
|
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
# Main compilation method. Once an object is hydrated, you can call this method to get the
|
41
|
+
# materialized hash.
|
42
|
+
def to_h
|
43
|
+
data.each_with_object({}) do |(key, value), memo|
|
44
|
+
method = value.is_a?(Array) ? :evaluate_values! : :evaluate_value!
|
45
|
+
|
46
|
+
send(method, memo, key, value)
|
47
|
+
end
|
44
48
|
end
|
45
49
|
|
46
50
|
private
|
47
51
|
|
48
52
|
attr_reader :data
|
49
53
|
|
50
|
-
def
|
51
|
-
option_set.
|
54
|
+
def load_default_data
|
55
|
+
option_set.each { |option| default!(option) }
|
52
56
|
end
|
53
57
|
|
54
58
|
def load_opts(opts)
|
55
59
|
(opts || {}).each { |k, v| send(k, v) }
|
56
60
|
end
|
57
61
|
|
58
|
-
def
|
59
|
-
|
62
|
+
def evaluate_values!(data, key, values)
|
63
|
+
data[key] = values.map { |value| value.is_a?(Hashcraft::Base) ? value.to_h : value }
|
64
|
+
|
65
|
+
self
|
60
66
|
end
|
61
67
|
|
62
|
-
def
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
+
def evaluate_value!(data, key, value)
|
69
|
+
data[key] = (value.is_a?(Hashcraft::Base) ? value.to_h : value)
|
70
|
+
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
def default!(option)
|
75
|
+
return self unless option.eager?
|
76
|
+
|
77
|
+
key = hash_key(option)
|
78
|
+
value = Transformers.instance.transform(value_transformer_to_use, option.default.dup, option)
|
79
|
+
|
80
|
+
data[key] = value
|
81
|
+
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
def value!(option, value, &block)
|
86
|
+
key = hash_key(option)
|
87
|
+
value = option.craft_value(value, &block)
|
88
|
+
value = Transformers.instance.transform(value_transformer_to_use, value, option)
|
89
|
+
|
90
|
+
option.value!(data, key, value)
|
91
|
+
|
92
|
+
self
|
93
|
+
end
|
94
|
+
|
95
|
+
def hash_key(option)
|
96
|
+
Transformers.instance.transform(key_transformer_to_use, option.hash_key, option)
|
68
97
|
end
|
69
98
|
end
|
70
99
|
end
|
data/lib/hashcraft/dsl.rb
CHANGED
@@ -14,14 +14,47 @@ module Hashcraft
|
|
14
14
|
# OptionSet instance along with materializing one for its
|
15
15
|
# inheritance chain (child has precedence.)
|
16
16
|
module Dsl
|
17
|
-
|
18
|
-
|
17
|
+
attr_reader :local_key_transformer,
|
18
|
+
:local_value_transformer
|
19
|
+
|
20
|
+
# DSL Method used to declare what the sub-class should use as a transformer for all keys.
|
21
|
+
# It will follow the typical inheritance chain and find the closest
|
22
|
+
# transformer to use (child-first).
|
23
|
+
def key_transformer(name)
|
24
|
+
tap { @local_key_transformer = name }
|
25
|
+
end
|
26
|
+
|
27
|
+
# DSL Method used to declare what the sub-class should use as a transformer for all values.
|
28
|
+
# It will follow the typical inheritance chain and find the closest
|
29
|
+
# transformer to use (child-first).
|
30
|
+
def value_transformer(name)
|
31
|
+
tap { @local_value_transformer = name }
|
32
|
+
end
|
33
|
+
|
34
|
+
def key_transformer_to_use # :nodoc:
|
35
|
+
return @key_transformer_to_use if @key_transformer_to_use
|
36
|
+
|
37
|
+
@key_transformer_to_use =
|
38
|
+
ancestors.select { |a| a < Base }
|
39
|
+
.find(&:local_key_transformer)
|
40
|
+
&.local_key_transformer
|
19
41
|
end
|
20
42
|
|
21
|
-
def
|
43
|
+
def value_transformer_to_use # :nodoc:
|
44
|
+
return @value_transformer_to_use if @value_transformer_to_use
|
45
|
+
|
46
|
+
@value_transformer_to_use =
|
47
|
+
ancestors.select { |a| a < Base }
|
48
|
+
.find(&:local_value_transformer)
|
49
|
+
&.local_value_transformer
|
50
|
+
end
|
51
|
+
|
52
|
+
def find_option(name) # :nodoc:
|
22
53
|
option_set.find(name)
|
23
54
|
end
|
24
55
|
|
56
|
+
# The main class-level DSL method consumed by sub-classes. This is the entry-point for the
|
57
|
+
# declaration of available options.
|
25
58
|
def option(*args)
|
26
59
|
opts = args.last.is_a?(Hash) ? args.pop : {}
|
27
60
|
|
@@ -29,12 +62,22 @@ module Hashcraft
|
|
29
62
|
option = Option.new(key, opts)
|
30
63
|
|
31
64
|
local_option_set.add(option)
|
65
|
+
|
66
|
+
method_name = option.name
|
67
|
+
|
68
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
69
|
+
def #{method_name}(opts = {}, &block)
|
70
|
+
option = find_option('#{method_name}')
|
71
|
+
|
72
|
+
value!(option, opts, &block)
|
73
|
+
end
|
74
|
+
RUBY
|
32
75
|
end
|
33
76
|
|
34
77
|
self
|
35
78
|
end
|
36
79
|
|
37
|
-
def option_set
|
80
|
+
def option_set # :nodoc:
|
38
81
|
@option_set ||=
|
39
82
|
ancestors
|
40
83
|
.reverse
|
@@ -42,7 +85,7 @@ module Hashcraft
|
|
42
85
|
.each_with_object(Generic::Dictionary.new) { |a, memo| memo.merge!(a.local_option_set) }
|
43
86
|
end
|
44
87
|
|
45
|
-
def local_option_set
|
88
|
+
def local_option_set # :nodoc:
|
46
89
|
@local_option_set ||= Generic::Dictionary.new(key: :name)
|
47
90
|
end
|
48
91
|
end
|
@@ -8,7 +8,7 @@
|
|
8
8
|
#
|
9
9
|
|
10
10
|
module Hashcraft
|
11
|
-
module Generic
|
11
|
+
module Generic # :nodoc: all
|
12
12
|
# Dictionary structure defining how we want to organize objects. Basically a type-insensitive
|
13
13
|
# hash where each key is the object's value for the specified key.
|
14
14
|
# All keys are #to_s evaluated in order to achieve the type-insensitivity.
|
@@ -28,8 +28,10 @@ module Hashcraft
|
|
28
28
|
freeze
|
29
29
|
end
|
30
30
|
|
31
|
-
def
|
32
|
-
|
31
|
+
def each(&block)
|
32
|
+
return enum_for(:each) unless block_given?
|
33
|
+
|
34
|
+
values.each(&block)
|
33
35
|
end
|
34
36
|
|
35
37
|
def find(key)
|
@@ -8,7 +8,7 @@
|
|
8
8
|
#
|
9
9
|
|
10
10
|
module Hashcraft
|
11
|
-
module Generic
|
11
|
+
module Generic # :nodoc: all
|
12
12
|
# A general data structure that can register and resolve objects by name.
|
13
13
|
# It also will act as a pass-thru if a non-string or non-symbol is passed through.
|
14
14
|
class Registry
|
@@ -46,11 +46,7 @@ module Hashcraft
|
|
46
46
|
def resolve(value)
|
47
47
|
return value unless lookup?(value)
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
raise ArgumentError, "registration: #{value} not found" unless mutator
|
52
|
-
|
53
|
-
mutator
|
49
|
+
map[value.to_s] || raise(ArgumentError, "registration: #{value} not found")
|
54
50
|
end
|
55
51
|
|
56
52
|
private
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Hashcraft
|
11
|
+
# Singleton that knows how to register and retrieve mutator instances.
|
12
|
+
# Entries can either be instances that respond to value! or procs that accept three arguments.
|
13
|
+
class Mutators < Generic::Registry # :nodoc:
|
14
|
+
FUNCTIONS = {
|
15
|
+
always_false: ->(data, key, _value) { data[key] = false },
|
16
|
+
always_true: ->(data, key, _value) { data[key] = true },
|
17
|
+
array: ->(data, key, value) { (data[key] ||= []) << value },
|
18
|
+
flat_array: lambda do |data, key, value|
|
19
|
+
data[key] ||= []
|
20
|
+
|
21
|
+
if value.is_a?(::Array)
|
22
|
+
data[key] += value
|
23
|
+
else
|
24
|
+
data[key] << value
|
25
|
+
end
|
26
|
+
end,
|
27
|
+
hash: ->(data, key, value) { (data[key] ||= {}).merge!(value || {}) },
|
28
|
+
property: ->(data, key, value) { data[key] = value },
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
private_constant :FUNCTIONS
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
# append on the default case
|
35
|
+
super(FUNCTIONS.merge('': FUNCTIONS[:property]))
|
36
|
+
end
|
37
|
+
|
38
|
+
def value!(name, data, key, value)
|
39
|
+
object = resolve(name)
|
40
|
+
method = object.is_a?(Proc) ? :call : :value!
|
41
|
+
|
42
|
+
object.send(method, data, key, value)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/hashcraft/option.rb
CHANGED
@@ -7,6 +7,8 @@
|
|
7
7
|
# LICENSE file in the root directory of this source tree.
|
8
8
|
#
|
9
9
|
|
10
|
+
require_relative 'mutators'
|
11
|
+
|
10
12
|
module Hashcraft
|
11
13
|
# Defines a method and corresponding attribute for a craftable class.
|
12
14
|
class Option
|
@@ -17,46 +19,47 @@ module Hashcraft
|
|
17
19
|
:mutator,
|
18
20
|
:name
|
19
21
|
|
20
|
-
|
22
|
+
alias eager? eager
|
23
|
+
|
24
|
+
def initialize(name, opts = {}) # :nodoc:
|
21
25
|
raise ArgumentError, 'name is required' if name.to_s.empty?
|
22
26
|
|
23
|
-
@craft
|
24
|
-
@default
|
25
|
-
@eager
|
26
|
-
@internal_meta
|
27
|
-
@key
|
28
|
-
@mutator
|
29
|
-
@name
|
27
|
+
@craft = opts[:craft]
|
28
|
+
@default = opts[:default]
|
29
|
+
@eager = opts[:eager] || false
|
30
|
+
@internal_meta = symbolize_keys(opts[:meta] || {})
|
31
|
+
@key = opts[:key].to_s
|
32
|
+
@mutator = opts[:mutator]
|
33
|
+
@name = name.to_s
|
30
34
|
|
31
35
|
freeze
|
32
36
|
end
|
33
37
|
|
38
|
+
def value!(data, key, value) # :nodoc:
|
39
|
+
Mutators.instance.value!(mutator, data, key, value)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Options are sent into transformers as arguments. Leverage the meta key for an option
|
43
|
+
# to store any additional data that you may need in transformers. This method provides a
|
44
|
+
# quick message-based entry point into inspecting the meta key's value.
|
34
45
|
def meta(key)
|
35
46
|
internal_meta[key.to_s.to_sym]
|
36
47
|
end
|
37
48
|
|
38
|
-
def
|
39
|
-
|
40
|
-
|
41
|
-
data[name] = default.dup
|
42
|
-
|
43
|
-
self
|
49
|
+
def hash_key # :nodoc:
|
50
|
+
key.empty? ? name : key
|
44
51
|
end
|
45
52
|
|
46
|
-
def
|
47
|
-
|
48
|
-
|
49
|
-
mutator.value!(data, name, value)
|
50
|
-
|
51
|
-
self
|
53
|
+
def craft_value(value, &block) # :nodoc:
|
54
|
+
craft ? craft.new(value, &block) : value
|
52
55
|
end
|
53
56
|
|
54
57
|
private
|
55
58
|
|
56
59
|
attr_reader :internal_meta
|
57
60
|
|
58
|
-
def
|
59
|
-
|
61
|
+
def symbolize_keys(hash)
|
62
|
+
hash.transform_keys(&:to_sym)
|
60
63
|
end
|
61
64
|
end
|
62
65
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Hashcraft
|
11
|
+
# Singleton that knows how to register and retrieve transformer instances.
|
12
|
+
# Entries can either be instances that respond to transform or procs that accept two arguments.
|
13
|
+
class Transformers < Generic::Registry # :nodoc:
|
14
|
+
FUNCTIONS = {
|
15
|
+
camel_case: lambda do |value, _option|
|
16
|
+
return value.to_s if value.to_s.empty?
|
17
|
+
|
18
|
+
name = value.to_s.split('_').collect(&:capitalize).join
|
19
|
+
|
20
|
+
name[0, 1].downcase + name[1..-1]
|
21
|
+
end,
|
22
|
+
pascal_case: ->(value, _option) { value.to_s.split('_').collect(&:capitalize).join },
|
23
|
+
pass_thru: ->(value, _option) { value }
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
private_constant :FUNCTIONS
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
# append on the default case
|
30
|
+
super(FUNCTIONS.merge('': FUNCTIONS[:pass_thru]))
|
31
|
+
end
|
32
|
+
|
33
|
+
def transform(name, value, option)
|
34
|
+
object = resolve(name)
|
35
|
+
method = object.is_a?(Proc) ? :call : :transform
|
36
|
+
|
37
|
+
object.send(method, value, option)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/hashcraft/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hashcraft
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew Ruggio
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-07-
|
11
|
+
date: 2020-07-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: guard-rspec
|
@@ -72,28 +72,28 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: 0.
|
75
|
+
version: 0.88.0
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: 0.
|
82
|
+
version: 0.88.0
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: simplecov
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: 0.
|
89
|
+
version: 0.18.5
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: 0.
|
96
|
+
version: 0.18.5
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: simplecov-console
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -132,21 +132,13 @@ files:
|
|
132
132
|
- hashcraft.gemspec
|
133
133
|
- lib/hashcraft.rb
|
134
134
|
- lib/hashcraft/base.rb
|
135
|
-
- lib/hashcraft/compiler.rb
|
136
|
-
- lib/hashcraft/core_ext/hash.rb
|
137
135
|
- lib/hashcraft/dsl.rb
|
138
136
|
- lib/hashcraft/generic.rb
|
139
137
|
- lib/hashcraft/generic/dictionary.rb
|
140
138
|
- lib/hashcraft/generic/registry.rb
|
141
|
-
- lib/hashcraft/
|
142
|
-
- lib/hashcraft/mutators/array.rb
|
143
|
-
- lib/hashcraft/mutators/hash.rb
|
144
|
-
- lib/hashcraft/mutators/property.rb
|
139
|
+
- lib/hashcraft/mutators.rb
|
145
140
|
- lib/hashcraft/option.rb
|
146
|
-
- lib/hashcraft/
|
147
|
-
- lib/hashcraft/transformers/camel_case.rb
|
148
|
-
- lib/hashcraft/transformers/pascal_case.rb
|
149
|
-
- lib/hashcraft/transformers/pass_thru.rb
|
141
|
+
- lib/hashcraft/transformers.rb
|
150
142
|
- lib/hashcraft/version.rb
|
151
143
|
homepage: https://github.com/bluemarblepayroll/hashcraft
|
152
144
|
licenses:
|
@@ -168,9 +160,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
168
160
|
version: '2.5'
|
169
161
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
170
162
|
requirements:
|
171
|
-
- - "
|
163
|
+
- - ">="
|
172
164
|
- !ruby/object:Gem::Version
|
173
|
-
version:
|
165
|
+
version: '0'
|
174
166
|
requirements: []
|
175
167
|
rubygems_version: 3.0.3
|
176
168
|
signing_key:
|
data/lib/hashcraft/compiler.rb
DELETED
@@ -1,80 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
#
|
4
|
-
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
-
#
|
6
|
-
# This source code is licensed under the MIT license found in the
|
7
|
-
# LICENSE file in the root directory of this source tree.
|
8
|
-
#
|
9
|
-
|
10
|
-
module Hashcraft
|
11
|
-
# This class understands how to traverse an option chain and output a hash.
|
12
|
-
class Compiler
|
13
|
-
attr_reader :key_transformer, :option_set, :value_transformer
|
14
|
-
|
15
|
-
def initialize(option_set, key_transformer: nil, value_transformer: nil)
|
16
|
-
raise ArgumentError, 'option_set is required' unless option_set
|
17
|
-
|
18
|
-
@option_set = option_set
|
19
|
-
@key_transformer = TransformerRegistry.resolve(key_transformer)
|
20
|
-
@value_transformer = TransformerRegistry.resolve(value_transformer)
|
21
|
-
|
22
|
-
freeze
|
23
|
-
end
|
24
|
-
|
25
|
-
def evaluate!(data)
|
26
|
-
data.each_with_object({}) do |(key, value), memo|
|
27
|
-
option = option_set.find(key)
|
28
|
-
|
29
|
-
evaluate_single!(memo, option, value)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
attr_reader :data
|
36
|
-
|
37
|
-
def evaluate_single!(data, option, value)
|
38
|
-
key = option.key.empty? ? option.name : option.key
|
39
|
-
transformed_key = key_transformer.transform(key, option)
|
40
|
-
|
41
|
-
method = value.is_a?(Array) ? :evaluate_values! : :evaluate_value!
|
42
|
-
|
43
|
-
send(method, option, data, transformed_key, value)
|
44
|
-
|
45
|
-
self
|
46
|
-
end
|
47
|
-
|
48
|
-
def evaluate_values!(option, data, key, values)
|
49
|
-
data[key] ||= []
|
50
|
-
|
51
|
-
values.each do |value|
|
52
|
-
data[key] <<
|
53
|
-
if value.is_a?(Hashcraft::Base)
|
54
|
-
value.to_h(
|
55
|
-
key_transformer: key_transformer,
|
56
|
-
value_transformer: value_transformer
|
57
|
-
)
|
58
|
-
else
|
59
|
-
value_transformer.transform(value, option)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
self
|
64
|
-
end
|
65
|
-
|
66
|
-
def evaluate_value!(option, data, key, value)
|
67
|
-
data[key] =
|
68
|
-
if value.is_a?(Hashcraft::Base)
|
69
|
-
value.to_h(
|
70
|
-
key_transformer: key_transformer,
|
71
|
-
value_transformer: value_transformer
|
72
|
-
)
|
73
|
-
else
|
74
|
-
value_transformer.transform(value, option)
|
75
|
-
end
|
76
|
-
|
77
|
-
self
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
#
|
4
|
-
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
-
#
|
6
|
-
# This source code is licensed under the MIT license found in the
|
7
|
-
# LICENSE file in the root directory of this source tree.
|
8
|
-
#
|
9
|
-
|
10
|
-
module Hashcraft
|
11
|
-
module CoreExt
|
12
|
-
# Monkey-patches for the core Hash class. These will be manually mixed in separately.
|
13
|
-
module Hash
|
14
|
-
unless method_defined?(:symbolize_keys)
|
15
|
-
def symbolize_keys
|
16
|
-
map { |k, v| [k.to_sym, v] }.to_h
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
#
|
4
|
-
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
-
#
|
6
|
-
# This source code is licensed under the MIT license found in the
|
7
|
-
# LICENSE file in the root directory of this source tree.
|
8
|
-
#
|
9
|
-
|
10
|
-
require_relative 'mutators/array'
|
11
|
-
require_relative 'mutators/hash'
|
12
|
-
require_relative 'mutators/property'
|
13
|
-
|
14
|
-
module Hashcraft
|
15
|
-
# Singleton that knows how to register and retrieve mutator instances.
|
16
|
-
class MutatorRegistry < Generic::Registry
|
17
|
-
def initialize
|
18
|
-
super(
|
19
|
-
'array' => Mutators::Array.instance,
|
20
|
-
'hash' => Mutators::Hash.instance,
|
21
|
-
'property' => Mutators::Property.instance,
|
22
|
-
'' => Mutators::Property.instance
|
23
|
-
)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
#
|
4
|
-
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
-
#
|
6
|
-
# This source code is licensed under the MIT license found in the
|
7
|
-
# LICENSE file in the root directory of this source tree.
|
8
|
-
#
|
9
|
-
|
10
|
-
module Hashcraft
|
11
|
-
module Mutators
|
12
|
-
# When a hash's key is an arry then this mutator can be used to push a new value on the
|
13
|
-
# respective array.
|
14
|
-
class Array
|
15
|
-
include Singleton
|
16
|
-
|
17
|
-
def value!(data, key, value)
|
18
|
-
data[key] ||= []
|
19
|
-
data[key] << value
|
20
|
-
|
21
|
-
self
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
#
|
4
|
-
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
-
#
|
6
|
-
# This source code is licensed under the MIT license found in the
|
7
|
-
# LICENSE file in the root directory of this source tree.
|
8
|
-
#
|
9
|
-
|
10
|
-
module Hashcraft
|
11
|
-
module Mutators
|
12
|
-
# When a hash's key a Hash then this mutator can be used to merge a new value on the
|
13
|
-
# respective hash.
|
14
|
-
class Hash
|
15
|
-
include Singleton
|
16
|
-
|
17
|
-
def value!(data, key, value)
|
18
|
-
data[key] ||= {}
|
19
|
-
data[key].merge!(value || {})
|
20
|
-
|
21
|
-
self
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
#
|
4
|
-
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
-
#
|
6
|
-
# This source code is licensed under the MIT license found in the
|
7
|
-
# LICENSE file in the root directory of this source tree.
|
8
|
-
#
|
9
|
-
|
10
|
-
module Hashcraft
|
11
|
-
module Mutators
|
12
|
-
# When a hash key can be simply assigned to (like a property) then this mutator can be used to
|
13
|
-
# simply do the assignment.
|
14
|
-
class Property
|
15
|
-
include Singleton
|
16
|
-
|
17
|
-
def value!(data, key, value)
|
18
|
-
data[key] = value
|
19
|
-
|
20
|
-
self
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
#
|
4
|
-
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
-
#
|
6
|
-
# This source code is licensed under the MIT license found in the
|
7
|
-
# LICENSE file in the root directory of this source tree.
|
8
|
-
#
|
9
|
-
|
10
|
-
require_relative 'transformers/camel_case'
|
11
|
-
require_relative 'transformers/pascal_case'
|
12
|
-
require_relative 'transformers/pass_thru'
|
13
|
-
|
14
|
-
module Hashcraft
|
15
|
-
# Singleton that knows how to register and retrieve transformer instances.
|
16
|
-
class TransformerRegistry < Generic::Registry
|
17
|
-
def initialize
|
18
|
-
super(
|
19
|
-
'camel_case' => Transformers::CamelCase.instance,
|
20
|
-
'pascal_case' => Transformers::PascalCase.instance,
|
21
|
-
'pass_thru' => Transformers::PassThru.instance,
|
22
|
-
'' => Transformers::PassThru.instance
|
23
|
-
)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
#
|
4
|
-
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
-
#
|
6
|
-
# This source code is licensed under the MIT license found in the
|
7
|
-
# LICENSE file in the root directory of this source tree.
|
8
|
-
#
|
9
|
-
|
10
|
-
module Hashcraft
|
11
|
-
module Transformers
|
12
|
-
# Transform snake-cased to camel-cased string. Example:
|
13
|
-
# date_of_birth => dateOfBirth
|
14
|
-
# DATE_OF_BIRTH => dateOfBirth
|
15
|
-
class CamelCase
|
16
|
-
include Singleton
|
17
|
-
|
18
|
-
def transform(value, _option)
|
19
|
-
return '' if value.to_s.empty?
|
20
|
-
|
21
|
-
name = value.to_s.split('_').collect(&:capitalize).join
|
22
|
-
|
23
|
-
name[0, 1].downcase + name[1..-1]
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
#
|
4
|
-
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
-
#
|
6
|
-
# This source code is licensed under the MIT license found in the
|
7
|
-
# LICENSE file in the root directory of this source tree.
|
8
|
-
#
|
9
|
-
|
10
|
-
module Hashcraft
|
11
|
-
module Transformers
|
12
|
-
# Transform snake-cased to pascal-cased string. Example:
|
13
|
-
# date_of_birth => DateOfBirth
|
14
|
-
# DATE_OF_BIRTH => DateOfBirth
|
15
|
-
class PascalCase
|
16
|
-
include Singleton
|
17
|
-
|
18
|
-
def transform(value, _option)
|
19
|
-
return '' if value.to_s.empty?
|
20
|
-
|
21
|
-
value.to_s.split('_').collect(&:capitalize).join
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
#
|
4
|
-
# Copyright (c) 2020-present, Blue Marble Payroll, LLC
|
5
|
-
#
|
6
|
-
# This source code is licensed under the MIT license found in the
|
7
|
-
# LICENSE file in the root directory of this source tree.
|
8
|
-
#
|
9
|
-
|
10
|
-
module Hashcraft
|
11
|
-
module Transformers
|
12
|
-
# Default transformer, simply returns the value passed in.
|
13
|
-
class PassThru
|
14
|
-
include Singleton
|
15
|
-
|
16
|
-
def transform(value, _option)
|
17
|
-
value
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|