brine-dsl 0.9.0 → 0.10.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.
@@ -0,0 +1,171 @@
1
+ ##
2
+ # @file requesting.rb
3
+ # Request construction and response storage.
4
+ ##
5
+
6
+ module Brine
7
+
8
+ ##
9
+ # Module in charge of constructing requests and saving responses.
10
+ ##
11
+ module Requesting
12
+ require 'faraday_middleware'
13
+ require 'brine/client_building'
14
+
15
+ ##
16
+ # The root url to which Brine will send requests.
17
+ #
18
+ # This will normally be the value of ENV['BRINE_ROOT_URL'],
19
+ # and that value should be directly usable after older
20
+ # ENV['ROOT_URL'] is end-of-lifed (at which point this can be removed).
21
+ #
22
+ # @brine_root_url is used if set to allow setting this value more programmatically.
23
+ #
24
+ # @return [String] The root URL to use or nil if none is provided.
25
+ ##
26
+ def brine_root_url
27
+ if @brine_root_url
28
+ @brine_root_url
29
+ elsif ENV['BRINE_ROOT_URL']
30
+ ENV['BRINE_ROOT_URL']
31
+ elsif ENV['ROOT_URL']
32
+ deprecation_message('1.0', 'ROOT_URL is deprecated, replace with BRINE_ROOT_URL') if ENV['ROOT_URL']
33
+ ENV['ROOT_URL']
34
+ end
35
+ end
36
+
37
+ ##
38
+ # Normalize an HTTP method for the HTTP client library (to a lowercased symbol).
39
+ #
40
+ # @param [String] method A text representation of the HTTP method.
41
+ # @return [Symbol] A representation of `method` usable by the HTTP client library.
42
+ ##
43
+ def parse_method(method)
44
+ method.downcase.to_sym
45
+ end
46
+
47
+ ##
48
+ # Set the client which will be used to send HTTP requests.
49
+ #
50
+ # This will generally be a connection as created by the ClientBuilding module.
51
+ #
52
+ # @param [Faraday::Connection, #run_request] client The client which will be used to issue HTTP requests.
53
+ ##
54
+ def set_client(client)
55
+ @client = client
56
+ end
57
+
58
+ ##
59
+ # The currently active client which will be used to issue HTTP calls.
60
+ #
61
+ # This will be initialized as neded on first access
62
+ # to a default client constructed by the ClientBuilding module.
63
+ #
64
+ # @return [Faraday::Connection, #run_request] The currently active client object.
65
+ ##
66
+ def client
67
+ @client ||= client_for_host(brine_root_url)
68
+ end
69
+
70
+ ##
71
+ # Clear any previously built request state and set defaults.
72
+ #
73
+ # This should be called upon request completion or when constructing a new
74
+ # request so no extant state inadvertently pollutes the new construction.
75
+ ##
76
+ def reset_request
77
+ @params = @headers = @body = nil
78
+ end
79
+
80
+ ##
81
+ # Store the provided body in the request being built.
82
+ #
83
+ # This will override any previous body value.
84
+ #
85
+ # @param [Object] The new data to be placed in the request body.
86
+ ##
87
+ def set_request_body(obj)
88
+ @body = obj
89
+ end
90
+
91
+ ##
92
+ # Send a `method` request to `url` including any current builder options and store response.
93
+ #
94
+ # The response will be available as the `#response` property.
95
+ #
96
+ # For requests such as simple GETs that only require a url this method may
97
+ # be self-contained; for more complex requests the values collected through
98
+ # request building are likely required. Any data present from the builder
99
+ # methods will always be inclued in the request and therefore such data should
100
+ # be cleared using `#reset_request` if it is not desired.
101
+ #
102
+ # @param [Symbol] method The client friendly representation of the HTTP method for the request
103
+ # (@see #parse_method).
104
+ # @param [String] url The url to which the request will be sent.
105
+ ##
106
+ def send_request(method, url)
107
+ @response = client.run_request(method, url, @body, headers) do |req|
108
+ req.params = request_params
109
+ end
110
+ end
111
+
112
+ ##
113
+ # The response for the last sent request.
114
+ #
115
+ # @return [Faraday::Response] The most recent response.
116
+ ##
117
+ def response
118
+ @response
119
+ end
120
+
121
+ ##
122
+ # The headers for the request currently being built.
123
+ #
124
+ # Will be initialized as needed on first access,
125
+ # with a default specifying JSON content-type.
126
+ #
127
+ # @return [Hash] The headers to use for the constructed request.
128
+ ##
129
+ def headers
130
+ @headers ||= {content_type: 'application/json'}
131
+ end
132
+
133
+ ##
134
+ # Set the specified header to the provided value.
135
+ #
136
+ # @param [String] k The name of the header whose value will be set.
137
+ # @param [Object] v The value to set for the specified header.
138
+ # This should normally be a String, but some other types may work.
139
+ ##
140
+ def set_header(k, v)
141
+ headers[k] = v
142
+ end
143
+
144
+ ##
145
+ # The query parameters which will be attached to the constructeed request.
146
+ #
147
+ # Will be initialized to an empty hash as needed upon first access.
148
+ #
149
+ # @return [Hash] The query parameters to use for request construction.
150
+ ##
151
+ def request_params
152
+ @request_params ||= {}
153
+ end
154
+
155
+ ##
156
+ # Assign the provided value to the specified request query parameter.
157
+ #
158
+ # @param [String] k The name of the query parameter whose value is being assigned.
159
+ # @param [Object] v The value to assign the query parameter (normally a String).
160
+ ##
161
+ def set_request_param(k, v)
162
+ request_params[k] = v
163
+ end
164
+
165
+ end
166
+
167
+ ##
168
+ # Mix the Requesting module functionality into the main Brine module.
169
+ ##
170
+ include Requesting
171
+ end
@@ -1,6 +1,6 @@
1
1
  require 'rspec'
