dry-types 0.15.0 → 1.0.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 +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +18 -2
- data/.travis.yml +4 -5
- data/.yardopts +6 -2
- data/CHANGELOG.md +69 -1
- data/Gemfile +3 -0
- data/README.md +2 -1
- data/Rakefile +2 -0
- data/benchmarks/hash_schemas.rb +2 -0
- data/benchmarks/lax_schema.rb +16 -0
- data/benchmarks/profile_invalid_input.rb +15 -0
- data/benchmarks/profile_lax_schema_valid.rb +16 -0
- data/benchmarks/profile_valid_input.rb +15 -0
- data/benchmarks/schema_valid_vs_invalid.rb +21 -0
- data/benchmarks/setup.rb +17 -0
- data/dry-types.gemspec +4 -2
- data/lib/dry-types.rb +2 -0
- data/lib/dry/types.rb +51 -13
- data/lib/dry/types/any.rb +21 -10
- data/lib/dry/types/array.rb +11 -1
- data/lib/dry/types/array/member.rb +65 -13
- data/lib/dry/types/builder.rb +48 -4
- data/lib/dry/types/builder_methods.rb +9 -8
- data/lib/dry/types/coercions.rb +71 -19
- data/lib/dry/types/coercions/json.rb +22 -3
- data/lib/dry/types/coercions/params.rb +98 -30
- data/lib/dry/types/compiler.rb +35 -12
- data/lib/dry/types/constrained.rb +73 -27
- data/lib/dry/types/constrained/coercible.rb +36 -6
- data/lib/dry/types/constraints.rb +15 -1
- data/lib/dry/types/constructor.rb +90 -43
- data/lib/dry/types/constructor/function.rb +201 -0
- data/lib/dry/types/container.rb +5 -0
- data/lib/dry/types/core.rb +7 -5
- data/lib/dry/types/decorator.rb +36 -9
- data/lib/dry/types/default.rb +48 -16
- data/lib/dry/types/enum.rb +30 -16
- data/lib/dry/types/errors.rb +73 -7
- data/lib/dry/types/extensions.rb +2 -0
- data/lib/dry/types/extensions/maybe.rb +43 -4
- data/lib/dry/types/fn_container.rb +5 -0
- data/lib/dry/types/hash.rb +22 -3
- data/lib/dry/types/hash/constructor.rb +13 -0
- data/lib/dry/types/inflector.rb +2 -0
- data/lib/dry/types/json.rb +4 -6
- data/lib/dry/types/{safe.rb → lax.rb} +34 -17
- data/lib/dry/types/map.rb +63 -29
- data/lib/dry/types/meta.rb +51 -0
- data/lib/dry/types/module.rb +7 -2
- data/lib/dry/types/nominal.rb +105 -13
- data/lib/dry/types/options.rb +12 -25
- data/lib/dry/types/params.rb +5 -3
- data/lib/dry/types/printable.rb +5 -1
- data/lib/dry/types/printer.rb +58 -57
- data/lib/dry/types/result.rb +26 -0
- data/lib/dry/types/schema.rb +169 -66
- data/lib/dry/types/schema/key.rb +34 -39
- data/lib/dry/types/spec/types.rb +41 -1
- data/lib/dry/types/sum.rb +70 -21
- data/lib/dry/types/type.rb +49 -0
- data/lib/dry/types/version.rb +3 -1
- metadata +14 -12
data/lib/dry/types/schema/key.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'dry/equalizer'
|
2
4
|
|
3
5
|
module Dry
|
4
6
|
module Types
|
7
|
+
# Schema is a hash with explicit member types defined
|
8
|
+
#
|
9
|
+
# @api public
|
5
10
|
class Schema < Hash
|
6
11
|
# Proxy type for schema keys. Contains only key name and
|
7
12
|
# whether it's required or not. All other calls deletaged
|
@@ -28,12 +33,19 @@ module Dry
|
|
28
33
|
@name = name
|
29
34
|
end
|
30
35
|
|
31
|
-
# @
|
32
|
-
def
|
33
|
-
type.(input, &block)
|
36
|
+
# @api private
|
37
|
+
def call_safe(input, &block)
|
38
|
+
type.call_safe(input, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @api private
|
42
|
+
def call_unsafe(input)
|
43
|
+
type.call_unsafe(input)
|
34
44
|
end
|
35
45
|
|
36
46
|
# @see Dry::Types::Nominal#try
|
47
|
+
#
|
48
|
+
# @api public
|
37
49
|
def try(input, &block)
|
38
50
|
type.try(input, &block)
|
39
51
|
end
|
@@ -41,6 +53,8 @@ module Dry
|
|
41
53
|
# Whether the key is required in schema input
|
42
54
|
#
|
43
55
|
# @return [Boolean]
|
56
|
+
#
|
57
|
+
# @api public
|
44
58
|
def required?
|
45
59
|
options.fetch(:required)
|
46
60
|
end
|
@@ -55,6 +69,8 @@ module Dry
|
|
55
69
|
#
|
56
70
|
# @param [Boolean] required New value
|
57
71
|
# @return [Dry::Types::Schema::Key]
|
72
|
+
#
|
73
|
+
# @api public
|
58
74
|
def required(required = Undefined)
|
59
75
|
if Undefined.equal?(required)
|
60
76
|
options.fetch(:required)
|
@@ -66,36 +82,26 @@ module Dry
|
|
66
82
|
# Make key not required
|
67
83
|
#
|
68
84
|
# @return [Dry::Types::Schema::Key]
|
85
|
+
#
|
86
|
+
# @api public
|
69
87
|
def omittable
|
70
88
|
required(false)
|
71
89
|
end
|
72
90
|
|
73
|
-
#
|
74
|
-
# evaluated/applied when key is absent in schema
|
75
|
-
# input.
|
91
|
+
# Turn key into a lax type. Lax types are not strict hence such keys are not required
|
76
92
|
#
|
77
|
-
# @
|
78
|
-
#
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
# Replace the underlying type
|
84
|
-
# @param [Dry::Types::Type] type
|
85
|
-
# @return [Dry::Types::Schema::Key]
|
86
|
-
def new(type)
|
87
|
-
self.class.new(type, name, options)
|
88
|
-
end
|
89
|
-
|
90
|
-
# @see Dry::Types::Safe
|
91
|
-
# @return [Dry::Types::Schema::Key]
|
92
|
-
def safe
|
93
|
-
new(type.safe)
|
93
|
+
# @return [Lax]
|
94
|
+
#
|
95
|
+
# @api public
|
96
|
+
def lax
|
97
|
+
super.required(false)
|
94
98
|
end
|
95
99
|
|
96
100
|
# Dump to internal AST representation
|
97
101
|
#
|
98
102
|
# @return [Array]
|
103
|
+
#
|
104
|
+
# @api public
|
99
105
|
def to_ast(meta: true)
|
100
106
|
[
|
101
107
|
:key,
|
@@ -107,22 +113,11 @@ module Dry
|
|
107
113
|
]
|
108
114
|
end
|
109
115
|
|
110
|
-
|
111
|
-
|
112
|
-
#
|
113
|
-
|
114
|
-
|
115
|
-
# @return [Hash] metadata associated with type
|
116
|
-
#
|
117
|
-
# @overload meta(data)
|
118
|
-
# @param [Hash] new metadata to merge into existing metadata
|
119
|
-
# @return [Type] new type with added metadata
|
120
|
-
def meta(data = nil)
|
121
|
-
if data.nil?
|
122
|
-
type.meta
|
123
|
-
else
|
124
|
-
new(type.meta(data))
|
125
|
-
end
|
116
|
+
private
|
117
|
+
|
118
|
+
# @api private
|
119
|
+
def decorate?(response)
|
120
|
+
response.is_a?(Type)
|
126
121
|
end
|
127
122
|
end
|
128
123
|
end
|
data/lib/dry/types/spec/types.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
RSpec.shared_examples_for 'Dry::Types::Nominal without primitive' do
|
2
4
|
def be_boolean
|
3
5
|
satisfy { |x| x == true || x == false }
|
@@ -46,6 +48,14 @@ RSpec.shared_examples_for 'Dry::Types::Nominal without primitive' do
|
|
46
48
|
end
|
47
49
|
end
|
48
50
|
end
|
51
|
+
|
52
|
+
describe '#to_proc' do
|
53
|
+
subject(:callable) { type.to_proc }
|
54
|
+
|
55
|
+
it 'converts a type to a proc' do
|
56
|
+
expect(callable).to be_a(Proc)
|
57
|
+
end
|
58
|
+
end
|
49
59
|
end
|
50
60
|
|
51
61
|
RSpec.shared_examples_for 'Dry::Types::Nominal#meta' do
|
@@ -69,7 +79,7 @@ RSpec.shared_examples_for 'Dry::Types::Nominal#meta' do
|
|
69
79
|
expect(type.meta).to be_a ::Hash
|
70
80
|
expect(type.meta).to be_frozen
|
71
81
|
expect(type.meta).not_to have_key :immutable_test
|
72
|
-
derived = type.
|
82
|
+
derived = type.meta(immutable_test: 1)
|
73
83
|
expect(derived.meta).to be_frozen
|
74
84
|
expect(derived.meta).to eql({immutable_test: 1})
|
75
85
|
expect(type.meta).not_to have_key :immutable_test
|
@@ -100,3 +110,33 @@ RSpec.shared_examples_for Dry::Types::Nominal do
|
|
100
110
|
end
|
101
111
|
end
|
102
112
|
end
|
113
|
+
|
114
|
+
RSpec.shared_examples_for 'a constrained type' do |inputs: Object.new|
|
115
|
+
let(:fallback) { Object.new }
|
116
|
+
|
117
|
+
describe '#call' do
|
118
|
+
it 'yields a block on failure' do
|
119
|
+
Array(inputs).each do |input|
|
120
|
+
expect(type.(input) { fallback }).to be(fallback)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'throws an error on invalid input' do
|
125
|
+
Array(inputs).each do |input|
|
126
|
+
expect { type.(input) }.to raise_error(Dry::Types::CoercionError)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
RSpec.shared_examples_for 'a nominal type' do |inputs: Object.new|
|
133
|
+
describe '#call' do
|
134
|
+
it 'always returns the input back' do
|
135
|
+
Array(inputs).each do |input|
|
136
|
+
expect(type.(input) { fail }).to be(input)
|
137
|
+
expect(type.(input)).to be(input)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
data/lib/dry/types/sum.rb
CHANGED
@@ -1,11 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'dry/types/options'
|
4
|
+
require 'dry/types/meta'
|
2
5
|
|
3
6
|
module Dry
|
4
7
|
module Types
|
8
|
+
# Sum type
|
9
|
+
#
|
10
|
+
# @api public
|
5
11
|
class Sum
|
6
12
|
include Type
|
7
13
|
include Builder
|
8
14
|
include Options
|
15
|
+
include Meta
|
9
16
|
include Printable
|
10
17
|
include Dry::Equalizer(:left, :right, :options, :meta, inspect: false)
|
11
18
|
|
@@ -15,6 +22,7 @@ module Dry
|
|
15
22
|
# @return [Type]
|
16
23
|
attr_reader :right
|
17
24
|
|
25
|
+
# @api private
|
18
26
|
class Constrained < Sum
|
19
27
|
# @return [Dry::Logic::Operations::Or]
|
20
28
|
def rule
|
@@ -25,21 +33,13 @@ module Dry
|
|
25
33
|
def constrained?
|
26
34
|
true
|
27
35
|
end
|
28
|
-
|
29
|
-
# @param [Object] input
|
30
|
-
# @return [Object]
|
31
|
-
# @raise [ConstraintError] if given +input+ not passing {#try}
|
32
|
-
def call(input)
|
33
|
-
try(input) { |result|
|
34
|
-
raise ConstraintError.new(result, input)
|
35
|
-
}.input
|
36
|
-
end
|
37
|
-
alias_method :[], :call
|
38
36
|
end
|
39
37
|
|
40
38
|
# @param [Type] left
|
41
39
|
# @param [Type] right
|
42
40
|
# @param [Hash] options
|
41
|
+
#
|
42
|
+
# @api private
|
43
43
|
def initialize(left, right, options = {})
|
44
44
|
super
|
45
45
|
@left, @right = left, right
|
@@ -47,33 +47,55 @@ module Dry
|
|
47
47
|
end
|
48
48
|
|
49
49
|
# @return [String]
|
50
|
+
#
|
51
|
+
# @api public
|
50
52
|
def name
|
51
53
|
[left, right].map(&:name).join(' | ')
|
52
54
|
end
|
53
55
|
|
54
56
|
# @return [false]
|
57
|
+
#
|
58
|
+
# @api public
|
55
59
|
def default?
|
56
60
|
false
|
57
61
|
end
|
58
62
|
|
59
63
|
# @return [false]
|
64
|
+
#
|
65
|
+
# @api public
|
60
66
|
def constrained?
|
61
67
|
false
|
62
68
|
end
|
63
69
|
|
64
70
|
# @return [Boolean]
|
71
|
+
#
|
72
|
+
# @api public
|
65
73
|
def optional?
|
66
74
|
primitive?(nil)
|
67
75
|
end
|
68
76
|
|
69
77
|
# @param [Object] input
|
78
|
+
#
|
70
79
|
# @return [Object]
|
71
|
-
|
72
|
-
|
80
|
+
#
|
81
|
+
# @api private
|
82
|
+
def call_unsafe(input)
|
83
|
+
left.call_safe(input) { right.call_unsafe(input) }
|
73
84
|
end
|
74
|
-
alias_method :[], :call
|
75
85
|
|
76
|
-
|
86
|
+
# @param [Object] input
|
87
|
+
#
|
88
|
+
# @return [Object]
|
89
|
+
#
|
90
|
+
# @api private
|
91
|
+
def call_safe(input, &block)
|
92
|
+
left.call_safe(input) { right.call_safe(input, &block) }
|
93
|
+
end
|
94
|
+
|
95
|
+
# @param [Object] input
|
96
|
+
#
|
97
|
+
# @api public
|
98
|
+
def try(input)
|
77
99
|
left.try(input) do
|
78
100
|
right.try(input) do |failure|
|
79
101
|
if block_given?
|
@@ -85,6 +107,7 @@ module Dry
|
|
85
107
|
end
|
86
108
|
end
|
87
109
|
|
110
|
+
# @api private
|
88
111
|
def success(input)
|
89
112
|
if left.valid?(input)
|
90
113
|
left.success(input)
|
@@ -95,6 +118,7 @@ module Dry
|
|
95
118
|
end
|
96
119
|
end
|
97
120
|
|
121
|
+
# @api private
|
98
122
|
def failure(input, _error = nil)
|
99
123
|
if !left.valid?(input)
|
100
124
|
left.failure(input, left.try(input).error)
|
@@ -104,28 +128,44 @@ module Dry
|
|
104
128
|
end
|
105
129
|
|
106
130
|
# @param [Object] value
|
131
|
+
#
|
107
132
|
# @return [Boolean]
|
133
|
+
#
|
134
|
+
# @api private
|
108
135
|
def primitive?(value)
|
109
136
|
left.primitive?(value) || right.primitive?(value)
|
110
137
|
end
|
111
138
|
|
112
|
-
#
|
113
|
-
#
|
114
|
-
|
115
|
-
|
139
|
+
# Manage metadata to the type. If the type is an optional, #meta delegates
|
140
|
+
# to the right branch
|
141
|
+
#
|
142
|
+
# @see [Meta#meta]
|
143
|
+
#
|
144
|
+
# @api public
|
145
|
+
def meta(data = nil)
|
146
|
+
if data.nil?
|
147
|
+
optional? ? right.meta : super
|
148
|
+
elsif optional?
|
149
|
+
self.class.new(left, right.meta(data), options)
|
150
|
+
else
|
151
|
+
super
|
152
|
+
end
|
116
153
|
end
|
117
|
-
alias_method :===, :valid?
|
118
154
|
|
119
|
-
# @api public
|
120
|
-
#
|
121
155
|
# @see Nominal#to_ast
|
156
|
+
#
|
157
|
+
# @api public
|
122
158
|
def to_ast(meta: true)
|
123
159
|
[:sum, [left.to_ast(meta: meta), right.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
|
124
160
|
end
|
125
161
|
|
126
162
|
# @param [Hash] options
|
163
|
+
#
|
127
164
|
# @return [Constrained,Sum]
|
165
|
+
#
|
128
166
|
# @see Builder#constrained
|
167
|
+
#
|
168
|
+
# @api public
|
129
169
|
def constrained(options)
|
130
170
|
if optional?
|
131
171
|
right.constrained(options).optional
|
@@ -133,6 +173,15 @@ module Dry
|
|
133
173
|
super
|
134
174
|
end
|
135
175
|
end
|
176
|
+
|
177
|
+
# Wrap the type with a proc
|
178
|
+
#
|
179
|
+
# @return [Proc]
|
180
|
+
#
|
181
|
+
# @api public
|
182
|
+
def to_proc
|
183
|
+
proc { |value| self.(value) }
|
184
|
+
end
|
136
185
|
end
|
137
186
|
end
|
138
187
|
end
|
data/lib/dry/types/type.rb
CHANGED
@@ -1,6 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/core/deprecations'
|
4
|
+
|
1
5
|
module Dry
|
2
6
|
module Types
|
7
|
+
# Common Type module denoting an object is a Type
|
8
|
+
#
|
9
|
+
# @api public
|
3
10
|
module Type
|
11
|
+
extend ::Dry::Core::Deprecations[:'dry-types']
|
12
|
+
|
13
|
+
deprecate(:safe, :lax)
|
14
|
+
|
15
|
+
# Whether a value is a valid member of the type
|
16
|
+
#
|
17
|
+
# @return [Boolean]
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
def valid?(input = Undefined)
|
21
|
+
call_safe(input) { return false }
|
22
|
+
true
|
23
|
+
end
|
24
|
+
# Anything can be coerced matches
|
25
|
+
alias_method :===, :valid?
|
26
|
+
|
27
|
+
# Apply type to a value
|
28
|
+
#
|
29
|
+
# @overload call(input = Undefined)
|
30
|
+
# Possibly unsafe coercion attempt. If a value doesn't
|
31
|
+
# match the type, an exception will be raised.
|
32
|
+
#
|
33
|
+
# @param [Object] input
|
34
|
+
# @return [Object]
|
35
|
+
#
|
36
|
+
# @overload call(input = Undefined)
|
37
|
+
# When a block is passed, {#call} will never throw an exception on
|
38
|
+
# failed coercion, instead it will call the block.
|
39
|
+
#
|
40
|
+
# @param [Object] input
|
41
|
+
# @yieldparam [Object] output Partially coerced value
|
42
|
+
# @return [Object]
|
43
|
+
#
|
44
|
+
# @api public
|
45
|
+
def call(input = Undefined, &block)
|
46
|
+
if block_given?
|
47
|
+
call_safe(input, &block)
|
48
|
+
else
|
49
|
+
call_unsafe(input)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
alias_method :[], :call
|
4
53
|
end
|
5
54
|
end
|
6
55
|
end
|
data/lib/dry/types/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-types
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Solnica
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-04-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -102,22 +102,16 @@ dependencies:
|
|
102
102
|
name: dry-logic
|
103
103
|
requirement: !ruby/object:Gem::Requirement
|
104
104
|
requirements:
|
105
|
-
- - ">="
|
106
|
-
- !ruby/object:Gem::Version
|
107
|
-
version: '0.5'
|
108
105
|
- - "~>"
|
109
106
|
- !ruby/object:Gem::Version
|
110
|
-
version: '0
|
107
|
+
version: '1.0'
|
111
108
|
type: :runtime
|
112
109
|
prerelease: false
|
113
110
|
version_requirements: !ruby/object:Gem::Requirement
|
114
111
|
requirements:
|
115
|
-
- - ">="
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '0.5'
|
118
112
|
- - "~>"
|
119
113
|
- !ruby/object:Gem::Version
|
120
|
-
version: '0
|
114
|
+
version: '1.0'
|
121
115
|
- !ruby/object:Gem::Dependency
|
122
116
|
name: bundler
|
123
117
|
requirement: !ruby/object:Gem::Requirement
|
@@ -209,6 +203,12 @@ files:
|
|
209
203
|
- README.md
|
210
204
|
- Rakefile
|
211
205
|
- benchmarks/hash_schemas.rb
|
206
|
+
- benchmarks/lax_schema.rb
|
207
|
+
- benchmarks/profile_invalid_input.rb
|
208
|
+
- benchmarks/profile_lax_schema_valid.rb
|
209
|
+
- benchmarks/profile_valid_input.rb
|
210
|
+
- benchmarks/schema_valid_vs_invalid.rb
|
211
|
+
- benchmarks/setup.rb
|
212
212
|
- dry-types.gemspec
|
213
213
|
- lib/dry-types.rb
|
214
214
|
- lib/dry/types.rb
|
@@ -226,6 +226,7 @@ files:
|
|
226
226
|
- lib/dry/types/constrained/coercible.rb
|
227
227
|
- lib/dry/types/constraints.rb
|
228
228
|
- lib/dry/types/constructor.rb
|
229
|
+
- lib/dry/types/constructor/function.rb
|
229
230
|
- lib/dry/types/container.rb
|
230
231
|
- lib/dry/types/core.rb
|
231
232
|
- lib/dry/types/decorator.rb
|
@@ -239,7 +240,9 @@ files:
|
|
239
240
|
- lib/dry/types/hash/constructor.rb
|
240
241
|
- lib/dry/types/inflector.rb
|
241
242
|
- lib/dry/types/json.rb
|
243
|
+
- lib/dry/types/lax.rb
|
242
244
|
- lib/dry/types/map.rb
|
245
|
+
- lib/dry/types/meta.rb
|
243
246
|
- lib/dry/types/module.rb
|
244
247
|
- lib/dry/types/nominal.rb
|
245
248
|
- lib/dry/types/options.rb
|
@@ -247,7 +250,6 @@ files:
|
|
247
250
|
- lib/dry/types/printable.rb
|
248
251
|
- lib/dry/types/printer.rb
|
249
252
|
- lib/dry/types/result.rb
|
250
|
-
- lib/dry/types/safe.rb
|
251
253
|
- lib/dry/types/schema.rb
|
252
254
|
- lib/dry/types/schema/key.rb
|
253
255
|
- lib/dry/types/spec/types.rb
|
@@ -271,7 +273,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
271
273
|
requirements:
|
272
274
|
- - ">="
|
273
275
|
- !ruby/object:Gem::Version
|
274
|
-
version: 2.
|
276
|
+
version: 2.4.0
|
275
277
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
276
278
|
requirements:
|
277
279
|
- - ">="
|