ur 0.0.4 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,8 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ur' unless Object.const_defined?(:Ur)
2
4
 
3
- class Ur
5
+ module Ur
6
+ # functionality common to Request and Response
4
7
  module RequestAndResponse
8
+ # functionality for handling request/response entities from Faraday
5
9
  module FaradayEntity
10
+ # @param env [Faraday::Env] faraday env passed to middleware #call
6
11
  def set_body_from_faraday(env)
7
12
  if env[:raw_body].respond_to?(:to_str)
8
13
  self.body = env[:raw_body].to_str.dup
@@ -20,20 +25,38 @@ class Ur
20
25
  end
21
26
  include FaradayEntity
22
27
 
23
- def content_type_attrs
24
- return @content_type_attrs if instance_variable_defined?(:@content_type_attrs)
25
- @content_type_attrs = ContentTypeAttrs.new(content_type)
26
- end
27
-
28
+ # the string value of the content type header. returns an
29
+ # {Ur::ContentType}, a subclass of String which additionally parses the Content-Type
30
+ # according to relevant RFCs.
31
+ # @return [Ur::ContentType]
28
32
  def content_type
29
33
  headers.each do |k, v|
30
- return v if k =~ /\Acontent[-_]type\z/i
34
+ return ContentType.new(v) if k =~ /\Acontent[-_]type\z/i
31
35
  end
32
36
  nil
33
37
  end
34
38
 
39
+ # the media type of the content type
35
40
  def media_type
36
- content_type_attrs.media_type
41
+ content_type ? content_type.media_type : nil
42
+ end
43
+
44
+ # is our content type JSON?
45
+ # @return [Boolean]
46
+ def json?
47
+ content_type && content_type.json?
48
+ end
49
+
50
+ # is our content type XML?
51
+ # @return [Boolean]
52
+ def xml?
53
+ content_type && content_type.xml?
54
+ end
55
+
56
+ # is our content type `x-www-form-urlencoded`?
57
+ # @return [Boolean]
58
+ def form_urlencoded?
59
+ content_type && content_type.form_urlencoded?
37
60
  end
38
61
  end
39
62
  end
data/lib/ur/response.rb CHANGED
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ur' unless Object.const_defined?(:Ur)
2
4
 
3
- class Ur
4
- class Response
5
+ module Ur
6
+ module Response
5
7
  include RequestAndResponse
6
8
  include SubUr
7
9
 
data/lib/ur/sub_ur.rb CHANGED
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ur' unless Object.const_defined?(:Ur)
2
4
 
3
- class Ur
5
+ module Ur
4
6
  module SubUr
5
7
  def ur
6
- parents.detect { |p| p.is_a?(::Ur) }
8
+ jsi_parent_nodes.detect { |p| p.is_a?(::Ur) }
7
9
  end
8
10
  end
9
11
  end
data/lib/ur/version.rb CHANGED
@@ -1 +1 @@
1
- UR_VERSION = "0.0.4".freeze
1
+ UR_VERSION = "0.2.1".freeze
data/lib/ur.rb CHANGED
@@ -1,46 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "ur/version"
2
4
 
3
5
  require 'jsi'
4
6
  require 'time'
5
7
  require 'addressable/uri'
8
+ require 'pathname'
9
+ require 'yaml'
6
10
 