2
2
  require 'brine/util'
3
- require 'brine/selector'
3
+ require 'brine/selecting'
4
4
  require 'jsonpath'
5
5
 
6
6
  # Chopping Block
@@ -0,0 +1,166 @@
1
+ ##
2
+ # @file selecting.rb
3
+ # Selection of one or more values to be used by Brine (normally for assertion).
4
+ ##
5
+
6
+ module Brine
7
+
8
+ ##
9
+ # A module providing assorted means to select particular values out of objects/graphs.
10
+ ##
11
+ module Selecting
12
+
13
+ require 'brine/coercing'
14
+ require 'rspec/expectations'
15
+ require 'jsonpath'
16
+
17
+ ##
18
+ # An object responsible for selecting one or more values.
19
+ # This Selector will test whether the targeted value itself satisfies the assertion.
20
+ #
21
+ # RSpec is used within this implementation to perform assertions.
22
+ # The Selector ultimately perform this assertion by accepting an RSpec matcher
23
+ # which it applied against the targeted value.
24
+ ##
25
+ class Selector
26
+ include RSpec::Matchers
27
+
28
+ ##
29
+ # [Coercer] The Coercer that may modify values prior to performing assertions.
30
+ ##
31
+ attr_accessor :coercer
32
+
33
+ ##
34
+ # Construct a selector to perform assertions against a provided target.
35
+ #
36
+ # @param [Object] taret The value against which assertions will be performed.
37
+ # @param [Boolean] negated Whether the assertions from this selector should be negated.
38
+ # This is deprecated and should instead be passed to #assert_that.
39
+ ##
40
+ def initialize(target, negated=false)
41
+ @target = target
42
+ @negated = negated
43
+ end
44
+
45
+ ##
46
+ # Optionally perform some modification to the RSpec matcher prior to assertion.
47
+ #
48
+ # This is designed to allow subclassess to be able to modify the way
49
+ # in which matchers are applied against the values. This method is a pass-through in this class.
50
+ #
51
+ # @param [RSpec::Matcher] matcher The matcher originally passed to #assert_that.
52
+ # @return [RSpec::Matcher] The Matcher to be used while performing the assertion.
53
+ ##
54
+ def filter_matcher(matcher)
55
+ matcher
56
+ end
57
+
58
+ ##
59
+ # Perform the provided assertion against the instance target.
60
+ #
61
+ # The values will be coerced prior to evaluation.
62
+ #
63
+ # @param [Object] value The value/parameter against which the target will be compared.
64
+ # In cases where no parameter is needed for comparison, this may be nil.
65
+ # @param [Boolean] negated If true the assertion should be expected to _fail_, otherwise it should pass.
66
+ # @param [Block] A block which will be passed a coerced copy of `value` and which should return an
67
+ # RSpec matcher which will be evaluated against the coerced target value.
68
+ ##
69
+ def assert_that(value, negated=nil)
70
+ # shim while moving negation to assertions.
71
+ negated = @negated if negated.nil?
72
+ target, value = coercer.coerce(@target, value)
73
+ message = negated ? :to_not : :to
74
+ matcher = filter_matcher(yield(value))
75
+ expect(target).send(message, matcher)
76
+ end
77
+
78
+ end
79
+
80
+ ##
81
+ # A Selector which will test whether any of the children of the targeted value satisfy the assertion.
82
+ ##
83
+ class AnySelector < Selector
84
+ def filter_matcher(matcher)
85
+ include(matcher)
86
+ end
87
+ end
88
+
89
+ ##
90
+ # A Selector which will test whether all of the children of the targeted value satisfy the assertion.
91
+ ##
92
+ class AllSelector < Selector
93
+ def filter_matcher(matcher)
94
+ all(matcher)
95
+ end
96
+ end
97
+
98
+ ##
99
+ # Activate a Selector for the provided target.
100
+ #
101
+ # @param [Object] target The value which the Selector should target.
102
+ # @param [Boolean] negated DEPRECATED If true the assertions should be expected to fail.
103
+ ##
104
+ def select(target, negated=nil)
105
+ use_selector(Selector.new(target, negated))
106
+ end
107
+
108
+ ##
109
+ # Activate a Selector for any of the children of the provided target.
110
+ #
111
+ # @param [Object] target The value which the Selector should target.
112
+ # @param [Boolean] negated DEPRECATED If true the assertions should be expected to fail.
113
+ ##
114
+ def select_any(target, negated=nil)
115
+ use_selector(AnySelector.new(target, negated))
116
+ end
117
+
118
+ ##
119
+ # Activate a Selector for all of the children of the provided target.
120
+ #
121
+ # @param [Object] target The value which the Selector should target.
122
+ # @param [Boolean] negated DEPRECATED If true the assertions should be expected to fail.
123
+ ##
124
+ def select_all(target, negated=nil)
125
+ use_selector(AllSelector.new(target, negated))
126
+ end
127
+
128
+ ##
129
+ # Configure selector and make it the active Selector.
130
+ #
131
+ # @param [Selector] selector The selector which will be activated.
132
+ ##
133
+ def use_selector(selector)
134
+ selector.coercer = coercer
135
+ @selector = selector
136
+ end
137
+
138
+ ##
139
+ # The currently active Selector.
140
+ #
141
+ # @return The Selector as set by #use_selector
142
+ ##
143
+ def selector
144
+ @selector
145
+ end
146
+
147
+ end
148
+
149
+ # Mix the Selecting module functionality into the main Brine module.
150
+ include Selecting
151
+ end
152
+
153
+ #
154
+ # Steps
155
+ #
156
+ RESPONSE_ATTRIBUTES='(status|headers|body)'
157
+ Then(/^the value of `([^`]*)` is( not)? (.*)$/) do |value, negated, assertion|
158
+ select(value, (!negated.nil?))
159
+ step "it is #{assertion}"
160
+ end
161
+
162
+ def dig_from_response(attribute, path=nil, plural=false)
163
+ root = response.send(attribute.to_sym)
164
+ return root if !path
165
+ JsonPath.new("$.#{path}").send(plural ? :on : :first, root)
166
+ end
@@ -1,50 +1,50 @@
1
1
  # assertions.rb - General assertions to be used with a Selector
2
2
 
3
3
  Then(/^it is equal to `([^`]*)`$/) do |value|
4
- selector.assert_that(value) {|v| eq v}
4
+ perform { selector.assert_that(value) {|v| eq v} }
5
5
  end
6
6
  Then(/^it is equal to:$/) do |value|
7
- selector.assert_that(value) {|v| eq v}
7
+ perform { selector.assert_that(value) {|v| eq v} }
8
8
  end
9
9
  Then(/^it is matching `([^`]*)`$/) do |value|
10
- selector.assert_that(value) {|v| match v}
10
+ perform { selector.assert_that(value) {|v| match v} }
11
11
  end
12
12
  Then (/^it is matching:$/) do |value|
13
- selector.assert_that(value) {|v| match v}
13
+ perform { selector.assert_that(value) {|v| match v} }
14
14
  end
15
15
  Then(/^it is greater than `([^`]*)`$/) do |value|
16
- selector.assert_that(value) {|v| be > v}
16
+ perform { selector.assert_that(value) {|v| be > v} }
17
17
  end
18
18
  Then(/^it is greater than or equal to `([^`]*)`$/) do |value|
19
- selector.assert_that(value) {|v| be >= v}
19
+ perform { selector.assert_that(value) {|v| be >= v} }
20
20
  end
21
21
  Then(/^it is less than `([^`]*)`$/) do |value|
22
- selector.assert_that(value) {|v| be < v}
22
+ perform { selector.assert_that(value) {|v| be < v} }
23
23
  end
24
24
  Then(/^it is less than or equal to `([^`]*)`$/) do |value|
25
- selector.assert_that(value) {|v| be <= v}
25
+ perform { selector.assert_that(value) {|v| be <= v} }
26
26
  end
27
27
 
28
28
  # Be a little smarter than default.
29
29
  Then(/^it is empty$/) do
30
- selector.assert_that(nil) do
30
+ perform { selector.assert_that(nil) do
31
31
  satisfy{|i| i.nil? || (i.respond_to?(:empty?) && i.empty?) }
32
- end
32
+ end }
33
33
  end
34
34
 
35
35
  Then(/^it is including `([^`]*)`$/) do |value|
36
- selector.assert_that(value) {|v| include v }
36
+ perform { selector.assert_that(value) {|v| include v } }
37
37
  end
38
38
  Then(/^it is including:$/) do |value|
39
- selector.assert_that(value) {|v| include v }
39
+ perform { selector.assert_that(value) {|v| include v } }
40
40
  end
41
41
 
42
42
  Then(/^it is a valid `([^`]*)`$/) do |type|
43
- selector.assert_that(type) {|t| type_check_for(t) }
43
+ perform { selector.assert_that(type) {|t| type_check_for(t) } }
44
44
  end
45
45
 
46
46
  Then(/^it is of length `([^`]*)`$/) do |length|
47
- selector.assert_that(length) do |l|
47
+ perform { selector.assert_that(length) do |l|
48
48
  satisfy{|i| i.respond_to?(:length) && i.length == l}
49
- end
49
+ end }
50
50
  end
@@ -1,6 +1,7 @@
1
1
  ##
2
2
  # @file assignment.rb
3
3
  # Assignment related steps.
4
+ ##
4
5
 
5
6
  ##
6
7
  # Assign the provided parameter.
@@ -8,7 +9,7 @@
8
9
  # @param name - The identifier to which the value will be bound.
9
10
  # @param value - The value to bind to the identifier.
10
11
  When(/^`([^`]*)` is assigned `([^`]*)`$/) do |name, value|
11
- bind(name, value)
12
+ perform { bind(name, value) }
12
13
  end
13
14
 
14
15
  ##
@@ -16,7 +17,7 @@ end
16
17
  #
17
18
  # @param name - The identifier to which a random string will be bound.
18
19
  When(/^`([^`]*)` is assigned a random string$/) do |name|
19
- bind(name, SecureRandom.uuid)
20
+ perform { bind(name, SecureRandom.uuid) }
20
21
  end
21
22
 
22
23
  ##
@@ -24,7 +25,7 @@ end
24
25
  #
25
26
  # @param name - The identifier to which the current timestamp will be bound.
26
27
  When(/^`([^`]*)` is assigned a timestamp$/) do |name|
27
- bind(name, DateTime.now)
28
+ perform { bind(name, DateTime.now) }
28
29
  end
29
30
 
30
31
  ##
@@ -37,5 +38,5 @@ end
37
38
  # whether to extract a single match or a collection of all matching.
38
39
  When(/^`([^`]*)` is assigned the response #{RESPONSE_ATTRIBUTES}(?: child(ren)? `([^`]*)`)?$/) do
