sober_swag 0.5.0 → 0.6.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/.github/config/rubocop_linter_action.yml +0 -1
- data/.gitignore +1 -0
- data/.rubocop.yml +3 -1
- data/README.md +66 -20
- data/bin/console +1 -1
- data/docs/serializers.md +13 -13
- data/example/Gemfile.lock +1 -1
- data/example/app/controllers/people_controller.rb +7 -5
- data/example/app/controllers/posts_controller.rb +8 -0
- data/example/spec/requests/people/index_spec.rb +2 -2
- data/lib/sober_swag/compiler/type.rb +7 -7
- data/lib/sober_swag/output_object/field_syntax.rb +1 -1
- data/lib/sober_swag/parser.rb +4 -1
- data/lib/sober_swag/serializer.rb +1 -1
- data/lib/sober_swag/version.rb +1 -1
- metadata +1 -2
- data/Gemfile.lock +0 -116
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cdf3896754f6acf3bef2df51e947439be3822d3635eb924d6029ab3157b13bd9
|
4
|
+
data.tar.gz: 75846c0b78e4e8dc9fc550af50aeb2bb73f3c1200e8c50af6c07092d2dd8c277
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae7076a9dcabcf2a99b642f967782195d1680ff13b6fd251123abdec15ec2eab18435ec1897fe212a060779b1bdffb8061bcb4b2eb89884713230ca0c70ecc27
|
7
|
+
data.tar.gz: 5f3fb9ee3c31feb8ede072171e5f0f2f5108061d7f09c44e4f4d88d929c001519734fce64b4c261ea4d8e63d9aa1862fdf374cdddd00da2b58d9cf75fc367320
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -3,7 +3,7 @@ Style/FrozenStringLiteralComment:
|
|
3
3
|
Style/BlockDelimiters:
|
4
4
|
EnforcedStyle: braces_for_chaining
|
5
5
|
AllCops:
|
6
|
-
TargetRubyVersion: 2.
|
6
|
+
TargetRubyVersion: 2.6.0
|
7
7
|
Exclude:
|
8
8
|
- 'bin/bundle'
|
9
9
|
- 'example/bin/bundle'
|
@@ -31,6 +31,8 @@ Metrics/AbcSize:
|
|
31
31
|
Style/Documentation:
|
32
32
|
Exclude:
|
33
33
|
- 'example/db/migrate/**/*'
|
34
|
+
Metrics/PerceivedComplexity:
|
35
|
+
Enabled: false
|
34
36
|
Layout/EmptyLinesAroundAttributeAccessor:
|
35
37
|
Enabled: true
|
36
38
|
Layout/SpaceAroundMethodCallOperator:
|
data/README.md
CHANGED
@@ -4,13 +4,11 @@
|
|
4
4
|

