qeweney 0.7.5 → 0.8.4

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 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