nice_http 1.6.5 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +191 -0
- data/lib/nice_http.rb +22 -3
- data/lib/nice_http/manage_request.rb +6 -2
- data/lib/nice_http/manage_response.rb +98 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 74ab2804913e2aaa1677a20d5819d9e8a30f4689ffc3f2cc9805ec1f1bd5a9f6
|
4
|
+
data.tar.gz: 24d575ea5251c1e9a39d9af68ca794c07d0f858e95d30c72dfccdcbefa40633e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69591afdbafca487154dd816af0eaff06edb3bfd09f877204124b9aa0978cf37f8b84a6f76d9c7f2baa5e19058d32277806226ff63b6603622065039bf7805b8
|
7
|
+
data.tar.gz: 3e0f852310ae7ecc1a47d4bed3b45df81ddd28cbde1c516c06da867562dcfb315530b508899742850aaae360477d68096117f61507b51ba1525ebfa1b5a514fa
|
data/README.md
CHANGED
@@ -351,6 +351,197 @@ Example posting a csv file:
|
|
351
351
|
|
352
352
|
```
|
353
353
|
|
354
|
+
## Http logs
|
355
|
+
|
356
|
+
You can set where the http logs will be stored by using the log attribute of the NiceHttp.
|
357
|
+
By default they will be stored in your root directory with the name nice_http.log.
|
358
|
+
```ruby
|
359
|
+
# you can specify the default for all connections
|
360
|
+
NiceHttp.log = :file_run
|
361
|
+
|
362
|
+
# also you can specify for a concrete connection
|
363
|
+
http = NiceHttp.new({host: 'www.example.com', log: './example.log'})
|
364
|
+
```
|
365
|
+
|
366
|
+
Other values you can supply:
|
367
|
+
* :fix_file, will log the communication on nice_http.log. (default).
|
368
|
+
* :no, won't generate any logs.
|
369
|
+
* :screen, will print the logs on the screen.
|
370
|
+
* :file, will be generated a log file with name: nice_http_YY-mm-dd-HHMMSS.log.
|
371
|
+
* :file_run, will generate a log file with the name where the object was created and extension .log, fex: myfile.rb.log
|
372
|
+
* String, the path and file name where the logs will be stored.
|
373
|
+
|
374
|
+
Example of logs:
|
375
|
+
```
|
376
|
+
I, [2019-03-22T18:38:58.518964 #29412] INFO -- : (47266856647720): Http connection created. host:www.reqres.in, port:443, ssl:true, mode:, proxy_host: , proxy_port:
|
377
|
+
I, [2019-03-22T18:38:58.537106 #29412] INFO -- : (47266856647720): Http connection: https://www.reqres.in:443
|
378
|
+
|
379
|
+
|
380
|
+
- - - - - - - - - - - - - - - - - - - - - - - - -
|
381
|
+
POST Request: Doom.example
|
382
|
+
path: /api/users
|
383
|
+
headers: {Loop:44, Cookie:, Boom:33, Content-Type:application/json, }
|
384
|
+
data: {
|
385
|
+
"name": "peter",
|
386
|
+
"job": "leader",
|
387
|
+
"products": [
|
388
|
+
{
|
389
|
+
"one": "uno",
|
390
|
+
"two": 2
|
391
|
+
},
|
392
|
+
{
|
393
|
+
"one": "uno",
|
394
|
+
"two": 22
|
395
|
+
}
|
396
|
+
]
|
397
|
+
}
|
398
|
+
|
399
|
+
I, [2019-03-22T18:38:58.873935 #29412] INFO -- :
|
400
|
+
RESPONSE:
|
401
|
+
201:Created
|
402
|
+
time_elapsed_total: '0.335720719'
|
403
|
+
time_elapsed: '0.335728095'
|
404
|
+
date: 'Fri, 22 Mar 2019 18:38:58 GMT'
|
405
|
+
content-type: 'application/json; charset=utf-8'
|
406
|
+
content-length: '172'
|
407
|
+
connection: 'keep-alive'
|
408
|
+
set-cookie: '__cfduid=dfb962e62cd8386ce4ab9bad601611553272738; expires=Sat, 21-Mar-20 18:38:58 GMT; path=/; domain=.reqres.in; HttpOnly'
|
409
|
+
x-powered-by: 'Express'
|
410
|
+
access-control-allow-origin: '*'
|
411
|
+
etag: 'W/"ac-EMh4XBmK5vry/OeKaGWILGtmHU0"'
|
412
|
+
expect-ct: 'max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"'
|
413
|
+
server: 'cloudflare'
|
414
|
+
cf-ray: '4bb99958090dbf89-AMS'
|
415
|
+
data: '{
|
416
|
+
"name": "peter",
|
417
|
+
"job": "leader",
|
418
|
+
"products": [
|
419
|
+
{
|
420
|
+
"one": "uno",
|
421
|
+
"two": 2
|
422
|
+
},
|
423
|
+
{
|
424
|
+
"one": "uno",
|
425
|
+
"two": 22
|
426
|
+
}
|
427
|
+
],
|
428
|
+
"id": "628",
|
429
|
+
"createdAt": "2019-03-22T18:43:33.619Z"
|
430
|
+
}'
|
431
|
+
|
432
|
+
I, [2019-03-22T18:38:58.874190 #29412] INFO -- : set-cookie added to Cookie header as required
|
433
|
+
I, [2019-03-22T18:38:59.075293 #29412] INFO -- :
|
434
|
+
|
435
|
+
- - - - - - - - - - - - - - - - - - - - - - - - -
|
436
|
+
GET Request: Doom.example
|
437
|
+
path: /api/users
|
438
|
+
Same headers and data as in the previous request.
|
439
|
+
I, [2019-03-22T18:38:59.403459 #29412] INFO -- :
|
440
|
+
RESPONSE:
|
441
|
+
200:OK
|
442
|
+
time_elapsed_total: '0.327002338'
|
443
|
+
time_elapsed: '0.327004766'
|
444
|
+
date: 'Fri, 22 Mar 2019 18:38:59 GMT'
|
445
|
+
content-type: 'application/json; charset=utf-8'
|
446
|
+
transfer-encoding: 'chunked'
|
447
|
+
connection: 'keep-alive'
|
448
|
+
x-powered-by: 'Express'
|
449
|
+
access-control-allow-origin: '*'
|
450
|
+
etag: 'W/"1bb-D+c3sZ5g5u/nmLPQRl1uVo2heAo"'
|
451
|
+
expect-ct: 'max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"'
|
452
|
+
server: 'cloudflare'
|
453
|
+
cf-ray: '4bb9995b5c20bf89-AMS'
|
454
|
+
data: '{
|
455
|
+
"page": 1,
|
456
|
+
"per_page": 3,
|
457
|
+
"total": 12,
|
458
|
+
"total_pages": 4,
|
459
|
+
"data": [
|
460
|
+
{
|
461
|
+
"id": 1,
|
462
|
+
"first_name": "George",
|
463
|
+
"last_name": "Bluth",
|
464
|
+
"avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/calebogden/128.jpg"
|
465
|
+
},
|
466
|
+
{
|
467
|
+
"id": 2,
|
468
|
+
"first_name": "Janet",
|
469
|
+
"last_name": "Weaver",
|
470
|
+
"avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/josephstein/128.jpg"
|
471
|
+
},
|
472
|
+
]
|
473
|
+
}'
|
474
|
+
|
475
|
+
```
|
476
|
+
|
477
|
+
## Http stats
|
478
|
+
|
479
|
+
If you want to get a summarize stats of your http communication you need to set `NiceHttp.create_stats = true`
|
480
|
+
|
481
|
+
Then whenever you want to access the stats: `NiceHttp.stats`
|
482
|
+
|
483
|
+
Also it is very convenient to store the stats on a file, for example on YAML format. You can use the at_exit method to be run at the end of the run:
|
484
|
+
|
485
|
+
```ruby
|
486
|
+
at_exit do
|
487
|
+
require 'yaml'
|
488
|
+
NiceHttp.stats.keys.each do |key|
|
489
|
+
File.open("./nice_http_stats_#{key}.yaml", "w") { |file| file.write(NiceHttp.stats[key].to_yaml) }
|
490
|
+
end
|
491
|
+
end
|
492
|
+
```
|
493
|
+
|
494
|
+
This is an example of the output:
|
495
|
+
|
496
|
+
```yaml
|
497
|
+
---
|
498
|
+
www.reqres.in:443:
|
499
|
+
:num_requests: 11
|
500
|
+
:time_elapsed:
|
501
|
+
:total: 2.947269038
|
502
|
+
:maximum: 0.357101109
|
503
|
+
:minimum: 0.198707111
|
504
|
+
:average: 0.2679335489090909
|
505
|
+
"/api/users":
|
506
|
+
:num_requests: 11
|
507
|
+
:time_elapsed:
|
508
|
+
:total: 2.947269038
|
509
|
+
:maximum: 0.357101109
|
510
|
+
:minimum: 0.198707111
|
511
|
+
:average: 0.2679335489090909
|
512
|
+
:method:
|
513
|
+
POST:
|
514
|
+
:num_requests: 8
|
515
|
+
:time_elapsed:
|
516
|
+
:total: 2.3342455970000002
|
517
|
+
:maximum: 0.357101109
|
518
|
+
:minimum: 0.198707111
|
519
|
+
:average: 0.29178069962500003
|
520
|
+
:response:
|
521
|
+
'201':
|
522
|
+
:num_requests: 8
|
523
|
+
:time_elapsed:
|
524
|
+
:total: 2.3342455970000002
|
525
|
+
:maximum: 0.357101109
|
526
|
+
:minimum: 0.198707111
|
527
|
+
:average: 0.29178069962500003
|
528
|
+
GET:
|
529
|
+
:num_requests: 3
|
530
|
+
:time_elapsed:
|
531
|
+
:total: 0.613023441
|
532
|
+
:maximum: 0.210662528
|
533
|
+
:minimum: 0.200197583
|
534
|
+
:average: 0.20434114699999997
|
535
|
+
:response:
|
536
|
+
'200':
|
537
|
+
:num_requests: 3
|
538
|
+
:time_elapsed:
|
539
|
+
:total: 0.613023441
|
540
|
+
:maximum: 0.210662528
|
541
|
+
:minimum: 0.200197583
|
542
|
+
:average: 0.20434114699999997
|
543
|
+
```
|
544
|
+
|
354
545
|
## Contributing
|
355
546
|
|
356
547
|
Bug reports are very welcome on GitHub at https://github.com/marioruiz/nice_http.
|
data/lib/nice_http.rb
CHANGED
@@ -9,7 +9,7 @@ require_relative "nice_http/http_methods"
|
|
9
9
|
# Attributes you can access using NiceHttp.the_attribute:
|
10
10
|
# :host, :port, :ssl, :headers, :debug, :log, :proxy_host, :proxy_port,
|
11
11
|
# :last_request, :last_response, :request_id, :use_mocks, :connections,
|
12
|
-
# :active, :auto_redirect, :values_for
|
12
|
+
# :active, :auto_redirect, :values_for, :create_stats, :stats
|
13
13
|
#
|
14
14
|
# @attr [String] host The host to be accessed
|
15
15
|
# @attr [Integer] port The port number
|
@@ -41,6 +41,8 @@ require_relative "nice_http/http_methods"
|
|
41
41
|
# my_http.logger.info "add this to the log file"
|
42
42
|
# @see https://ruby-doc.org/stdlib-2.5.0/libdoc/logger/rdoc/Logger.html
|
43
43
|
# @attr [Hash] values_for The default values to set on the data in case not specified others
|
44
|
+
# @attr [Boolean] create_stats If true, NiceHttp will create stats of the http communication and store them on NiceHttp.stats hash
|
45
|
+
# @attr [Hash] stats It contains detailed stats of the http communication
|
44
46
|
######################################################
|
45
47
|
class NiceHttp
|
46
48
|
include NiceHttpManageRequest
|
@@ -64,7 +66,7 @@ class NiceHttp
|
|
64
66
|
class << self
|
65
67
|
attr_accessor :host, :port, :ssl, :headers, :debug, :log, :proxy_host, :proxy_port,
|
66
68
|
:last_request, :last_response, :request_id, :use_mocks, :connections,
|
67
|
-
:active, :auto_redirect, :log_files, :values_for
|
69
|
+
:active, :auto_redirect, :log_files, :values_for, :create_stats, :stats
|
68
70
|
end
|
69
71
|
|
70
72
|
######################################################
|
@@ -88,6 +90,21 @@ class NiceHttp
|
|
88
90
|
@active = 0
|
89
91
|
@auto_redirect = true
|
90
92
|
@log_files = {}
|
93
|
+
@create_stats = false
|
94
|
+
@stats = {
|
95
|
+
all: {
|
96
|
+
num_requests: 0,
|
97
|
+
time_elapsed: {
|
98
|
+
total: 0,
|
99
|
+
maximum: 0,
|
100
|
+
minimum: 100000,
|
101
|
+
average: 0,
|
102
|
+
},
|
103
|
+
method: {},
|
104
|
+
},
|
105
|
+
path: {},
|
106
|
+
name: {},
|
107
|
+
}
|
91
108
|
end
|
92
109
|
reset!
|
93
110
|
|
@@ -104,7 +121,7 @@ class NiceHttp
|
|
104
121
|
######################################################
|
105
122
|
# Change the default values for NiceHttp supplying a Hash
|
106
123
|
#
|
107
|
-
# @param par [Hash] keys: :host, :port, :ssl, :headers, :debug, :log, :proxy_host, :proxy_port, :use_mocks, :auto_redirect, :values_for
|
124
|
+
# @param par [Hash] keys: :host, :port, :ssl, :headers, :debug, :log, :proxy_host, :proxy_port, :use_mocks, :auto_redirect, :values_for, :create_stats
|
108
125
|
######################################################
|
109
126
|
def self.defaults=(par = {})
|
110
127
|
@host = par[:host] if par.key?(:host)
|
@@ -118,6 +135,7 @@ class NiceHttp
|
|
118
135
|
@proxy_port = par[:proxy_port] if par.key?(:proxy_port)
|
119
136
|
@use_mocks = par[:use_mocks] if par.key?(:use_mocks)
|
120
137
|
@auto_redirect = par[:auto_redirect] if par.key?(:auto_redirect)
|
138
|
+
@create_stats = par[:create_stats] if par.key?(:create_stats)
|
121
139
|
end
|
122
140
|
|
123
141
|
######################################################
|
@@ -184,6 +202,7 @@ class NiceHttp
|
|
184
202
|
@auto_redirect = false #set it up at the end of initialize
|
185
203
|
auto_redirect = self.class.auto_redirect
|
186
204
|
@num_redirects = 0
|
205
|
+
@create_stats = self.class.create_stats
|
187
206
|
|
188
207
|
#todo: set only the cookies for the current domain
|
189
208
|
#key: path, value: hash with key is the name of the cookie and value the value
|
@@ -191,7 +191,7 @@ module NiceHttpManageRequest
|
|
191
191
|
|
192
192
|
headers_ts = ""
|
193
193
|
headers_t.each { |key, val| headers_ts += key.to_s + ":" + val.to_s() + ", " }
|
194
|
-
message = "#{"- " * 25}\n"
|
194
|
+
message = "\n\n#{"- " * 25}\n"
|
195
195
|
if arguments.size == 1 and arguments[0].kind_of?(Hash) and arguments[0].key?(:name)
|
196
196
|
message += "#{method_s.upcase} Request: #{arguments[0][:name]}"
|
197
197
|
else
|
@@ -203,7 +203,7 @@ module NiceHttpManageRequest
|
|
203
203
|
message += " data: " + data_s.to_s() + "\n"
|
204
204
|
message = @message_server + "\n" + message
|
205
205
|
else
|
206
|
-
message += " Same as the
|
206
|
+
message += " Same#{" headers" if headers_t != {}}#{" and" if headers_t != {} and data.to_s != ""}#{" data" if data.to_s != ""} as in the previous request."
|
207
207
|
end
|
208
208
|
if path.to_s().scan(/^https?:\/\//).size > 0 and path.to_s().scan(/^https?:\/\/#{@host}/).size == 0
|
209
209
|
# the path is for another server than the current
|
@@ -219,6 +219,10 @@ module NiceHttpManageRequest
|
|
219
219
|
@prev_request[:path] = path
|
220
220
|
@prev_request[:data] = data
|
221
221
|
@prev_request[:headers] = headers_t
|
222
|
+
@prev_request[:method] = method_s.upcase
|
223
|
+
if arguments.size == 1 and arguments[0].kind_of?(Hash) and arguments[0].key?(:name)
|
224
|
+
@prev_request[:name] = arguments[0][:name]
|
225
|
+
end
|
222
226
|
return path, data, headers_t
|
223
227
|
rescue Exception => stack
|
224
228
|
@logger.fatal(stack)
|
@@ -24,6 +24,9 @@ module NiceHttpManageResponse
|
|
24
24
|
else
|
25
25
|
@response[:time_elapsed] = nil
|
26
26
|
end
|
27
|
+
|
28
|
+
create_stats(resp) if @create_stats
|
29
|
+
|
27
30
|
begin
|
28
31
|
# this is to be able to access all keys as symbols
|
29
32
|
new_resp = Hash.new()
|
@@ -90,8 +93,8 @@ module NiceHttpManageResponse
|
|
90
93
|
|
91
94
|
@response[:code] = resp.code
|
92
95
|
message = "\nRESPONSE: \n " + @response[:code].to_s() + ":" + @response[:message].to_s()
|
93
|
-
if @debug or @prev_response[:'content-type']
|
94
|
-
|
96
|
+
if @debug or @prev_response[:'content-type'] != @response[:'content-type'] or @prev_response[:'content-length'] != @response[:'content-length'] or
|
97
|
+
@prev_response[:data] != @response[:data] or @prev_response[:code] != @response[:code] or @prev_response[:message] != @response[:message]
|
95
98
|
self.class.last_response = message if @debug
|
96
99
|
@response.each { |key, value|
|
97
100
|
if value.to_s() != ""
|
@@ -180,4 +183,97 @@ module NiceHttpManageResponse
|
|
180
183
|
@logger.fatal "manage_response Error on method #{method_s} "
|
181
184
|
end
|
182
185
|
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
def set_stats(hash)
|
190
|
+
unless hash.key?(:num_requests)
|
191
|
+
# to add to the end the previous keys so num_requests and time_elapsed come first
|
192
|
+
keys = hash.keys
|
193
|
+
hash.keys.each do |k|
|
194
|
+
hash.delete(k)
|
195
|
+
end
|
196
|
+
|
197
|
+
hash[:num_requests] = 0
|
198
|
+
hash[:time_elapsed] = {
|
199
|
+
total: 0,
|
200
|
+
maximum: 0,
|
201
|
+
minimum: 100000,
|
202
|
+
average: 0,
|
203
|
+
}
|
204
|
+
|
205
|
+
# to add to the end the previous keys so num_requests and time_elapsed come first
|
206
|
+
keys.each do |k|
|
207
|
+
hash[k] = {}
|
208
|
+
end
|
209
|
+
end
|
210
|
+
hash[:num_requests] += 1
|
211
|
+
hash[:time_elapsed][:total] += @response[:time_elapsed]
|
212
|
+
hash[:time_elapsed][:maximum] = @response[:time_elapsed] if @response[:time_elapsed] > hash[:time_elapsed][:maximum]
|
213
|
+
hash[:time_elapsed][:minimum] = @response[:time_elapsed] if @response[:time_elapsed] < hash[:time_elapsed][:minimum]
|
214
|
+
hash[:time_elapsed][:average] = hash[:time_elapsed][:total] / hash[:num_requests]
|
215
|
+
end
|
216
|
+
|
217
|
+
private
|
218
|
+
|
219
|
+
def create_stats(resp)
|
220
|
+
# all
|
221
|
+
set_stats(self.class.stats[:all])
|
222
|
+
# all method
|
223
|
+
unless self.class.stats[:all][:method].key?(@prev_request[:method])
|
224
|
+
self.class.stats[:all][:method][@prev_request[:method]] = {
|
225
|
+
response: {},
|
226
|
+
}
|
227
|
+
end
|
228
|
+
set_stats(self.class.stats[:all][:method][@prev_request[:method]])
|
229
|
+
# all method response
|
230
|
+
unless self.class.stats[:all][:method][@prev_request[:method]][:response].key?(resp.code)
|
231
|
+
self.class.stats[:all][:method][@prev_request[:method]][:response][resp.code] = {}
|
232
|
+
end
|
233
|
+
set_stats(self.class.stats[:all][:method][@prev_request[:method]][:response][resp.code])
|
234
|
+
|
235
|
+
# server
|
236
|
+
server = "#{@host}:#{@port}"
|
237
|
+
unless self.class.stats[:path].key?(server)
|
238
|
+
self.class.stats[:path][server] = {}
|
239
|
+
end
|
240
|
+
set_stats(self.class.stats[:path][server])
|
241
|
+
# server path
|
242
|
+
unless self.class.stats[:path][server].key?(@prev_request[:path])
|
243
|
+
self.class.stats[:path][server][@prev_request[:path]] = {method: {}}
|
244
|
+
end
|
245
|
+
set_stats(self.class.stats[:path][server][@prev_request[:path]])
|
246
|
+
# server path method
|
247
|
+
unless self.class.stats[:path][server][@prev_request[:path]][:method].key?(@prev_request[:method])
|
248
|
+
self.class.stats[:path][server][@prev_request[:path]][:method][@prev_request[:method]] = {
|
249
|
+
response: {},
|
250
|
+
}
|
251
|
+
end
|
252
|
+
set_stats(self.class.stats[:path][server][@prev_request[:path]][:method][@prev_request[:method]])
|
253
|
+
# server path method response
|
254
|
+
unless self.class.stats[:path][server][@prev_request[:path]][:method][@prev_request[:method]][:response].key?(resp.code)
|
255
|
+
self.class.stats[:path][server][@prev_request[:path]][:method][@prev_request[:method]][:response][resp.code] = {}
|
256
|
+
end
|
257
|
+
set_stats(self.class.stats[:path][server][@prev_request[:path]][:method][@prev_request[:method]][:response][resp.code])
|
258
|
+
|
259
|
+
if @prev_request.key?(:name)
|
260
|
+
# name
|
261
|
+
unless self.class.stats[:name].key?(@prev_request[:name])
|
262
|
+
self.class.stats[:name][@prev_request[:name]] = {method: {}}
|
263
|
+
end
|
264
|
+
set_stats(self.class.stats[:name][@prev_request[:name]])
|
265
|
+
# name method
|
266
|
+
unless self.class.stats[:name][@prev_request[:name]][:method].key?(@prev_request[:method])
|
267
|
+
self.class.stats[:name][@prev_request[:name]][:method][@prev_request[:method]] = {
|
268
|
+
response: {},
|
269
|
+
}
|
270
|
+
end
|
271
|
+
set_stats(self.class.stats[:name][@prev_request[:name]][:method][@prev_request[:method]])
|
272
|
+
# name method response
|
273
|
+
unless self.class.stats[:name][@prev_request[:name]][:method][@prev_request[:method]][:response].key?(resp.code)
|
274
|
+
self.class.stats[:name][@prev_request[:name]][:method][@prev_request[:method]][:response][resp.code] = {}
|
275
|
+
end
|
276
|
+
set_stats(self.class.stats[:name][@prev_request[:name]][:method][@prev_request[:method]][:response][resp.code])
|
277
|
+
end
|
278
|
+
end
|
183
279
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nice_http
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mario Ruiz
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-03-
|
11
|
+
date: 2019-03-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nice_hash
|