|
5
5
|
|
6
6
|
SoberSwag is a combination of [Dry-Types](https://dry-rb.org/gems/dry-types/1.2/) and [Swagger](https://swagger.io/) that makes your Rails APIs more awesome.
|
7
|
-
Other tools generate
|
7
|
+
Other tools generate documentation from a DSL.
|
8
8
|
This generates documentation from *types*, which (conveniently) also lets you get supercharged strong-params-on-steroids.
|
9
9
|
|
10
10
|
An introductory presentation is available [here](https://www.icloud.com/keynote/0bxP3Dn8ETNO0lpsSQSVfEL6Q#SoberSwagPresentation).
|
11
11
|
|
12
|
-
This gem uses pattern matching, and is thus only compatible with Ruby 2.7 or later.
|
13
|
-
|
14
12
|
## Types for a fully-automated API
|
15
13
|
|
16
14
|
SoberSwag lets you type your API using describe blocks.
|
@@ -18,22 +16,22 @@ In any controller that includes `SoberSwag::Controller`, you get access to the s
|
|
18
16
|
This lets you type your API endpoint:
|
19
17
|
|
20
18
|
```ruby
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
31
|
-
path_params { attribute :id, Types::Params::Integer }
|
19
|
+
class PeopleController < ApplicationController
|
20
|
+
include SoberSwag::Controller
|
21
|
+
define :patch, :update, '/people/{id}' do
|
22
|
+
query_params do
|
23
|
+
attribute? :include_extra_info, Types::Params::Bool
|
24
|
+
end
|
25
|
+
request_body do
|
26
|
+
attribute? :name, Types::Params::String
|
27
|
+
attribute? :age, Types::Params::Integer
|
32
28
|
end
|
29
|
+
path_params { attribute :id, Types::Params::Integer }
|
33
30
|
end
|
31
|
+
end
|
34
32
|
```
|
35
33
|
|
36
|
-
We can now
|
34
|
+
We can now use this information to generate swagger documentation, available at the `swagger` action on this controller.
|
37
35
|
More than that, we can use this information *inside* our controller methods:
|
38
36
|
|
39
37
|
```ruby
|
@@ -87,27 +85,75 @@ end
|
|
87
85
|
|
88
86
|
Support for easily typing "render the activerecord errors for me please" is (unfortunately) under development.
|
89
87
|
|
90
|
-
### SoberSwag
|
88
|
+
### SoberSwag Input Objects
|
91
89
|
|
92
90
|
Input parameters (including path, query, and request body) are typed using [dry-struct](https://dry-rb.org/gems/dry-struct/1.0/).
|
93
|
-
You don't have to do them inline
|
91
|
+
You don't have to do them inline. You can define them in another file, like so:
|
94
92
|
|
95
93
|
```ruby
|
96
|
-
User = SoberSwag.
|
94
|
+
User = SoberSwag.input_object do
|
97
95
|
attribute :name, SoberSwag::Types::String
|
98
96
|
# use ? if attributes are not required
|
99
97
|
attribute? :favorite_movie, SoberSwag::Types::String
|
100
98
|
# use .optional if attributes may be null
|
101
|
-
attribute :age, SoberSwag::Types::Params
|
99
|
+
attribute :age, SoberSwag::Types::Params::Integer.optional
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
Then, in your controller, just do:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
class PeopleController < ApplicationController
|
107
|
+
include SoberSwag::Controller
|
108
|
+
|
109
|
+
define :path, :update, '/people/{id}' do
|
110
|
+
request_body(User)
|
111
|
+
path_params { attribute :id, Types::Params::Integer }
|
112
|
+
response(:ok, 'the updated person', PersonOutputObject)
|
113
|
+
end
|
114
|
+
def update
|
115
|
+
# same as above!
|
116
|
+
end
|
102
117
|
end
|
103
118
|
```
|
104
119
|
|
105
120
|
Under the hood, this literally just generates a subclass of `Dry::Struct`.
|
106
121
|
We use the DSL-like method just to make working with Rails' reloading less annoying.
|
107
122
|
|
123
|
+
#### Adding additional documentation
|
124
|
+
|
125
|
+
You can use the `.meta` attribute on a type to add additional documentation.
|
126
|
+
Some keys are considered "well-known" and will be present on the swagger output.
|
127
|
+
For example:
|
128
|
+
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
User = SoberSwag.input_object do
|
132
|
+
attribute? :name, SoberSwag::Types::String.meta(description: <<~MARKDOWN, deprecated: true)
|
133
|
+
The given name of the students, with strings encoded as escaped-ASCII.
|
134
|
+
This is used by an internal Cobol microservice from 1968.
|
135
|
+
Please use unicode_name instead unless you are that microservice.
|
136
|
+
MARKDOWN
|
137
|
+
attribute? :unicode_name, SoberSwag::Types::String
|
138
|
+
end
|
139
|
+
```
|
140
|
+
|
141
|
+
This will output the swagger you expect, with a description and a deprecated flag.
|
142
|
+
|
143
|
+
#### Adding Default Values
|
144
|
+
|
145
|
+
Sometimes it makes sense to specify a default value.
|
146
|
+
Don't worry, we've got you covered:
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
QueryInput = SoberSwag.input_object do
|
150
|
+
attribute :allow_first, SoberSwag::Types::Params::Bool.default(false) # smartly alters type-definition to establish that passing this is not required.
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
108
154
|
## Special Thanks
|
109
155
|
|
110
|
-
This gem is a
|
156
|
+
This gem is a mishmash of ideas from various sources.
|
111
157
|
The biggest thanks is owed to the [dry-rb](https://github.com/dry-rb) project, upon which the typing of SoberSwag is based.
|
112
158
|
On an API design level, much is owed to [blueprinter](https://github.com/procore/blueprinter) for the serializers.
|
113
159
|
The idea of a strongly-typed API came from the Haskell framework [servant](https://www.servant.dev/).
|
data/bin/console
CHANGED
data/docs/serializers.md
CHANGED
@@ -4,11 +4,11 @@ Serializers are a way to transform from one type to another.
|
|
4
4
|
For example, you might want to change an ActiveRecord object to a JSON struct.
|
5
5
|
You might also want to change an internal date-interval into a two-element array of dates, or some custom text format.
|
6
6
|
You can do all of these things with SoberSwag serializers.
|
7
|
-
Furthermore, Serializers document the *type* that they serialize, so you can use it to
|
7
|
+
Furthermore, Serializers document the *type* that they serialize, so you can use it to generate documentation.
|
8
8
|
|
9
9
|
## The Basics
|
10
10
|
|
11
|
-
All serializers are
|
11
|
+
All serializers are inherited from [`SoberSwag::Serializer::Base`](../lib/sober_swag/serializer/base.rb).
|
12
12
|
This is an abstract class that implements several methods, most of which will be documented later.
|
13
13
|
The two that are most interesting, however, are `#type` and `#serialize`.
|
14
14
|
|
@@ -55,12 +55,12 @@ In the future, we might add some "debug mode" sorta thing that will do type-chec
|
|
55
55
|
|
56
56
|
### Mapped
|
57
57
|
|
58
|
-
Sometimes, you can create a
|
58
|
+
Sometimes, you can create a serializer via a *proc*.
|
59
59
|
For example, let's say that I want a serializer that takes a `Date` and returns a string.
|
60
60
|
I can do this:
|
61
61
|
|
62
62
|
```ruby
|
63
|
-
date_string = SoberSwag::Serializer.
|
63
|
+
date_string = SoberSwag::Serializer.primitive(:String).via_map { |d| d.to_s }
|
64
64
|
```
|
65
65
|
|
66
66
|
This is implemented via [`SoberSwag::Serializer::Mapped`](../lib/sober_swag/serializer/mapped.rb).
|
@@ -87,7 +87,7 @@ my_serializer.optional.serialize(nil) # => nil
|
|
87
87
|
# ^ nils become nil
|
88
88
|
```
|
89
89
|
|
90
|
-
This properly changes the `type` to be a
|
90
|
+
This properly changes the `type` to be a nilable type, as well.
|
91
91
|
|
92
92
|
### Array
|
93
93
|
|
@@ -121,9 +121,9 @@ Let's define an output object:
|
|
121
121
|
|
122
122
|
```ruby
|
123
123
|
StudentOutputObject = SoberSwag::OutputObject.define do
|
124
|
-
field :first_name,
|
125
|
-
field :last_name,
|
126
|
-
field :recent_grades,
|
124
|
+
field :first_name, primitive(:String)
|
125
|
+
field :last_name, primitive(:String)
|
126
|
+
field :recent_grades, primitive(:Integer).array do |student|
|
127
127
|
student.graded_assignments.limit(100).pluck(:grade)
|
128
128
|
end
|
129
129
|
end
|
@@ -143,10 +143,10 @@ Let's take a look at their use:
|
|
143
143
|
|
144
144
|
```ruby
|
145
145
|
StudentOutputObject = SoberSwag::OutputObject.define do
|
146
|
-
field :first_name,
|
147
|
-
field :last_name,
|
146
|
+
field :first_name, primitive(:String)
|
147
|
+
field :last_name, primitive(:String)
|
148
148
|
view :detail do
|
149
|
-
field :recent_grades,
|
149
|
+
field :recent_grades, primitive(:Integer).array do |student|
|
150
150
|
student.graded_assignments.limit(100).pluck(:grade)
|
151
151
|
end
|
152
152
|
end
|
@@ -189,7 +189,7 @@ StudentOutputObject = SoberSwag::OutputObject.define do
|
|
189
189
|
end
|
190
190
|
```
|
191
191
|
|
192
|
-
This can cause a circular
|
192
|
+
This can cause a circular dependency.
|
193
193
|
To break this, you can use a lambda:
|
194
194
|
|
195
195
|
```ruby
|
@@ -200,4 +200,4 @@ StudentOutputObject = SoberSwag::OutputObject.define do
|
|
200
200
|
end
|
201
201
|
```
|
202
202
|
|
203
|
-
For clarity (and to prevent infinitely-looping serializers on accident, we
|
203
|
+
For clarity (and to prevent infinitely-looping serializers on accident, we recommend you *always* use an explicit view for dependent output objects.
|
data/example/Gemfile.lock
CHANGED
@@ -61,16 +61,18 @@ class PeopleController < ApplicationController
|
|
61
61
|
|
62
62
|
define :get, :index, '/people/' do
|
63
63
|
query_params do
|
64
|
-
attribute? :
|
65
|
-
|
66
|
-
|
64
|
+
attribute? :filters do
|
65
|
+
attribute? :first_name, Types::String
|
66
|
+
attribute? :last_name, Types::String
|
67
|
+
end
|
68
|
+
attribute :view, Types::String.default('base'.freeze).enum('base', 'detail')
|
67
69
|
end
|
68
70
|
response(:ok, 'all the people', PersonOutputObject.array)
|
69
71
|
end
|
70
72
|
def index
|
71
73
|
@people = Person.all
|
72
|
-
@people = @people.where('UPPER(first_name) LIKE UPPER(?)', "%#{parsed_query.first_name}%") if parsed_query.first_name
|
73
|
-
@people = @people.where('UPPER(last_name) LIKE UPPER(?)', "%#{parsed_query.last_name}%") if parsed_query.last_name
|
74
|
+
@people = @people.where('UPPER(first_name) LIKE UPPER(?)', "%#{parsed_query.filters.first_name}%") if parsed_query.filters&.first_name
|
75
|
+
@people = @people.where('UPPER(last_name) LIKE UPPER(?)', "%#{parsed_query.filters.last_name}%") if parsed_query.filters&.last_name
|
74
76
|
respond!(:ok, @people.includes(:posts), serializer_opts: { view: parsed_query.view })
|
75
77
|
end
|
76
78
|
|
@@ -39,12 +39,20 @@ class PostsController < ApplicationController
|
|
39
39
|
define :get, :index, '/posts/' do
|
40
40
|
query_params do
|
41
41
|
attribute? :view, ViewTypes
|
42
|
+
attribute :include_first, SoberSwag::Types::Params::Bool.default(false).meta(description: <<~MARKDOWN)
|
43
|
+
For historical reasons the first-ever post is the entire text of *Finnegan's Wake.*
|
44
|
+
Unfortunately, our contractors wound up depending on this quirk to complete dark arcane ceremonies,
|
45
|
+
so we can't remove it. Thus, by default, we don't include the first post unless you explicitly ask us to
|
46
|
+
(maybe you feel like some classic literature?).
|
47
|
+
MARKDOWN
|
42
48
|
end
|
43
49
|
response(:ok, 'all the posts', PostOutputObject.array)
|
44
50
|
end
|
45
51
|
def index
|
46
52
|
@posts = Post.all
|
47
53
|
|
54
|
+
@posts = @posts.where('id > 1') unless parsed_query.include_first
|
55
|
+
|
48
56
|
respond!(:ok, @posts.includes(:person), serializer_opts: { view: parsed_query.view })
|
49
57
|
end
|
50
58
|
|
@@ -35,13 +35,13 @@ RSpec.describe 'Index action for people' do
|
|
35
35
|
end
|
36
36
|
|
37
37
|
context 'with a good first-name search' do
|
38
|
-
let(:request) { get '/people', params: { first_name: 'A' } }
|
38
|
+
let(:request) { get '/people', params: { filters: { first_name: 'A' } } }
|
39
39
|
|
40
40
|
it_behaves_like 'a request with the person'
|
41
41
|
end
|
42
42
|
|
43
43
|
context 'with a good last-name search' do
|
44
|
-
let(:request) { get '/people', params: { last_name: 'G' } }
|
44
|
+
let(:request) { get '/people', params: { filters: { last_name: 'G' } } }
|
45
45
|
|
46
46
|
it_behaves_like 'a request with the person'
|
47
47
|
end
|
@@ -73,13 +73,16 @@ module SoberSwag
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def path_schema
|
76
|
-
path_schema_stub.map
|
76
|
+
path_schema_stub.map do |e|
|
77
|
+
ensure_uncomplicated(e[:name], e[:schema])
|
78
|
+
e.merge(in: :path)
|
79
|
+
end
|
77
80
|
rescue TooComplicatedError => e
|
78
81
|
raise TooComplicatedForPathError, e.message
|
79
82
|
end
|
80
83
|
|
81
84
|
def query_schema
|
82
|
-
path_schema_stub.map { |e| e.merge(in: :query) }
|
85
|
+
path_schema_stub.map { |e| e.merge(in: :query, style: :deepObject, explode: true) }
|
83
86
|
rescue TooComplicatedError => e
|
84
87
|
raise TooComplicatedForQueryError, e.message
|
85
88
|
end
|
@@ -175,7 +178,7 @@ module SoberSwag
|
|
175
178
|
end
|
176
179
|
end
|
177
180
|
|
178
|
-
def to_object_schema(object) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
181
|
+
def to_object_schema(object) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
179
182
|
case object
|
180
183
|
when Nodes::List
|
181
184
|
{
|
@@ -239,13 +242,10 @@ module SoberSwag
|
|
239
242
|
def path_schema_stub
|
240
243
|
@path_schema_stub ||=
|
241
244
|
object_schema[:properties].map do |k, v|
|
242
|
-
ensure_uncomplicated(k, v)
|
245
|
+
# ensure_uncomplicated(k, v)
|
243
246
|
{
|
244
247
|
name: k,
|
245
248
|
schema: v.reject { |key, _| %i[required nullable].include?(key) },
|
246
|
-
# rubocop:disable Style/DoubleNegation
|
247
|
-
allowEmptyValue: !object_schema[:required].include?(k) || !!v[:nullable], # if it's required, no empties, but if *nullabe*, empties are okay
|
248
|
-
# rubocop:enable Style/DoubleNegation
|
249
249
|
required: object_schema[:required].include?(k) || false
|
250
250
|
}
|
251
251
|
end
|
@@ -10,7 +10,7 @@ module SoberSwag
|
|
10
10
|
##
|
11
11
|
# Given a symbol to this, we will use a primitive name
|
12
12
|
def primitive(name)
|
13
|
-
SoberSwag::Serializer.
|
13
|
+
SoberSwag::Serializer.primitive(SoberSwag::Types.const_get(name))
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
data/lib/sober_swag/parser.rb
CHANGED
@@ -10,6 +10,9 @@ module SoberSwag
|
|
10
10
|
|
11
11
|
def to_syntax # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
12
12
|
case @node
|
13
|
+
when Dry::Types::Default
|
14
|
+
# we handle this elsewhere, so
|
15
|
+
bind(Parser.new(@node.type))
|
13
16
|
when Dry::Types::Array::Member
|
14
17
|
Nodes::List.new(bind(Parser.new(@node.member)))
|
15
18
|
when Dry::Types::Enum
|
@@ -21,7 +24,7 @@ module SoberSwag
|
|
21
24
|
when Dry::Types::Schema::Key
|
22
25
|
Nodes::Attribute.new(
|
23
26
|
@node.name,
|
24
|
-
@node.required?,
|
27
|
+
@node.required? && !@node.type.default?,
|
25
28
|
bind(Parser.new(@node.type))
|
26
29
|
)
|
27
30
|
when Dry::Types::Sum
|
data/lib/sober_swag/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sober_swag
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Anthony Super
|
@@ -166,7 +166,6 @@ files:
|
|
166
166
|
- ".ruby-version"
|
167
167
|
- ".travis.yml"
|
168
168
|
- Gemfile
|
169
|
-
- Gemfile.lock
|
170
169
|
- LICENSE.txt
|
171
170
|
- README.md
|
172
171
|
- Rakefile
|
data/Gemfile.lock
DELETED
@@ -1,116 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
sober_swag (0.3.0)
|
5
|
-
activesupport
|
6
|
-
dry-struct (~> 1.0)
|
7
|
-
dry-types (~> 1.2)
|
8
|
-
|
9
|
-
GEM
|
10
|
-
remote: https://rubygems.org/
|
11
|
-
specs:
|
12
|
-
activesupport (6.0.3.2)
|
13
|
-
concurrent-ruby (~> 1.0, >= 1.0.2)
|
14
|
-
i18n (>= 0.7, < 2)
|
15
|
-
minitest (~> 5.1)
|
16
|
-
tzinfo (~> 1.1)
|
17
|
-
zeitwerk (~> 2.2, >= 2.2.2)
|
18
|
-
ast (2.4.1)
|
19
|
-
coderay (1.1.2)
|
20
|
-
concurrent-ruby (1.1.6)
|
21
|
-
diff-lcs (1.4.4)
|
22
|
-
docile (1.3.2)
|
23
|
-
dry-configurable (0.11.6)
|
24
|
-
concurrent-ruby (~> 1.0)
|
25
|
-
dry-core (~> 0.4, >= 0.4.7)
|
26
|
-
dry-equalizer (~> 0.2)
|
27
|
-
dry-container (0.7.2)
|
28
|
-
concurrent-ruby (~> 1.0)
|
29
|
-
dry-configurable (~> 0.1, >= 0.1.3)
|
30
|
-
dry-core (0.4.9)
|
31
|
-
concurrent-ruby (~> 1.0)
|
32
|
-
dry-equalizer (0.3.0)
|
33
|
-
dry-inflector (0.2.0)
|
34
|
-
dry-logic (1.0.6)
|
35
|
-
concurrent-ruby (~> 1.0)
|
36
|
-
dry-core (~> 0.2)
|
37
|
-
dry-equalizer (~> 0.2)
|
38
|
-
dry-struct (1.3.0)
|
39
|
-
dry-core (~> 0.4, >= 0.4.4)
|
40
|
-
dry-equalizer (~> 0.3)
|
41
|
-
dry-types (~> 1.3)
|
42
|
-
ice_nine (~> 0.11)
|
43
|
-
dry-types (1.4.0)
|
44
|
-
concurrent-ruby (~> 1.0)
|
45
|
-
dry-container (~> 0.3)
|
46
|
-
dry-core (~> 0.4, >= 0.4.4)
|
47
|
-
dry-equalizer (~> 0.3)
|
48
|
-
dry-inflector (~> 0.1, >= 0.1.2)
|
49
|
-
dry-logic (~> 1.0, >= 1.0.2)
|
50
|
-
i18n (1.8.3)
|
51
|
-
concurrent-ruby (~> 1.0)
|
52
|
-
ice_nine (0.11.2)
|
53
|
-
method_source (0.9.2)
|
54
|
-
minitest (5.14.1)
|
55
|
-
parallel (1.19.2)
|
56
|
-
parser (2.7.1.4)
|
57
|
-
ast (~> 2.4.1)
|
58
|
-
pry (0.12.2)
|
59
|
-
coderay (~> 1.1.0)
|
60
|
-
method_source (~> 0.9.0)
|
61
|
-
rainbow (3.0.0)
|
62
|
-
rake (13.0.1)
|
63
|
-
regexp_parser (1.7.1)
|
64
|
-
rexml (3.2.4)
|
65
|
-
rspec (3.9.0)
|
66
|
-
rspec-core (~> 3.9.0)
|
67
|
-
rspec-expectations (~> 3.9.0)
|
68
|
-
rspec-mocks (~> 3.9.0)
|
69
|
-
rspec-core (3.9.2)
|
70
|
-
rspec-support (~> 3.9.3)
|
71
|
-
rspec-expectations (3.9.2)
|
72
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
73
|
-
rspec-support (~> 3.9.0)
|
74
|
-
rspec-mocks (3.9.1)
|
75
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
76
|
-
rspec-support (~> 3.9.0)
|
77
|
-
rspec-support (3.9.3)
|
78
|
-
rubocop (0.88.0)
|
79
|
-
parallel (~> 1.10)
|
80
|
-
parser (>= 2.7.1.1)
|
81
|
-
rainbow (>= 2.2.2, < 4.0)
|
82
|
-
regexp_parser (>= 1.7)
|
83
|
-
rexml
|
84
|
-
rubocop-ast (>= 0.1.0, < 1.0)
|
85
|
-
ruby-progressbar (~> 1.7)
|
86
|
-
unicode-display_width (>= 1.4.0, < 2.0)
|
87
|
-
rubocop-ast (0.1.0)
|
88
|
-
parser (>= 2.7.0.1)
|
89
|
-
rubocop-rspec (1.42.0)
|
90
|
-
rubocop (>= 0.87.0)
|
91
|
-
ruby-progressbar (1.10.1)
|
92
|
-
simplecov (0.18.5)
|
93
|
-
docile (~> 1.1)
|
94
|
-
simplecov-html (~> 0.11)
|
95
|
-
simplecov-html (0.12.2)
|
96
|
-
thread_safe (0.3.6)
|
97
|
-
tzinfo (1.2.7)
|
98
|
-
thread_safe (~> 0.1)
|
99
|
-
unicode-display_width (1.7.0)
|
100
|
-
zeitwerk (2.4.0)
|
101
|
-
|
102
|
-
PLATFORMS
|
103
|
-
ruby
|
104
|
-
|
105
|
-
DEPENDENCIES
|
106
|
-
bundler (~> 2.0)
|
107
|
-
pry
|
108
|
-
rake (~> 13.0)
|
109
|
-
rspec (~> 3.0)
|
110
|
-
rubocop
|
111
|
-
rubocop-rspec
|
112
|
-
simplecov
|
113
|
-
sober_swag!
|
114
|
-
|
115
|
-
BUNDLED WITH
|
116
|
-
2.1.4
|