qeweney 0.7.5 → 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c4a3ab1651dedfc78d9d35aa048db2d77e3eb8873d2cbd81d2944746b07ecee8
4
- data.tar.gz: f75a0a000b4c1f690885deb9b163baf9ead6d285ab2c58366821c26e995342d4
3
+ metadata.gz: f42e3096a55b17945a57c5251356358d001c0dffccd946b6d15e45197a2b14e7
4
+ data.tar.gz: 461d3ee59c50da3600f9dbb77240de119a796e720cfb3e5887f872e472619eb6
5
5
  SHA512:
6
- metadata.gz: e6aa575224a992f928fd42bec42979798e6656841eb676ca43996776ede7e24b303982a69376f735be0a5ec2ce02ced79b573a10353cd5b5d7097e168f4ce625
7
- data.tar.gz: 3870128bb83a47be1fee088bd128e8701098b92f301cc515e863da462001ea65af51018ab3e1ae0e9c2dfd7250fad310690917d47fa2afa8b3fd88c5b2cbb381
6
+ metadata.gz: 2b8767bb2d9c537d612270d14b99c250cc1094d5809d937a921da59aa53156ea91a06db51393fb0e98d850bbd996e6f7f019757f1e041ad25e65efcc1e1d4f20
7
+ data.tar.gz: 8fc8247af91f73ceba720d76eb5ef0965d9543e72c6d8edfae9e8b951ddd7269b2d2c921abf7cca05b18037f165389f9c43d3b57093206e3b81975b67c4aac75
data/CHANGELOG.md CHANGED
@@ -1,6 +1,28 @@
1
+ ## 0.8.4 2021-03-23
2
+
3
+ - Rename and fix `#parse_query` to deal with no-value query parts
4
+
5
+ ## 0.8.3 2021-03-22
6
+
7
+ - Streamline routing behaviour in sub routes (an explicit default sub route is
8
+ now required if no sub route matches)
9
+
10
+ ## 0.8.2 2021-03-17
11
+
12
+ - Fix form parsing when charset is specified
13
+
14
+ ## 0.8.1 2021-03-10
15
+
16
+ - Add `Request#transfer_counts`, `Request#total_transfer`
17
+ - Fix `Request#rx_incr`
18
+
19
+ ## 0.8 2021-03-10
20
+
21
+ - Pass request as first argument to all adapter methods
22
+
1
23
  ## 0.7.5 2021-03-08
2
24
 
3
- - Set content-type header in
25
+ - Set content-type header in `#serve_file`
4
26
 
5
27
  ## 0.7.4 2021-03-07
6
28
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- qeweney (0.7.5)
4
+ qeweney (0.8.4)
5
5
  escape_utils (~> 1.2.1)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -1,11 +1,71 @@
1
1
  # Qeweney
2
2
 
3
- ## Cross-library feature rich HTTP request / response API
3
+ ## Cross-library HTTP request / response API for servers
4
4
 