39
40
  |name, attribute, plural, path|
40
- bind(name, dig_from_response(attribute, path, !plural.nil?))
41
+ perform { bind(name, dig_from_response(attribute, path, !plural.nil?)) }
41
42
  end
@@ -1,4 +1,4 @@
1
1
  # cleanup.rb - Track information needed for Resource Cleanup
2
2
  When(/^a resource is created at `([^`]*)`$/) do |path|
3
- track_created_resource path
4
- end
3
+ perform { track_created_resource path }
4
+ end
@@ -0,0 +1,16 @@
1
+ ##
2
+ # @file perform.rb
3
+ # Steps related to performing actions
4
+ ##
5
+
6
+ When /^actions are defined such that$/ do
7
+ collect_actions
8
+ end
9
+
10
+ Then /^the actions are( not)? successful within a `([^`]*)` period$/ do |negated, period|
11
+ method = negated ? :to : :to_not
12
+ expect { poll_for(retrieve_duration(period)) {
13
+ performer.evaluate
14
+ } }.send(method, raise_error)
15
+ reset_performer
16
+ end
@@ -1,24 +1,28 @@
1
1
  # request_construction.rb - Build and send requests
2
2
 
3
3
  When(/^the request body is assigned:$/) do |input|
4
- set_request_body(input)
4
+ perform { set_request_body(input) }
5
5
  end
6
6
 
7
7
  When(/^the request query parameter `([^`]*)` is assigned `([^`]*)`$/) do |param, value|
8
- add_request_param(param, value)
8
+ perform { set_request_param(param, value) }
9
9
  end
10
10
 
11
11
  When(/^the request header `([^`]*)` is assigned `([^`]*)`$/) do |header, value|
12
- add_header(header, value)
12
+ perform { set_header(header, value) }
13
13
  end
14
14
 
15
15
  When(/^the request credentials are set for basic auth user `([^`]*)` and password `([^`]*)`$/) do |user, password|
16
- base64_combined = Base64.strict_encode64("#{user}:#{password}")
17
- add_header('Authorization', "Basic #{base64_combined}")
16
+ perform do
17
+ base64_combined = Base64.strict_encode64("#{user}:#{password}")
18
+ set_header('Authorization', "Basic #{base64_combined}")
19
+ end
18
20
  end
19
21
 
20
22
  When(/^an? (GET|POST|PATCH|PUT|DELETE|HEAD|OPTIONS) is sent to `([^`]*)`$/) do |method, url|
21
- send_request(parse_method(method), URI.escape(url))
22
- bind('response', response)
23
- reset_request
23
+ perform do
24
+ send_request(parse_method(method), URI.escape(url))
25
+ bind('response', response)
26
+ reset_request
27
+ end
24
28
  end
@@ -1,37 +1,49 @@
1
1
  #RESPONSE_ATTRIBUTES='(status|headers|body)'
2
2
  Then(/^the value of the response #{RESPONSE_ATTRIBUTES}(?: child(ren)? `([^`]*)`)? is( not)? (.*)(?<!:)$/) do
3
3
  |attribute, plural, path, negated, assertion|
4
- use_selector(Selector.new(dig_from_response(attribute, path, !plural.nil?), (!negated.nil?)))
5
- step "it is #{assertion}"
4
+ perform do
5
+ select(dig_from_response(attribute, path, !plural.nil?), (!negated.nil?))
6
+ step "it is #{assertion}"
7
+ end
6
8
  end
7
9
 
8
10
  Then(/^the value of the response #{RESPONSE_ATTRIBUTES}(?: child(ren)? `([^`]*)`)? is( not)? (.*)(?<=:)$/) do
9
11
  |attribute, plural, path, negated, assertion, multi|
10
- use_selector(Selector.new(dig_from_response(attribute, path, !plural.nil?), (!negated.nil?)))
11
- step "it is #{assertion}", multi.to_json
12
+ perform do
13
+ select(dig_from_response(attribute, path, !plural.nil?), (!negated.nil?))
14
+ step "it is #{assertion}", multi.to_json
15
+ end
12
16
  end
13
17
 
14
18
  Then(/^the value of the response #{RESPONSE_ATTRIBUTES}(?: child(ren)? `([^`]*)`)? does( not)? have any element that is (.*)(?<!:)$/) do
15
19
  |attribute, plural, path, negated, assertion|
16
- use_selector(AnySelector.new(dig_from_response(attribute, path, !plural.nil?), (!negated.nil?)))
17
- step "it is #{assertion}"
20
+ perform do
21
+ select_any(dig_from_response(attribute, path, !plural.nil?), (!negated.nil?))
22
+ step "it is #{assertion}"
23
+ end
18
24
  end
19
25
 
20
26
  Then(/^the value of the response #{RESPONSE_ATTRIBUTES}(?: child(ren)? `([^`]*)`)? does( not)? have any element that is (.*)(?<=:)$/) do
21
27
  |attribute, plural, path, negated, assertion, multi|
22
- use_selector(AnySelector.new(dig_from_response(attribute, path, !plural.nil?), (!negated.nil?)))
23
- step "it is #{assertion}", multi.to_json
28
+ perform do
29
+ select_any(dig_from_response(attribute, path, !plural.nil?), (!negated.nil?))
30
+ step "it is #{assertion}", multi.to_json
31
+ end
24
32
  end
25
33
 
26
34
  #Would be negated with `not all' which would be equivalent to any(not ) but that's not readily supported
27
35
  Then(/^the value of the response #{RESPONSE_ATTRIBUTES}(?: child(ren)? `([^`]*)`)? has elements which are all (.*)(?<!:)$/) do
28
36
  |attribute, plural, path, assertion|
29
- use_selector(AllSelector.new(dig_from_response(attribute, path, !plural.nil?), false))
30
- step "it is #{assertion}"
37
+ perform do
38
+ select_all(dig_from_response(attribute, path, !plural.nil?), false)
39
+ step "it is #{assertion}"
40
+ end
31
41
  end
32
42
 
33
43
  Then(/^the value of the response #{RESPONSE_ATTRIBUTES}(?: child(ren)? `([^`]*)`)? has elements which are all (.*)(?<=:)$/) do
34
44
  |attribute, plural, path, assertion, multi|
35
- use_selector(AllSelector.new(dig_from_response(attribute, path, !plural.nil?), false))
36
- step "it is #{assertion}", multi.to_json
45
+ perform do
46
+ select_all(dig_from_response(attribute, path, !plural.nil?), false)
47
+ step "it is #{assertion}", multi.to_json
48
+ end
37
49
  end
@@ -5,6 +5,9 @@ require 'rspec'
5
5
  #
6
6
  HTTP_METHOD='GET|POST|PATCH|PUT|DELETE|HEAD|OPTIONS'
7
7
 
8
+ ENV['BRINE_DURATION_SECONDS_short'] = '3'
9
+ ENV['BRINE_DURATION_SECONDS_long'] = '6'
10
+
8
11
  class StubResponse
9
12
  attr_accessor :body, :status, :headers
10
13
 
@@ -15,6 +18,19 @@ class StubResponse
15
18
  end
16
19
  end
17
20
 
21
+ class DelayedStubResponse < StubResponse
22
+
23
+ def initialize(delay)
24
+ super()
25
+ @activation = Time.now + delay
26
+ end
27
+
28
+ def body
29
+ Time.now < @activation ? nil : @body
30
+ end
31
+
32
+ end
33
+
18
34
  class StubRequest
19
35
  attr_accessor :method, :path, :headers, :body
20
36
 
@@ -135,6 +151,10 @@ When /^the response status is assigned `([^`]*)`$/ do |status|
135
151
  @response.status = status.to_i # this coercion isn't needed but is a guarantee
136
152
  end
137
153
 
154
+ When /^the response is delayed `([^`]*)` seconds$/ do |seconds|
155
+ @response = DelayedStubResponse.new(seconds)
156
+ end
157
+
138
158
  Then(/^the response body as JSON is:$/) do |text|
139
159
  expect(response.body.to_json).to eq text
140
160
  end
@@ -1,10 +1,14 @@
1
- # = transformers.rb:: Argument Transforms for Brine
1
+ ##
2
+ # @file transformers.rb
3
+ # Argument Transforms for Brine.
2
4
  #
3
5
  # The Transforms that convert provided inputs to support richer
4
6
  # functionaliy than the simple strings which Cucumber provides.
5
7
 
6
- # == Scalar transforms
8
+ ##
9
+ # @defgroup Scalar transforms
7
10
  # Convert inputs into basic Ruby data types which represent a single value
11
+ ##
8
12
 
9
13
  # Integers
10
14
  Transform /\A(-?\d+)\z/ do |number|
@@ -31,8 +35,10 @@ Transform /^#{DATE}T#{TIME}#{MILLIS}#{TZ}$/ do |date|
31
35
  Time.parse(date)
32
36
  end
33
37
 
34
- # == Structure transforms
38
+ ##
39
+ # @defgroup Structure transforms
35
40
  # Converts inputs to general data structures
41
+ ##
36
42
 
37
43
  # Lists
38
44
  Transform /\A\[.*\]\z/m do |input|
@@ -46,8 +52,10 @@ Transform /\A{.*}\z$/m do |input|
46
52
  JSON.parse(input)
47
53
  end
48
54
 
49
- # == Atypical transforms
55
+ ##
56
+ # @defgroup Atypical transforms
50
57
  # Transforms for which data type is not the primary focus
58
+ ##
51
59
 
52
60
  # Whitespace removal transforms
53
61
  # Handle stripping leading and trailing whitespace.