brine-dsl 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.