7
- Ur = JSI.class_for_schema({
8
- id: 'https://schemas.ur.unth.net/ur',
9
- type: 'object',
10
- properties: {
11
- bound: {
12
- type: 'string',
13
- description: %q([rfc2616] Inbound and outbound refer to the request and response paths for messages: "inbound" means "traveling toward the origin server", and "outbound" means "traveling toward the user agent"),
14
- enum: ['inbound', 'outbound'],
15
- },
16
- request: {
17
- type: 'object',
18
- properties: {
19
- method: {type: 'string', description: 'HTTP ', example: 'POST'},
20
- uri: {type: 'string', example: 'https://example.com/foo?bar=baz'},
21
- headers: {type: 'object'},
22
- body: {type: 'string'},
23
- },
24
- },
25
- response: {
26
- type: 'object',
27
- properties: {
28
- status: {type: 'integer', example: 200},
29
- headers: {type: 'object'},
30
- body: {type: 'string'},
31
- },
32
- },
33
- processing: {
34
- type: 'object',
35
- properties: {
36
- began_at_s: {type: 'string'},
37
- duration: {type: 'number'},
38
- tags: {type: 'array', items: {type: 'string'}}
39
- },
40
- },
41
- },
42
- })
43
- class Ur
11
+ UR_ROOT = Pathname.new(__FILE__).dirname.parent.expand_path
12
+ Ur = JSI.new_schema_module(YAML.load_file(UR_ROOT.join('resources/ur.schema.yml')))
13
+ module Ur
44
14
  VERSION = UR_VERSION
45
15
 
46
16
  autoload :SubUr, 'ur/sub_ur'
@@ -50,17 +20,29 @@ class Ur
50
20
  autoload :RackMiddleware, 'ur/middleware'
51
21
  autoload :Faraday, 'ur/faraday'
52
22
 
53
- Request = JSI.class_for_schema(self.schema['properties']['request'])
54
- Response = JSI.class_for_schema(self.schema['properties']['response'])
55
- Processing = JSI.class_for_schema(self.schema['properties']['processing'])
23
+ Request = self.properties['request']
24
+ Response = self.properties['response']
25
+ Metadata = self.properties['metadata']
56
26
  require 'ur/request'
57
27
  require 'ur/response'
58
- require 'ur/processing'
28
+ require 'ur/metadata'
59
29
 
60
- autoload :ContentTypeAttrs, 'ur/content_type_attrs'
30
+ autoload :ContentType, 'ur/content_type'
61
31
 
62
32
  class << self
63
- def from_rack_request(request_env)
33
+ def new(instance = {}, schemas: Set[], **options)
34
+ unless instance.respond_to?(:to_hash)
35
+ raise(TypeError, "expected hash for ur instance. got: #{instance.pretty_inspect.chomp}")
36
+ end
37
+
38
+ JSI::SchemaSet[schema, *schemas].new_jsi(instance, **options).tap do |ur|
39
+ ur.request = {} if ur.request.nil?
40
+ ur.response = {} if ur.response.nil?
41
+ ur.metadata = {} if ur.metadata.nil?
42
+ end
43
+ end
44
+
45
+ def from_rack_request(request_env, **options)
64
46
  if request_env.is_a?(Rack::Request)
65
47
  rack_request = request_env
66
48
  env = request_env.env
@@ -69,9 +51,7 @@ class Ur
69
51
  env = request_env
70
52
  end
71
53
 
72
- new({'bound' => 'inbound'}).tap do |ur|
73
- ur.processing.begin!
74
-
54
+ new({'bound' => 'inbound'}, options).tap do |ur|
75
55
  ur.request['method'] = rack_request.request_method
76
56
 
