morpher 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e0eec4915e34accded2656d830113f0ae9af9ffb77d5aa09b76886886464eb02
4
+ data.tar.gz: cd0255f306246e12d521b749c1015b0c3f569df1be1ecd633aa5862524dfbcbc
5
+ SHA512:
6
+ metadata.gz: 79dcc3f54caaa111991c5e2423319ed7a3f80a46c36fdb56331b0d26169c5050bb288b9ce315c9a5dcc9fcc364f6fe65509ac55c46a6cda596aee35301c43d8a
7
+ data.tar.gz: 2c2b9d6c716ff971baebc94825d13e20ed986d35413e40fa817e67f33a38fe86fca894e20d30d16ff5f238f81bac306bb1572ead8d3e325e7905e1590ffb6fe3
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2021 Markus Schirp
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/lib/morpher.rb ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'abstract_type'
4
+ require 'adamantium'
5
+ require 'anima'
6
+ require 'concord'
7
+ require 'mprelude'
8
+
9
+ module Morpher
10
+ Either = MPrelude::Either
11
+ end
12
+
13
+ require 'morpher/newtype'
14
+ require 'morpher/record'
15
+ require 'morpher/transform'
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Morpher
4
+ # Generator for primitive wrappers
5
+ class Newtype < Module
6
+ include Concord.new(:transform)
7
+
8
+ # rubocop:disable Metrics/MethodLength
9
+ def included(host)
10
+ transform = transform()
11
+
12
+ host.class_eval do
13
+ include Adamantium::Flat, Concord::Public.new(:value)
14
+
15
+ const_set(
16
+ :TRANSFORM,
17
+ Transform::Sequence.new(
18
+ [
19
+ transform,
20
+ Transform::Success.new(public_method(:new))
21
+ ]
22
+ )
23
+ )
24
+ end
25
+ end
26
+ # rubocop:enable Metrics/MethodLength
27
+ end
28
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Morpher
4
+ # Generator for struct a-like wrappers
5
+ class Record < Module
6
+ include Concord.new(:attributes)
7
+
8
+ # rubocop:disable Metrics/MethodLength
9
+ def included(host)
10
+ attributes = attributes()
11
+ keys_transform = keys_transform()
12
+
13
+ host.class_eval do
14
+ include Adamantium::Flat, Anima.new(*attributes.keys)
15
+
16
+ const_set(
17
+ :TRANSFORM,
18
+ Transform::Sequence.new(
19
+ [
20
+ Transform::Primitive.new(Hash),
21
+ Transform::Hash::Symbolize.new,
22
+ Transform::Hash.new(required: keys_transform, optional: []),
23
+ Transform::Success.new(public_method(:new))
24
+ ]
25
+ )
26
+ )
27
+ end
28
+ end
29
+ # rubocop:enable Metrics/MethodLength
30
+
31
+ private
32
+
33
+ def keys_transform
34
+ attributes.map do |name, transform|
35
+ Transform::Hash::Key.new(name, transform)
36
+ end
37
+ end
38
+ end # Record
39
+ end # Morpher
@@ -0,0 +1,471 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Morpher
4
+ # Composable transform declaration and execution
5
+ class Transform
6
+ include AbstractType
7
+ include Adamantium
8
+
9
+ # Default slug
10
+ #
11
+ # @return [String]
12
+ def slug
13
+ self.class.to_s
14
+ end
15
+
16
+ # Apply transformation to input
17
+ #
18
+ # @param [Object] input
19
+ #
20
+ # @return [Either<Error, Object>]
21
+ abstract_method :call
22
+
23
+ # Deep error data structure
24
+ class Error
25
+ include Anima.new(
26
+ :cause,
27
+ :input,
28
+ :message,
29
+ :transform
30
+ )
31
+ include Adamantium
32
+
33
+ COMPACT = '%<path>s: %<message>s'
34
+
35
+ private_constant(*constants(false))
36
+
37
+ # Compact error message
38
+ #
39
+ # @return [String]
40
+ def compact_message
41
+ COMPACT % { path: path, message: trace.last.message }
42
+ end
43
+ memoize :compact_message
44
+
45
+ # Error path trace
46
+ #
47
+ # @return [Array<Error>]
48
+ def trace
49
+ [self, *cause&.trace]
50
+ end
51
+ memoize :trace
52
+
53
+ private
54
+
55
+ def path
56
+ trace.map { |error| error.transform.slug }.reject(&:empty?).join('/')
57
+ end
58
+ end # Error
59
+
60
+ # Wrapper adding a name to a transformation
61
+ class Named < self
62
+ include Concord.new(:name, :transformer)
63
+
64
+ # Apply transformation to input
65
+ #
66
+ # @return [Either<Error, Object>]
67
+ def call(input)
68
+ transformer.call(input).lmap(&method(:wrap_error))
69
+ end
70
+
71
+ # Named slug
72
+ #
73
+ # @return [String]
74
+ def slug
75
+ name
76
+ end
77
+ end # Named
78
+
79
+ # Transform based on a (captured) block with added name
80
+ class Block < self
81
+ include Anima.new(:block, :name)
82
+
83
+ def self.capture(name, &block)
84
+ new(block: block, name: name)
85
+ end
86
+
87
+ def call(input)
88
+ block
89
+ .call(input)
90
+ .lmap do |message|
91
+ Error.new(
92
+ cause: nil,
93
+ input: input,
94
+ message: message,
95
+ transform: self
96
+ )
97
+ end
98
+ end
99
+
100
+ def slug
101
+ name
102
+ end
103
+ end
104
+
105
+ private
106
+
107
+ def error(input:, cause: nil, message: nil)
108
+ Error.new(
109
+ cause: cause,
110
+ input: input,
111
+ message: message,
112
+ transform: self
113
+ )
114
+ end
115
+
116
+ def lift_error(error)
117
+ error.with(transform: self)
118
+ end
119
+
120
+ def wrap_error(error)
121
+ error(cause: error, input: error.input)
122
+ end
123
+
124
+ def failure(value)
125
+ Either::Left.new(value)
126
+ end
127
+
128
+ def success(value)
129
+ Either::Right.new(value)
130
+ end
131
+
132
+ # Index attached to a transform
133
+ class Index < self
134
+ include Anima.new(:index, :transform)
135
+
136
+ private(*anima.attribute_names) # rubocop:disable Style/AccessModifierDeclarations
137
+
138
+ # Create error at specified index
139
+ #
140
+ # @param [Error] cause
141
+ # @param [Integer] index
142
+ #
143
+ # @return [Error]
144
+ def self.wrap(cause, index)
145
+ Error.new(
146
+ cause: cause,
147
+ input: cause.input,
148
+ message: nil,
149
+ transform: new(index: index, transform: cause.transform)
150
+ )
151
+ end
152
+
153
+ # Apply transformation to input
154
+ #
155
+ # @param [Object] input
156
+ #
157
+ # @return [Either<Error, Object>]
158
+ def call(input)
159
+ transform.call(input).lmap(&method(:wrap_error))
160
+ end
161
+
162
+ # Rendering slug
163
+ #
164
+ # @return [Array<String>]
165
+ def slug
166
+ '%<index>d' % { index: index }
167
+ end
168
+ memoize :slug
169
+ end # Index
170
+
171
+ # Transform guarding a specific primitive
172
+ class Primitive < self
173
+ include Concord.new(:primitive)
174
+
175
+ MESSAGE = 'Expected: %<expected>s but got: %<actual>s'
176
+
177
+ private_constant(*constants(false))
178
+
179
+ # Apply transformation to input
180
+ #
181
+ # @param [Object] input
182
+ #
183
+ # @return [Either<Error, Object>]
184
+ def call(input)
185
+ if input.instance_of?(primitive)
186
+ success(input)
187
+ else
188
+ failure(
189
+ error(
190
+ input: input,
191
+ message: MESSAGE % { actual: input.class, expected: primitive }
192
+ )
193
+ )
194
+ end
195
+ end
196
+
197
+ # Rendering slug
198
+ #
199
+ # @return [String]
200
+ def slug
201
+ primitive.to_s
202
+ end
203
+ memoize :slug
204
+ end # Primitive
205
+
206
+ # Transform guarding boolean primitives
207
+ class Boolean < self
208
+ include Concord.new
209
+
210
+ MESSAGE = 'Expected: boolean but got: %<actual>s'
211
+
212
+ private_constant(*constants(false))
213
+
214
+ # Apply transformation to input
215
+ #
216
+ # @param [Object] input
217
+ #
218
+ # @return [Either<Error, Object>]
219
+ def call(input)
220
+ if input.equal?(true) || input.equal?(false)
221
+ success(input)
222
+ else
223
+ failure(
224
+ error(
225
+ message: MESSAGE % { actual: input.inspect },
226
+ input: input
227
+ )
228
+ )
229
+ end
230
+ end
231
+ end # Boolean
232
+
233
+ # Transform an array via mapping it over transform
234
+ class Array < self
235
+ include Concord.new(:transform)
236
+
237
+ MESSAGE = 'Failed to coerce array at index: %<index>d'
238
+ PRIMITIVE = Primitive.new(::Array)
239
+
240
+ private_constant(*constants(false))
241
+
242
+ # Apply transformation to input
243
+ #
244
+ # @param [Object] input
245
+ #
246
+ # @return [Either<Error, Array<Object>>]
247
+ def call(input)
248
+ PRIMITIVE
249
+ .call(input)
250
+ .lmap(&method(:lift_error))
251
+ .bind(&method(:run))
252
+ end
253
+
254
+ private
255
+
256
+ # rubocop:disable Metrics/MethodLength
257
+ def run(input)
258
+ output = []
259
+
260
+ input.each_with_index do |value, index|
261
+ output << transform.call(value).lmap do |error|
262
+ return failure(
263
+ error(
264
+ cause: Index.wrap(error, index),
265
+ message: MESSAGE % { index: index },
266
+ input: input
267
+ )
268
+ )
269
+ end.from_right
270
+ end
271
+
272
+ success(output)
273
+ end
274
+ # rubocop:enable Metrics/MethodLength
275
+ end # Array
276
+
277
+ # Transform a hash via mapping it over key specific transforms
278
+ class Hash < self
279
+ include Anima.new(:optional, :required)
280
+
281
+ KEY_MESSAGE = 'Missing keys: %<missing>s, Unexpected keys: %<unexpected>s'
282
+ PRIMITIVE = Primitive.new(::Hash)
283
+
284
+ private_constant(*constants(false))
285
+
286
+ # Transform to symbolize array keys
287
+ class Symbolize < Transform
288
+ include Equalizer.new
289
+
290
+ # Apply transformation to input
291
+ #
292
+ # @param [Hash{String => Object}]
293
+ #
294
+ # @return [Hash{Symbol => Object}]
295
+ def call(input)
296
+ unless input.keys.all? { |key| key.instance_of?(String) }
297
+ return failure(error(input: input, message: 'Found non string key in input'))
298
+ end
299
+
300
+ success(input.transform_keys(&:to_sym))
301
+ end
302
+ end # Symbolize
303
+
304
+ # Key specific transformation
305
+ class Key < Transform
306
+ include Concord::Public.new(:value, :transform)
307
+
308
+ # Rendering slug
309
+ #
310
+ # @return [String]
311
+ def slug
312
+ '[%<key>s]' % { key: value.inspect }
313
+ end
314
+ memoize :slug
315
+
316
+ # Apply transformation to input
317
+ #
318
+ # @param [Object]
319
+ #
320
+ # @return [Either<Error, Object>]
321
+ def call(input)
322
+ transform.call(input).lmap do |error|
323
+ error(cause: error, input: input)
324
+ end
325
+ end
326
+ end # Key
327
+
328
+ # Apply transformation to input
329
+ #
330
+ # @param [Object] input
331
+ #
332
+ # @return [Either<Error, Object>]
333
+ def call(input)
334
+ PRIMITIVE
335
+ .call(input)
336
+ .lmap(&method(:lift_error))
337
+ .bind(&method(:reject_keys))
338
+ .bind(&method(:transform))
339
+ end
340
+
341
+ private
342
+
343
+ def transform(input)
344
+ transform_required(input).bind do |required|
345
+ transform_optional(input).fmap(&required.public_method(:merge))
346
+ end
347
+ end
348
+
349
+ def transform_required(input)
350
+ transform_keys(required, input)
351
+ end
352
+
353
+ def transform_optional(input)
354
+ transform_keys(
355
+ optional.select { |key| input.key?(key.value) },
356
+ input
357
+ )
358
+ end
359
+
360
+ # rubocop:disable Metrics/MethodLength
361
+ def transform_keys(keys, input)
362
+ success(
363
+ keys
364
+ .map do |key|
365
+ [
366
+ key.value,
367
+ coerce_key(key, input).from_right do |error|
368
+ return failure(error)
369
+ end
370
+ ]
371
+ end
372
+ .to_h
373
+ )
374
+ end
375
+ # rubocop:enable Metrics/MethodLength
376
+
377
+ def coerce_key(key, input)
378
+ key.call(input.fetch(key.value)).lmap do |error|
379
+ error(input: input, cause: error)
380
+ end
381
+ end
382
+
383
+ # rubocop:disable Metrics/MethodLength
384
+ def reject_keys(input)
385
+ keys = input.keys
386
+ unexpected = keys - allowed_keys
387
+ missing = required_keys - keys
388
+
389
+ if unexpected.empty? && missing.empty?
390
+ success(input)
391
+ else
392
+ failure(
393
+ error(
394
+ input: input,
395
+ message: KEY_MESSAGE % { missing: missing, unexpected: unexpected }
396
+ )
397
+ )
398
+ end
399
+ end
400
+ # rubocop:enable Metrics/MethodLength
401
+
402
+ def allowed_keys
403
+ required_keys + optional.map(&:value)
404
+ end
405
+ memoize :allowed_keys
406
+
407
+ def required_keys
408
+ required.map(&:value)
409
+ end
410
+ memoize :required_keys
411
+ end # Hash
412
+
413
+ # Sequence of transformations
414
+ class Sequence < self
415
+ include Concord.new(:steps)
416
+
417
+ # Apply transformation to input
418
+ #
419
+ # @param [Object]
420
+ #
421
+ # @return [Either<Error, Object>]
422
+ def call(input)
423
+ current = input
424
+
425
+ steps.each_with_index do |step, index|
426
+ current = step.call(current).from_right do |error|
427
+ return failure(error(cause: Index.wrap(error, index), input: input))
428
+ end
429
+ end
430
+
431
+ success(current)
432
+ end
433
+ end # Sequence
434
+
435
+ # Generic exception transformer
436
+ class Exception < self
437
+ include Concord.new(:error_class, :block)
438
+
439
+ # Apply transformation to input
440
+ #
441
+ # @param [Object]
442
+ #
443
+ # @return [Either<Error, Object>]
444
+ def call(input)
445
+ Either
446
+ .wrap_error(error_class) { block.call(input) }
447
+ .lmap { |exception| error(input: input, message: exception.to_s) }
448
+ end
449
+ end # Exception
450
+
451
+ # Transform sucessfully
452
+ class Success < self
453
+ include Concord.new(:block)
454
+
455
+ # Apply transformation to input
456
+ #
457
+ # @param [Object]
458
+ #
459
+ # @return [Either<Error, Object>]
460
+ def call(input)
461
+ success(block.call(input))
462
+ end
463
+ end # Success
464
+
465
+ BOOLEAN = Transform::Boolean.new
466
+ FLOAT = Transform::Primitive.new(Float)
467
+ INTEGER = Transform::Primitive.new(Integer)
468
+ STRING = Transform::Primitive.new(String)
469
+ STRING_ARRAY = Transform::Array.new(STRING)
470
+ end # Transform
471
+ end # Morpher
metadata ADDED
@@ -0,0 +1,258 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: morpher
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.1
5
+ platform: ruby
6
+ authors:
7
+ - Markus Schirp
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-03-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: abstract_type
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.0.7
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.0.7
27
+ - !ruby/object:Gem::Dependency
28
+ name: adamantium
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.2.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.2.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: anima
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.3.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.3.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: ast
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: concord
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.1.5
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.1.5
83
+ - !ruby/object:Gem::Dependency
84
+ name: equalizer
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.0.9
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.0.9
97
+ - !ruby/object:Gem::Dependency
98
+ name: ice_nine
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.11.0
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.11.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: mprelude
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.1.0
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.1.0
125
+ - !ruby/object:Gem::Dependency
126
+ name: procto
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.0.2
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.0.2
139
+ - !ruby/object:Gem::Dependency
140
+ name: mutant
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '0.10'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '0.10'
153
+ - !ruby/object:Gem::Dependency
154
+ name: mutant-rspec
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0.10'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '0.10'
167
+ - !ruby/object:Gem::Dependency
168
+ name: rspec
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '3.10'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '3.10'
181
+ - !ruby/object:Gem::Dependency
182
+ name: rspec-core
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '3.10'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '3.10'
195
+ - !ruby/object:Gem::Dependency
196
+ name: rspec-its
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: 1.3.0
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: 1.3.0
209
+ - !ruby/object:Gem::Dependency
210
+ name: rubocop
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - "~>"
214
+ - !ruby/object:Gem::Version
215
+ version: '1.11'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - "~>"
221
+ - !ruby/object:Gem::Version
222
+ version: '1.11'
223
+ description: Domain Transformation Algebra
224
+ email: mbj@schirp-dso.com
225
+ executables: []
226
+ extensions: []
227
+ extra_rdoc_files:
228
+ - LICENSE
229
+ files:
230
+ - LICENSE
231
+ - lib/morpher.rb
232
+ - lib/morpher/newtype.rb
233
+ - lib/morpher/record.rb
234
+ - lib/morpher/transform.rb
235
+ homepage: https://github.com/mbj/morpher
236
+ licenses:
237
+ - MIT
238
+ metadata: {}
239
+ post_install_message:
240
+ rdoc_options: []
241
+ require_paths:
242
+ - lib
243
+ required_ruby_version: !ruby/object:Gem::Requirement
244
+ requirements:
245
+ - - ">="
246
+ - !ruby/object:Gem::Version
247
+ version: '2.5'
248
+ required_rubygems_version: !ruby/object:Gem::Requirement
249
+ requirements:
250
+ - - ">="
251
+ - !ruby/object:Gem::Version
252
+ version: '0'
253
+ requirements: []
254
+ rubygems_version: 3.1.4
255
+ signing_key:
256
+ specification_version: 4
257
+ summary: Domain Transformation Algebra
258
+ test_files: []