morpher 0.3.1

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 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: []