77
57
  ur.request.addressable_uri = Addressable::URI.new(
@@ -101,9 +81,8 @@ class Ur
101
81
  end
102
82
  end
103
83
 
104
- def from_faraday_request(request_env, logger: nil)
105
- new({'bound' => 'outbound'}).tap do |ur|
106
- ur.processing.begin!
84
+ def from_faraday_request(request_env, **options)
85
+ new({'bound' => 'outbound'}, options).tap do |ur|
107
86
  ur.request['method'] = request_env[:method].to_s
108
87
  ur.request.uri = request_env[:url].normalize.to_s
109
88
  ur.request.headers = request_env[:request_headers]
@@ -112,19 +91,13 @@ class Ur
112
91
  end
113
92
  end
114
93
 
115
- def initialize(ur = {}, **opt, &b)
116
- super(ur, **opt, &b)
117
- unless instance.respond_to?(:to_hash)
118
- raise(TypeError, "expected hash argument. got: #{ur.pretty_inspect.chomp}")
119
- end
120
- self.request = {} if self.request.nil?
121
- self.response = {} if self.response.nil?
122
- self.processing = {} if self.processing.nil?
123
- end
124
-
125
- def logger=(logger)
94
+ # Ur#logger_tags applies tags from a tagged logger to this ur's metadata.
95
+ # note: ur does not log anything and this logger is not stored.
96
+ # @param [logger] a tagged logger
97
+ # @return [void]
98
+ def logger_tags(logger)
126
99
  if logger && logger.formatter.respond_to?(:current_tags)
127
- processing.tags = logger.formatter.current_tags.dup
100
+ metadata.tags = logger.formatter.current_tags.dup
128
101
  end
129
102
  end
130
103
 
@@ -136,8 +109,6 @@ class Ur
136
109
  response.body = response_body.to_enum.to_a.join('')
137
110
 
138
111
  response_body_proxy = ::Rack::BodyProxy.new(response_body) do
139
- processing.finish!
140
-
141
112
  yield
142
113
  end
143
114
  [status, response_headers, response_body_proxy]
@@ -148,7 +119,6 @@ class Ur
148
119
  response.status = response_env[:status]
149
120
  response.headers = response_env[:response_headers]
150
121
  response.set_body_from_faraday(response_env)
151
- processing.finish!
152
122
 
153
123
  yield(response_env)
154
124
  end
@@ -0,0 +1,51 @@
1
+ $schema: http://json-schema.org/draft-07/schema
2
+ $id: https://schemas.ur.unth.net/ur
3
+ type: object
4
+ properties:
5
+ bound:
6
+ type: string
7
+ description: '[RFC7230] The terms "inbound" and "outbound" are used to describe directional requirements in relation to the request route: "inbound" means toward the origin server and "outbound" means toward the user agent.'
8
+ enum:
9
+ - inbound
10
+ - outbound
11
+ request:
12
+ type: object
13
+ properties:
14
+ method:
15
+ type: string
16
+ description: '[RFC7230] The method token indicates the request method to be performed on the target resource.'
17
+ example: POST
18
+ uri:
19
+ type: string
20
+ format: uri
21
+ example: https://example.com/foo?bar=baz
22
+ headers:
23
+ type: object
24
+ body:
25
+ type: string
26
+ response:
27
+ type: object
28
+ properties:
29
+ status:
30
+ type: integer
31
+ description: >
32
+ The status-code element is a 3-digit integer code describing the
33
+ result of the server's attempt to understand and satisfy the client's
34
+ corresponding request.
35
+ example: 200
36
+ headers:
37
+ type: object
38
+ body:
39
+ type: string
40
+ metadata:
41
+ type: object
42
+ properties:
43
+ began_at_s:
44
+ type: string
45
+ format: date-time
46
+ duration:
47
+ type: number
48
+ tags:
49
+ type: array
50
+ items:
51
+ type: string
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ur
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ethan
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-27 00:00:00.000000000 Z
11
+ date: 2022-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jsi
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.2.0
19
+ version: 0.6.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.2.0
26
+ version: 0.6.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: addressable
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -38,168 +38,34 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '2.0'
41
- - !ruby/object:Gem::Dependency
42
- name: rack
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: rack-test
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: faraday
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: faraday_middleware
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
- - !ruby/object:Gem::Dependency
98
- name: activesupport
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '0'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
- - !ruby/object:Gem::Dependency
112
- name: rake
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: '10.0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: '10.0'
125
- - !ruby/object:Gem::Dependency
126
- name: minitest
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - "~>"
130
- - !ruby/object:Gem::Version
131
- version: '5.0'
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - "~>"
137
- - !ruby/object:Gem::Version
138
- version: '5.0'
139
- - !ruby/object:Gem::Dependency
140
- name: minitest-reporters
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - ">="
144
- - !ruby/object:Gem::Version
145
- version: '0'
146
- type: :development
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - ">="
151
- - !ruby/object:Gem::Version
152
- version: '0'
153
- - !ruby/object:Gem::Dependency
154
- name: simplecov
155
- requirement: !ruby/object:Gem::Requirement
156
- requirements:
157
- - - ">="
158
- - !ruby/object:Gem::Version
159
- version: '0'
160
- type: :development
161
- prerelease: false
162
- version_requirements: !ruby/object:Gem::Requirement
163
- requirements:
164
- - - ">="
165
- - !ruby/object:Gem::Version
166
- version: '0'
167
41
  description: ur provides a unified representation of a request and response. it can
168
42
  be interpreted from rack, faraday, or potentially other sources, and provides a
169
43
  consistent interface to access the attributes inherent to the request and additional
170
44
  useful parsers and computation from the request.
171
45
  email:
172
- - ethan@unth
46
+ - ethan.ur@unth.net
173
47
  executables: []
174
48
  extensions: []
175
49
  extra_rdoc_files: []
176
50
  files:
177
- - ".simplecov"
178
- - ".yardopts"
179
51
  - CHANGELOG.md
180
- - LICENSE.txt
52
+ - LICENSE.md
181
53
  - README.md
182
- - Rakefile.rb
183
54
  - lib/ur.rb
184
- - lib/ur/content_type_attrs.rb
55
+ - lib/ur/content_type.rb
185
56
  - lib/ur/faraday.rb
186
57
  - lib/ur/faraday/yield_ur.rb
58
+ - lib/ur/metadata.rb
187
59
  - lib/ur/middleware.rb
188
- - lib/ur/processing.rb
189
60
  - lib/ur/request.rb
190
61
  - lib/ur/request_and_response.rb
191
62
  - lib/ur/response.rb
192
63
  - lib/ur/sub_ur.rb
193
64
  - lib/ur/version.rb
194
- - test/test_helper.rb
195
- - test/ur_faraday_test.rb
196
- - test/ur_processing_test.rb
197
- - test/ur_rack_test.rb
198
- - test/ur_test.rb
199
- - ur.gemspec
65
+ - resources/ur.schema.yml
200
66
  homepage: https://github.com/notEthan/ur
201
67
  licenses:
202
- - MIT
68
+ - LGPL-3.0
203
69
  metadata: {}
204
70
  post_install_message:
205
71
  rdoc_options: []
@@ -216,15 +82,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
216
82
  - !ruby/object:Gem::Version
217
83
  version: '0'
218
84
  requirements: []
219
- rubyforge_project:
220
- rubygems_version: 2.7.8
85
+ rubygems_version: 3.1.2
221
86
  signing_key:
222
87
  specification_version: 4
223
88
  summary: 'ur: unified request representation'
224
- test_files:
225
- - test/test_helper.rb
226
- - test/ur_faraday_test.rb
227
- - test/ur_processing_test.rb
228
- - test/ur_rack_test.rb
229
- - test/ur_test.rb
230
- - ".simplecov"
89
+ test_files: []
data/.simplecov DELETED
@@ -1 +0,0 @@
1
- SimpleCov.start
data/.yardopts DELETED
@@ -1 +0,0 @@
1
- --main README.md --markup=markdown {lib}/**/*.rb
data/LICENSE.txt DELETED
@@ -1,21 +0,0 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2018 Ethan
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
data/Rakefile.rb DELETED
@@ -1,9 +0,0 @@
1
- require "rake/testtask"
2
-
3
- Rake::TestTask.new(:test) do |t|
4
- t.libs << "test"
5
- t.libs << "lib"
6
- t.test_files = FileList["test/**/*_test.rb"]
7
- end
8
-
9
- task :default => :test
@@ -1,83 +0,0 @@
1
- require 'ur' unless Object.const_defined?(:Ur)
2
-
3
- class Ur
4
- # parses attributes out of content type header
5
- class ContentTypeAttrs
6
- def initialize(content_type)
7
- raise(ArgumentError) unless content_type.nil? || content_type.respond_to?(:to_str)
8
- content_type = content_type.to_str if content_type
9
- @media_type = (content_type.split(/\s*[;]\s*/, 2).first if content_type)
10
- @media_type.strip! if @media_type
11
- @content_type = content_type
12
- @parsed = false
13
- @attributes = Hash.new { |h,k| h[k] = [] }
14
- catch(:unparseable) do
15
- throw(:unparseable) unless content_type
16
- uri_parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI
17
- scanner = StringScanner.new(content_type)
18
- scanner.scan(/.*;\s*/) || throw(:unparseable)
19
- while scanner.scan(/(\w+)=("?)([^"]*)("?)\s*(,?)\s*/)
20
- key = scanner[1]
21
- quote1 = scanner[2]
22
- value = scanner[3]
23
- quote2 = scanner[4]
24
- comma_follows = !scanner[5].empty?
25
- throw(:unparseable) unless quote1 == quote2
26
- throw(:unparseable) if !comma_follows && !scanner.eos?
27
- @attributes[uri_parser.unescape(key)] << uri_parser.unescape(value)
28
- end
29
- throw(:unparseable) unless scanner.eos?
30
- @parsed = true
31
- end
32
- end
33
-
34
- attr_reader :content_type, :media_type
35
-
36
- def parsed?
37
- @parsed
38
- end
39
-
40
- def [](key)
41
- @attributes[key]
42
- end
43
-
44
- def text?
45
- # ordered hash by priority mapping types to binary or text
46
- # regexps will have \A and \z added
47
- types = {
48
- %r(image/.*) => :binary,
49
- %r(audio/.*) => :binary,
50
- %r(video/.*) => :binary,
51
- %r(model/.*) => :binary,
52
- %r(text/.*) => :text,
53
- %r(message/.*) => :text,
54
- %r(application/(.+\+)?json) => :text,
55
- %r(application/(.+\+)?xml) => :text,
56
- %r(model/(.+\+)?xml) => :text,
57
- 'application/x-www-form-urlencoded' => :text,
58
- 'application/javascript' => :text,
59
- 'application/ecmascript' => :text,
60
- 'application/octet-stream' => :binary,
61
- 'application/ogg' => :binary,
62
- 'application/pdf' => :binary,
63
- 'application/postscript' => :binary,
64
- 'application/zip' => :binary,
65
- 'application/gzip' => :binary,
66
- 'application/vnd.apple.pkpass' => :binary,
67
- }
68
- types.each do |match, type|
69
- matched = match.is_a?(Regexp) ? media_type =~ %r(\A#{match.source}\z) : media_type == match
70
- if matched
71
- return type == :text
72
- end
73
- end
74
- # fallback (unknown or not given) assume that unknown content types are binary but omitted
75
- # content-type means text
76
- if content_type && content_type =~ /\S/
77
- return false
78
- else
79
- return true
80
- end
81
- end
82
- end
83
- end
data/test/test_helper.rb DELETED
@@ -1,22 +0,0 @@
1
- proc { |p| $:.unshift(p) unless $:.any? { |lp| File.expand_path(lp) == p } }.call(File.expand_path('../lib', File.dirname(__FILE__)))
2
-
3
- require 'simplecov'
4
- require 'byebug'
5
-
6
- # NO EXPECTATIONS
7
- ENV["MT_NO_EXPECTATIONS"] = ''
8
-
9
- require 'minitest/autorun'
10
- require 'minitest/reporters'
11
- Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
12
-
13
- class UrSpec < Minitest::Spec
14
- def assert_json_equal(exp, act, *a)
15
- assert_equal(JSI::Typelike.as_json(exp), JSI::Typelike.as_json(act), *a)
16
- end
17
- end
18
-
19
- # register this to be the base class for specs instead of Minitest::Spec
20
- Minitest::Spec.register_spec_type(//, UrSpec)
21
-
22
- require 'ur'