5
- Qeweney provides a uniform API for dealing with HTTP requests and responses.
5
+ Qeweney provides a uniform API for dealing with HTTP requests and responses on
6
+ the server side. Qeweney defines a uniform adapter interface that allows
7
+ handling incoming HTTP requests and sending HTTP responses over any protocol or
8
+ transport, be it HTTP/1, HTTP/2 or a Rack interface.
9
+
10
+ Qeweney is primarily designed to work with
11
+ [Tipi](https://github.com/digital-fabric/tipi), but can also be used directly
12
+ inside Rack apps, or to drive Rack apps.
6
13
 
7
14
  ## Features
8
15
 
9
- - Works with different web server APIs, notably Tipi, Digital Fabric, and Rack
10
- - Transport-agnostic
11
- - High-performance routing API inspired by Roda
16
+ - Works with different web server APIs, notably Tipi, Digital Fabric, and Rack.
17
+ - Transport-agnostic.
18
+ - High-performance routing API inspired by Roda.
19
+ - Rich API for extracting data from HTTP requests: form parsing, cookies, file
20
+ uploads, etc.
21
+ - Rich API for constructing HTTP responses: streaming responses, HTTP upgrades,
22
+ static file serving, delate and gzip encoding, caching etc.
23
+ - Suitable for both blocking and non-blocking concurrency models.
24
+ - Allows working with request before request body is read and parsed.
25
+
26
+ ## Overview
27
+
28
+ In Qeweney, the main class developers will interact with is `Qeweney::Request`,
29
+ which encapsulates an HTTP request (from the server's point of view), and offers
30
+ an API for extracting request information and responding to that request.
31
+
32
+ A request is always associated with an _adapter_, an object that implements the
33
+ Qeweney adapter interface, and that allows reading request bodies (for uploads
34
+ and form submissions) and sending responses.
35
+
36
+ ## The Qeweney Adapter Interface
37
+
38
+ ```ruby
39
+ class AdapterInterface
40
+ # Reads a chunk from the request body
41
+ # @req [Qeweney::Request] request for which the chunk is to be read
42
+ def get_body_chunk(req)
43
+ end
44
+
45
+ # Send a non-streaming response
46
+ # @req [Qeweney::Request] request for which the response is sent
47
+ # @body [String, nil] response body
48
+ # @headers [Hash] response headers
49
+ def respond(req, body, headers)
50
+ end
51
+
52
+ # Send only headers
53
+ # @req [Qeweney::Request] request for which the response is sent
54
+ # @headers [Hash] response headers
55
+ # @empty_response [boolean] whether response is empty
56
+ def send_headers(req, headers, empty_response: nil)
57
+ end
58
+
59
+ # Send a body chunk (this implies chunked transfer encoding)
60
+ # @req [Qeweney::Request] request for which the response is sent
61
+ # @body [String, nil] chunk
62
+ # @done [boolean] whether response is finished
63
+ def send_chunk(req, body, done: false)
64
+ end
65
+
66
+ # Finishes response
67
+ # @req [Qeweney::Request] request for which the response is sent
68
+ def finish(req)
69
+ end
70
+ end
71
+ ```
data/lib/qeweney/rack.rb CHANGED
@@ -32,20 +32,20 @@ module Qeweney
32
32
  headers
33
33
  end
34
34
 
35
- def respond(body, headers)
35
+ def respond(req, body, headers)
36
36
  @response_body << body
37
37
  @response_headers = headers
38
38
  end
39
39
 
40
- def send_headers(headers, empty_response: nil)
40
+ def send_headers(req, headers, empty_response: nil)
41
41
  @response_headers = headers
42
42
  end
43
43
 
44
- def send_chunk(body, done: false)
44
+ def send_chunk(req, body, done: false)
45
45
  @response_body << body
46
46
  end
47
47
 
48
- def finish
48
+ def finish(req)
49
49
  end
50
50
 
51
51
  def rack_response
@@ -34,7 +34,7 @@ module Qeweney
34
34
  return chunk
35
35
  end
36
36
 
37
- @message_complete ? nil : @adapter.get_body_chunk
37
+ @message_complete ? nil : @adapter.get_body_chunk(self)
38
38
  end
39
39
 
40
40
  def each_chunk
@@ -44,7 +44,7 @@ module Qeweney
44
44
  end
45
45
  @buffered_body_chunks = nil
46
46
  end
47
- while !@message_complete && (chunk = @adapter.get_body_chunk)
47
+ while !@message_complete && (chunk = @adapter.get_body_chunk(self))
48
48
  yield chunk
49
49
  end
50
50
  end
@@ -59,7 +59,7 @@ module Qeweney
59
59
  end
60
60
 
61
61
  def consume
62
- @adapter.consume_request
62
+ @adapter.consume_request(self)
63
63
  end
64
64
 
65
65
  def keep_alive?
@@ -68,7 +68,7 @@ module Qeweney
68
68
 
69
69
  def read
70
70
  buf = @buffered_body_chunks ? @buffered_body_chunks.join : nil
71
- while (chunk = @adapter.get_body_chunk)
71
+ while (chunk = @adapter.get_body_chunk(self))
72
72
  (buf ||= +'') << chunk
73
73
  end
74
74
  @buffered_body_chunks = nil
@@ -77,7 +77,7 @@ module Qeweney
77
77
  alias_method :body, :read
78
78
 
79
79
  def respond(body, headers = {})
80
- @adapter.respond(body, headers)
80
+ @adapter.respond(self, body, headers)
81
81
  @headers_sent = true
82
82
  end
83
83
 
@@ -85,24 +85,40 @@ module Qeweney
85
85
  return if @headers_sent
86
86
 
87
87
  @headers_sent = true
88
- @adapter.send_headers(headers, empty_response: empty_response)
88
+ @adapter.send_headers(self, headers, empty_response: empty_response)
89
89
  end
90
90
 
91
91
  def send_chunk(body, done: false)
92
92
  send_headers({}) unless @headers_sent
93
93
 
94
- @adapter.send_chunk(body, done: done)
94
+ @adapter.send_chunk(self, body, done: done)
95
95
  end
96
96
  alias_method :<<, :send_chunk
97
97
 
98
98
  def finish
99
99
  send_headers({}) unless @headers_sent
100
100
 
101
- @adapter.finish
101
+ @adapter.finish(self)
102
102
  end
103
103
 
104
104
  def headers_sent?
105
105
  @headers_sent
106
106
  end
107
+
108
+ def rx_incr(count)
109
+ headers[':rx'] ? headers[':rx'] += count : headers[':rx'] = count
110
+ end
111
+
112
+ def tx_incr(count)
113
+ headers[':tx'] ? headers[':tx'] += count : headers[':tx'] = count
114
+ end
115
+
116
+ def transfer_counts
117
+ [headers[':rx'], headers[':tx']]
118
+ end
119
+
120
+ def total_transfer
121
+ (headers[':rx'] || 0) + (headers[':tx'] || 0)
122
+ end
107
123
  end
108
124
  end
@@ -52,13 +52,13 @@ module Qeweney
52
52
  def query
53
53
  return @query if @query
54
54
 
55
- @query = (q = uri.query) ? split_query_string(q) : {}
55
+ @query = (q = uri.query) ? parse_query(q) : {}
56
56
  end
57
57
 
58
- def split_query_string(query)
58
+ def parse_query(query)
59
59
  query.split('&').each_with_object({}) do |kv, h|
60
60
  k, v = kv.split('=')
61
- h[k.to_sym] = URI.decode_www_form_component(v)
61
+ h[k.to_sym] = v ? URI.decode_www_form_component(v) : true
62
62
  end
63
63
  end
64
64
 
@@ -101,10 +101,10 @@ module Qeweney
101
101
  module RequestInfoClassMethods
102
102
  def parse_form_data(body, headers)
103
103
  case (content_type = headers['content-type'])
104
- when /multipart\/form\-data; boundary=([^\s]+)/
104
+ when /^multipart\/form\-data; boundary=([^\s]+)/
105
105
  boundary = "--#{Regexp.last_match(1)}"
106
106
  parse_multipart_form_data(body, boundary)
107
- when 'application/x-www-form-urlencoded'
107
+ when /^application\/x-www-form-urlencoded/
108
108
  parse_urlencoded_form_data(body)
109
109
  else
110
110
  raise "Unsupported form data content type: #{content_type}"
@@ -159,6 +159,7 @@ module Qeweney
159
159
 
160
160
  # TODO: send separate chunks for multi-part body
161
161
  # TODO: add support for streaming body
162
+ # TODO: add support for websocket
162
163
  end
163
164
  end
164
165
  end
@@ -16,7 +16,7 @@ module Qeweney
16
16
 
17
17
  def route_found(&block)
18
18
  catch(:stop, &block)
19
- throw :stop, headers_sent? ? :found : nil
19
+ throw :stop, :found
20
20
  end
21
21
 
22
22
  @@regexp_cache = {}
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Qeweney
4
- VERSION = '0.7.5'
4
+ VERSION = '0.8.4'
5
5
  end
@@ -13,6 +13,16 @@ class RequestInfoTest < MiniTest::Test
13
13
  assert_equal({ a: '1', b: '2', c: '3/4' }, r.query)
14
14
  end
15
15
 
16
+ def test_query
17
+ r = Qeweney.mock(':path' => '/GponForm/diag_Form?images/')
18
+ assert_equal '/GponForm/diag_Form', r.path
19
+ assert_equal({:'images/' => true}, r.query)
20
+
21
+ r = Qeweney.mock(':path' => '/?a=1&b=2')
22
+ assert_equal '/', r.path
23
+ assert_equal({a: '1', b: '2'}, r.query)
24
+ end
25
+
16
26
  def test_host
17
27
  r = Qeweney.mock(':path' => '/')
18
28
  assert_nil r.host
@@ -8,7 +8,7 @@ class RedirectTest < MiniTest::Test
8
8
  r.redirect('/foo')
9
9
 
10
10
  assert_equal [
11
- [:respond, nil, {":status"=>302, "Location"=>"/foo"}]
11
+ [:respond, r, nil, {":status"=>302, "Location"=>"/foo"}]
12
12
  ], r.response_calls
13
13
  end
14
14
 
@@ -17,7 +17,7 @@ class RedirectTest < MiniTest::Test
17
17
  r.redirect('/bar', Qeweney::Status::MOVED_PERMANENTLY)
18
18
 
19
19
  assert_equal [
20
- [:respond, nil, {":status"=>301, "Location"=>"/bar"}]
20
+ [:respond, r, nil, {":status"=>301, "Location"=>"/bar"}]
21
21
  ], r.response_calls
22
22
  end
23
23
  end
@@ -37,7 +37,7 @@ class StaticFileResponeTest < MiniTest::Test
37
37
  r.serve_file('helper.rb', base_path: __dir__)
38
38
 
39
39
  assert_equal [
40
- [:respond, @content, { 'etag' => @etag, 'last-modified' => @last_modified }]
40
+ [:respond, r, @content, { 'etag' => @etag, 'last-modified' => @last_modified }]
41
41
  ], r.response_calls
42
42
  end
43
43
 
@@ -45,25 +45,25 @@ class StaticFileResponeTest < MiniTest::Test
45
45
  r = Qeweney.mock('if-none-match' => @etag)
46
46
  r.serve_file('helper.rb', base_path: __dir__)
47
47
  assert_equal [
48
- [:respond, nil, { 'etag' => @etag, ':status' => Qeweney::Status::NOT_MODIFIED }]
48
+ [:respond, r, nil, { 'etag' => @etag, ':status' => Qeweney::Status::NOT_MODIFIED }]
49
49
  ], r.response_calls
50
50
 
51
51
  r = Qeweney.mock('if-modified-since' => @last_modified)
52
52
  r.serve_file('helper.rb', base_path: __dir__)
53
53
  assert_equal [
54
- [:respond, nil, { 'etag' => @etag, ':status' => Qeweney::Status::NOT_MODIFIED }]
54
+ [:respond, r, nil, { 'etag' => @etag, ':status' => Qeweney::Status::NOT_MODIFIED }]
55
55
  ], r.response_calls
56
56
 
57
57
  r = Qeweney.mock('if-none-match' => 'foobar')
58
58
  r.serve_file('helper.rb', base_path: __dir__)
59
59
  assert_equal [
60
- [:respond, @content, { 'etag' => @etag, 'last-modified' => @last_modified }]
60
+ [:respond, r, @content, { 'etag' => @etag, 'last-modified' => @last_modified }]
61
61
  ], r.response_calls
62
62
 
63
63
  r = Qeweney.mock('if-modified-since' => Time.now.httpdate)
64
64
  r.serve_file('helper.rb', base_path: __dir__)
65
65
  assert_equal [
66
- [:respond, @content, { 'etag' => @etag, 'last-modified' => @last_modified }]
66
+ [:respond, r, @content, { 'etag' => @etag, 'last-modified' => @last_modified }]
67
67
  ], r.response_calls
68
68
  end
69
69
 
@@ -75,7 +75,7 @@ class StaticFileResponeTest < MiniTest::Test
75
75
  deflated_content = deflate.deflate(@content, Zlib::FINISH)
76
76
 
77
77
  assert_equal [
78
- [:respond, deflated_content, {
78
+ [:respond, r, deflated_content, {
79
79
  'etag' => @etag,
80
80
  'last-modified' => @last_modified,
81
81
  'vary' => 'Accept-Encoding',
@@ -96,7 +96,7 @@ class StaticFileResponeTest < MiniTest::Test
96
96
  gzipped_content = buf.string
97
97
 
98
98
  assert_equal [
99
- [:respond, gzipped_content, {
99
+ [:respond, r, gzipped_content, {
100
100
  'etag' => @etag,
101
101
  'last-modified' => @last_modified,
102
102
  'vary' => 'Accept-Encoding',
@@ -109,7 +109,7 @@ class StaticFileResponeTest < MiniTest::Test
109
109
  r = Qeweney.mock
110
110
  r.serve_file('foo.rb', base_path: __dir__)
111
111
  assert_equal [
112
- [:respond, nil, { ':status' => Qeweney::Status::NOT_FOUND }]
112
+ [:respond, r, nil, { ':status' => Qeweney::Status::NOT_FOUND }]
113
113
  ], r.response_calls
114
114
  end
115
115
  end
@@ -120,7 +120,7 @@ class UpgradeTest < MiniTest::Test
120
120
  r.upgrade('df')
121
121
 
122
122
  assert_equal [
123
- [:respond, nil, {
123
+ [:respond, r, nil, {
124
124
  ':status' => 101,
125
125
  'Upgrade' => 'df',
126
126
  'Connection' => 'upgrade'
@@ -132,7 +132,7 @@ class UpgradeTest < MiniTest::Test
132
132
  r.upgrade('df', { 'foo' => 'bar' })
133
133
 
134
134
  assert_equal [
135
- [:respond, nil, {
135
+ [:respond, r, nil, {
136
136
  ':status' => 101,
137
137
  'Upgrade' => 'df',
138
138
  'Connection' => 'upgrade',
@@ -155,7 +155,7 @@ class UpgradeTest < MiniTest::Test
155
155
  accept = Digest::SHA1.base64digest('abcdefghij258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
156
156
 
157
157
  assert_equal [
158
- [:respond, nil, {
158
+ [:respond, r, nil, {
159
159
  ':status' => 101,
160
160
  'Upgrade' => 'websocket',
161
161
  'Connection' => 'upgrade',
@@ -179,7 +179,7 @@ class ServeRackTest < MiniTest::Test
179
179
  })
180
180
 
181
181
  assert_equal [
182
- [:respond, "get /foo/bar", {':status' => 404, 'Foo' => 'Bar' }]
182
+ [:respond, r, 'get /foo/bar', {':status' => 404, 'Foo' => 'Bar' }]
183
183
  ], r.response_calls
184
184
  end
185
185
  end
data/test/test_routing.rb CHANGED
@@ -12,6 +12,7 @@ class RoutingTest < MiniTest::Test
12
12
  r.on_post do
13
13
  r.redirect '/'
14
14
  end
15
+ r.default { r.respond nil, ':status' => 404 }
15
16
  end
16
17
  end
17
18
  end
@@ -19,27 +20,27 @@ class RoutingTest < MiniTest::Test
19
20
  def test_app1
20
21
  r = Qeweney.mock(':path' => '/foo')
21
22
  App1.(r)
22
- assert_equal [[:respond, nil, { ':status' => 404 }]], r.response_calls
23
+ assert_equal [[:respond, r, nil, { ':status' => 404 }]], r.response_calls
23
24
 
24
25
  r = Qeweney.mock(':path' => '/')
25
26
  App1.(r)
26
- assert_equal [[:respond, nil, { ':status' => 302, 'Location' => '/hello' }]], r.response_calls
27
+ assert_equal [[:respond, r, nil, { ':status' => 302, 'Location' => '/hello' }]], r.response_calls
27
28
 
28
29
  r = Qeweney.mock(':path' => '/hello', ':method' => 'foo')
29
30
  App1.(r)
30
- assert_equal [[:respond, nil, { ':status' => 404 }]], r.response_calls
31
+ assert_equal [[:respond, r, nil, { ':status' => 404 }]], r.response_calls
31
32
 
32
33
  r = Qeweney.mock(':path' => '/hello', ':method' => 'get')
33
34
  App1.(r)
34
- assert_equal [[:respond, 'Hello', {}]], r.response_calls
35
+ assert_equal [[:respond, r, 'Hello', {}]], r.response_calls
35
36
 
36
37
  r = Qeweney.mock(':path' => '/hello', ':method' => 'post')
37
38
  App1.(r)
38
- assert_equal [[:respond, nil, { ':status' => 302, 'Location' => '/' }]], r.response_calls
39
+ assert_equal [[:respond, r, nil, { ':status' => 302, 'Location' => '/' }]], r.response_calls
39
40
 
40
41
  r = Qeweney.mock(':path' => '/hello/world', ':method' => 'get')
41
42
  App1.(r)
42
- assert_equal [[:respond, 'Hello world', {}]], r.response_calls
43
+ assert_equal [[:respond, r, 'Hello world', {}]], r.response_calls
43
44
  end
44
45
 
45
46
  def test_on_root
@@ -58,19 +59,19 @@ class RoutingTest < MiniTest::Test
58
59
 
59
60
  r = Qeweney.mock(':path' => '/')
60
61
  app.(r)
61
- assert_equal [[:respond, 'root', {}]], r.response_calls
62
+ assert_equal [[:respond, r, 'root', {}]], r.response_calls
62
63
 
63
64
  r = Qeweney.mock(':path' => '/foo')
64
65
  app.(r)
65
- assert_equal [[:respond, 'foo root', {}]], r.response_calls
66
+ assert_equal [[:respond, r, 'foo root', {}]], r.response_calls
66
67
 
67
68
  r = Qeweney.mock(':path' => '/foo/bar')
68
69
  app.(r)
69
- assert_equal [[:respond, 'bar root', {}]], r.response_calls
70
+ assert_equal [[:respond, r, 'bar root', {}]], r.response_calls
70
71
 
71
72
  r = Qeweney.mock(':path' => '/foo/bar/baz')
72
73
  app.(r)
73
- assert_equal [[:respond, 'baz root', {}]], r.response_calls
74
+ assert_equal [[:respond, r, 'baz root', {}]], r.response_calls
74
75
  end
75
76
 
76
77
  def test_relative_path
@@ -90,27 +91,27 @@ class RoutingTest < MiniTest::Test
90
91
  r = Qeweney.mock(':path' => '/')
91
92
  app.(r)
92
93
  assert_equal '/', default_relative_path
93
- assert_equal [[:respond, 'ROOT/', {}]], r.response_calls
94
+ assert_equal [[:respond, r, 'ROOT/', {}]], r.response_calls
94
95
 
95
96
 
96
97
  r = Qeweney.mock(':path' => '/foo/bar/baz')
97
98
  app.(r)
98
99
  assert_equal '/foo/bar/baz', default_relative_path
99
- assert_equal [[:respond, 'FOO/bar/baz', {}]], r.response_calls
100
+ assert_equal [[:respond, r, 'FOO/bar/baz', {}]], r.response_calls
100
101
 
101
102
  r = Qeweney.mock(':path' => '/bar/a/b/c')
102
103
  app.(r)
103
104
  assert_equal '/bar/a/b/c', default_relative_path
104
- assert_equal [[:respond, 'BAR/a/b/c', {}]], r.response_calls
105
+ assert_equal [[:respond, r, 'BAR/a/b/c', {}]], r.response_calls
105
106
 
106
107
  r = Qeweney.mock(':path' => '/bar/baz/b/c')
107
108
  app.(r)
108
109
  assert_equal '/bar/baz/b/c', default_relative_path
109
- assert_equal [[:respond, 'BAR/BAZ/b/c', {}]], r.response_calls
110
+ assert_equal [[:respond, r, 'BAR/BAZ/b/c', {}]], r.response_calls
110
111
 
111
112
  r = Qeweney.mock(':path' => '/baz/d/e/f')
112
113
  app.(r)
113
114
  assert_equal '/baz/d/e/f', default_relative_path
114
- assert_equal [[:respond, '/d/e/f', {}]], r.response_calls
115
+ assert_equal [[:respond, r, '/d/e/f', {}]], r.response_calls
115
116
  end
116
117
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qeweney
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.5
4
+ version: 0.8.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-08 00:00:00.000000000 Z
11
+ date: 2021-03-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: escape_utils