APIMATIC_Caculator 1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 75b13bc1c2210717f70eb8b5f85611fc3c428ae6f065ffb238a3bdc66a0a2ffe
4
+ data.tar.gz: fab38b51828621d0de8b11f40d78adee3696c8f28ba16dca76813c8c1243fe23
5
+ SHA512:
6
+ metadata.gz: af7b36c3384f0172028ba2e710d2839c129ccb9dca6c3edd5fd5751ca21444afeec2e1bc89d25e55d974b5e5e8e7cedb661037256a4e6ad95fbe9236e9f7c1aa
7
+ data.tar.gz: 2806cb73cfea9e0da1f4c55f6e1406a09d82a48d2a104a00c3853ad876405b24ff81b0a244b4224449df07ddca2faa8401012bcf0adefdc27c08210b5575e84f
data/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ License:
2
+ ========
3
+ The MIT License (MIT)
4
+ http://opensource.org/licenses/MIT
5
+
6
+ Copyright (c) 2014 - 2022 APIMATIC Limited
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ of this software and associated documentation files (the "Software"), to deal
10
+ in the Software without restriction, including without limitation the rights
11
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ copies of the Software, and to permit persons to whom the Software is
13
+ furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in
16
+ all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ THE SOFTWARE.
25
+
26
+ Trade Mark:
27
+ ==========
28
+ APIMATIC is a trade mark for APIMATIC Limited
data/README.md ADDED
@@ -0,0 +1,66 @@
1
+
2
+ # Getting Started with APIMATIC Calculator
3
+
4
+ ## Introduction
5
+
6
+ Simple calculator API hosted on APIMATIC
7
+
8
+ ## Install the Package
9
+
10
+ Install the gem from the command line:
11
+
12
+ ```ruby
13
+ gem install APIMATIC_Caculator -v 1.0
14
+ ```
15
+
16
+ Or add the gem to your Gemfile and run `bundle`:
17
+
18
+ ```ruby
19
+ gem 'APIMATIC_Caculator', '1.0'
20
+ ```
21
+
22
+ For additional gem details, see the [RubyGems page for the APIMATIC_Caculator gem](https://rubygems.org/gems/APIMATIC_Caculator/versions/1.0).
23
+
24
+ ## Test the SDK
25
+
26
+ To run the tests, navigate to the root directory of the SDK in your terminal and execute the following command:
27
+
28
+ ```
29
+ rake
30
+ ```
31
+
32
+ ## Initialize the API Client
33
+
34
+ **_Note:_** Documentation for the client can be found [here.](/doc/client.md)
35
+
36
+ The following parameters are configurable for the API Client:
37
+
38
+ | Parameter | Type | Description |
39
+ | --- | --- | --- |
40
+ | `environment` | Environment | The API environment. <br> **Default: `Environment.PRODUCTION`** |
41
+ | `connection` | `Faraday::Connection` | The Faraday connection object passed by the SDK user for making requests |
42
+ | `timeout` | `Float` | The value to use for connection timeout. <br> **Default: 60** |
43
+ | `max_retries` | `Integer` | The number of times to retry an endpoint call if it fails. <br> **Default: 0** |
44
+ | `retry_interval` | `Float` | Pause in seconds between retries. <br> **Default: 1** |
45
+ | `backoff_factor` | `Float` | The amount to multiply each successive retry's interval amount by in order to provide backoff. <br> **Default: 2** |
46
+ | `retry_statuses` | `Array` | A list of HTTP statuses to retry. <br> **Default: [408, 413, 429, 500, 502, 503, 504, 521, 522, 524]** |
47
+ | `retry_methods` | `Array` | A list of HTTP methods to retry. <br> **Default: %i[get put]** |
48
+
49
+ The API client can be initialized as follows:
50
+
51
+ ```ruby
52
+ client = ApimaticCalculator::Client.new(
53
+ environment: Environment::PRODUCTION,
54
+ )
55
+ ```
56
+
57
+ ## List of APIs
58
+
59
+ * [Simple Calculator](/doc/controllers/simple-calculator.md)
60
+
61
+ ## Classes Documentation
62
+
63
+ * [Utility Classes](/doc/utility-classes.md)
64
+ * [HttpResponse](/doc/http-response.md)
65
+ * [HttpRequest](/doc/http-request.md)
66
+
@@ -0,0 +1,277 @@
1
+ # apimatic_calculator
2
+ #
3
+ # This file was automatically generated by APIMATIC v2.0
4
+ # ( https://apimatic.io ).
5
+
6
+ module ApimaticCalculator
7
+ # API utility class
8
+ class APIHelper
9
+ # Serializes an array parameter (creates key value pairs).
10
+ # @param [String] The name of the parameter.
11
+ # @param [Array] The value of the parameter.
12
+ # @param [String] The format of the serialization.
13
+ def self.serialize_array(key, array, formatting: 'indexed')
14
+ tuples = []
15
+
16
+ tuples += case formatting
17
+ when 'csv'
18
+ [[key, array.map { |element| CGI.escape(element.to_s) }.join(',')]]
19
+ when 'psv'
20
+ [[key, array.map { |element| CGI.escape(element.to_s) }.join('|')]]
21
+ when 'tsv'
22
+ [[key, array.map { |element| CGI.escape(element.to_s) }.join("\t")]]
23
+ else
24
+ array.map { |element| [key, element] }
25
+ end
26
+ tuples
27
+ end
28
+
29
+ # Replaces template parameters in the given url.
30
+ # @param [String] The query string builder to replace the template
31
+ # parameters.
32
+ # @param [Hash] The parameters to replace in the url.
33
+ def self.append_url_with_template_parameters(query_builder, parameters)
34
+ # perform parameter validation
35
+ unless query_builder.instance_of? String
36
+ raise ArgumentError, 'Given value for parameter \"query_builder\" is
37
+ invalid.'
38
+ end
39
+
40
+ # Return if there are no parameters to replace.
41
+ return query_builder if parameters.nil?
42
+
43
+ parameters.each do |key, val|
44
+ if val.nil?
45
+ replace_value = ''
46
+ elsif val['value'].instance_of? Array
47
+ if val['encode'] == true
48
+ val['value'].map! { |element| CGI.escape(element.to_s) }
49
+ else
50
+ val['value'].map!(&:to_s)
51
+ end
52
+ replace_value = val['value'].join('/')
53
+ else
54
+ replace_value = if val['encode'] == true
55
+ CGI.escape(val['value'].to_s)
56
+ else
57
+ val['value'].to_s
58
+ end
59
+ end
60
+
61
+ # Find the template parameter and replace it with its value.
62
+ query_builder = query_builder.gsub("{#{key}}", replace_value)
63
+ end
64
+ query_builder
65
+ end
66
+
67
+ # Appends the given set of parameters to the given query string.
68
+ # @param [String] The query string builder to add the query parameters to.
69
+ # @param [Hash] The parameters to append.
70
+ def self.append_url_with_query_parameters(query_builder, parameters)
71
+ # Perform parameter validation.
72
+ unless query_builder.instance_of? String
73
+ raise ArgumentError, 'Given value for parameter \"query_builder\"
74
+ is invalid.'
75
+ end
76
+
77
+ # Return if there are no parameters to replace.
78
+ return query_builder if parameters.nil?
79
+
80
+ array_serialization = 'indexed'
81
+ parameters = process_complex_types_parameters(parameters, array_serialization)
82
+
83
+ parameters.each do |key, value|
84
+ seperator = query_builder.include?('?') ? '&' : '?'
85
+ unless value.nil?
86
+ if value.instance_of? Array
87
+ value.compact!
88
+ APIHelper.serialize_array(
89
+ key, value, formatting: array_serialization
90
+ ).each do |element|
91
+ seperator = query_builder.include?('?') ? '&' : '?'
92
+ query_builder += "#{seperator}#{element[0]}=#{element[1]}"
93
+ end
94
+ else
95
+ query_builder += "#{seperator}#{key}=#{CGI.escape(value.to_s)}"
96
+ end
97
+ end
98
+ end
99
+ query_builder
100
+ end
101
+
102
+ # Validates and processes the given Url.
103
+ # @param [String] The given Url to process.
104
+ # @return [String] Pre-processed Url as string.
105
+ def self.clean_url(url)
106
+ # Perform parameter validation.
107
+ raise ArgumentError, 'Invalid Url.' unless url.instance_of? String
108
+
109
+ # Ensure that the urls are absolute.
110
+ matches = url.match(%r{^(https?://[^/]+)})
111
+ raise ArgumentError, 'Invalid Url format.' if matches.nil?
112
+
113
+ # Get the http protocol match.
114
+ protocol = matches[1]
115
+
116
+ # Check if parameters exist.
117
+ index = url.index('?')
118
+
119
+ # Remove redundant forward slashes.
120
+ query = url[protocol.length...(!index.nil? ? index : url.length)]
121
+ query.gsub!(%r{//+}, '/')
122
+
123
+ # Get the parameters.
124
+ parameters = !index.nil? ? url[url.index('?')...url.length] : ''
125
+
126
+ # Return processed url.
127
+ protocol + query + parameters
128
+ end
129
+
130
+ # Parses JSON string.
131
+ # @param [String] A JSON string.
132
+ def self.json_deserialize(json)
133
+ JSON.parse(json)
134
+ rescue StandardError
135
+ raise TypeError, 'Server responded with invalid JSON.'
136
+ end
137
+
138
+ # Removes elements with empty values from a hash.
139
+ # @param [Hash] The hash to clean.
140
+ def self.clean_hash(hash)
141
+ hash.delete_if { |_key, value| value.to_s.strip.empty? }
142
+ end
143
+
144
+ # Form encodes a hash of parameters.
145
+ # @param [Hash] The hash of parameters to encode.
146
+ # @return [Hash] A hash with the same parameters form encoded.
147
+ def self.form_encode_parameters(form_parameters)
148
+ array_serialization = 'indexed'
149
+ encoded = {}
150
+ form_parameters.each do |key, value|
151
+ encoded.merge!(APIHelper.form_encode(value, key, formatting:
152
+ array_serialization))
153
+ end
154
+ encoded
155
+ end
156
+
157
+ # Process complex types in query_params.
158
+ # @param [Hash] The hash of query parameters.
159
+ # @return [Hash] A hash with the processed query parameters.
160
+ def self.process_complex_types_parameters(query_parameters, array_serialization)
161
+ processed_params = {}
162
+ query_parameters.each do |key, value|
163
+ processed_params.merge!(APIHelper.form_encode(value, key, formatting:
164
+ array_serialization))
165
+ end
166
+ processed_params
167
+ end
168
+
169
+ def self.custom_merge(a, b)
170
+ x = {}
171
+ a.each do |key, value_a|
172
+ b.each do |k, value_b|
173
+ next unless key == k
174
+
175
+ x[k] = []
176
+ if value_a.instance_of? Array
177
+ value_a.each do |v|
178
+ x[k].push(v)
179
+ end
180
+ else
181
+ x[k].push(value_a)
182
+ end
183
+ if value_b.instance_of? Array
184
+ value_b.each do |v|
185
+ x[k].push(v)
186
+ end
187
+ else
188
+ x[k].push(value_b)
189
+ end
190
+ a.delete(k)
191
+ b.delete(k)
192
+ end
193
+ end
194
+ x.merge!(a)
195
+ x.merge!(b)
196
+ x
197
+ end
198
+
199
+ # Form encodes an object.
200
+ # @param [Dynamic] An object to form encode.
201
+ # @param [String] The name of the object.
202
+ # @return [Hash] A form encoded representation of the object in the form
203
+ # of a hash.
204
+ def self.form_encode(obj, instance_name, formatting: 'indexed')
205
+ retval = {}
206
+
207
+ serializable_types = [String, Numeric, TrueClass,
208
+ FalseClass, Date, DateTime]
209
+
210
+ # If this is a structure, resolve it's field names.
211
+ obj = obj.to_hash if obj.is_a? BaseModel
212
+
213
+ # Create a form encoded hash for this object.
214
+ if obj.nil?
215
+ nil
216
+ elsif obj.instance_of? Array
217
+ if formatting == 'indexed'
218
+ obj.each_with_index do |value, index|
219
+ retval.merge!(APIHelper.form_encode(value, "#{instance_name}[#{index}]"))
220
+ end
221
+ elsif serializable_types.map { |x| obj[0].is_a? x }.any?
222
+ obj.each do |value|
223
+ abc = if formatting == 'unindexed'
224
+ APIHelper.form_encode(value, "#{instance_name}[]",
225
+ formatting: formatting)
226
+ else
227
+ APIHelper.form_encode(value, instance_name,
228
+ formatting: formatting)
229
+ end
230
+ retval = APIHelper.custom_merge(retval, abc)
231
+ end
232
+ else
233
+ obj.each_with_index do |value, index|
234
+ retval.merge!(APIHelper.form_encode(value, "#{instance_name}[#{index}]",
235
+ formatting: formatting))
236
+ end
237
+ end
238
+ elsif obj.instance_of? Hash
239
+ obj.each do |key, value|
240
+ retval.merge!(APIHelper.form_encode(value, "#{instance_name}[#{key}]",
241
+ formatting: formatting))
242
+ end
243
+ elsif obj.instance_of? File
244
+ retval[instance_name] = UploadIO.new(
245
+ obj, 'application/octet-stream', File.basename(obj.path)
246
+ )
247
+ else
248
+ retval[instance_name] = obj
249
+ end
250
+ retval
251
+ end
252
+
253
+ # Retrieves a field from a Hash/Array based on an Array of keys/indexes
254
+ # @param [Hash, Array] The hash to extract data from
255
+ # @param [Array<String, Integer>] The keys/indexes to use
256
+ # @return [Object] The extracted value
257
+ def self.map_response(obj, keys)
258
+ val = obj
259
+ begin
260
+ keys.each do |key|
261
+ val = if val.is_a? Array
262
+ if key.to_i.to_s == key
263
+ val[key.to_i]
264
+ else
265
+ val = nil
266
+ end
267
+ else
268
+ val.fetch(key.to_sym)
269
+ end
270
+ end
271
+ rescue NoMethodError, TypeError, IndexError
272
+ val = nil
273
+ end
274
+ val
275
+ end
276
+ end
277
+ end
@@ -0,0 +1,35 @@
1
+ # apimatic_calculator
2
+ #
3
+ # This file was automatically generated by APIMATIC v2.0
4
+ # ( https://apimatic.io ).
5
+
6
+ module ApimaticCalculator
7
+ # apimatic_calculator client class.
8
+ class Client
9
+ attr_reader :config
10
+
11
+ # Access to simple_calculator controller.
12
+ # @return [SimpleCalculatorController] Returns the controller instance.
13
+ def simple_calculator
14
+ @simple_calculator ||= SimpleCalculatorController.new config
15
+ end
16
+
17
+ def initialize(connection: nil, timeout: 60, max_retries: 0,
18
+ retry_interval: 1, backoff_factor: 2,
19
+ retry_statuses: [408, 413, 429, 500, 502, 503, 504, 521, 522, 524],
20
+ retry_methods: %i[get put],
21
+ environment: Environment::PRODUCTION, config: nil)
22
+ @config = if config.nil?
23
+ Configuration.new(connection: connection, timeout: timeout,
24
+ max_retries: max_retries,
25
+ retry_interval: retry_interval,
26
+ backoff_factor: backoff_factor,
27
+ retry_statuses: retry_statuses,
28
+ retry_methods: retry_methods,
29
+ environment: environment)
30
+ else
31
+ config
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,110 @@
1
+ # apimatic_calculator
2
+ #
3
+ # This file was automatically generated by APIMATIC v2.0
4
+ # ( https://apimatic.io ).
5
+
6
+ module ApimaticCalculator
7
+ # An enum for SDK environments.
8
+ class Environment
9
+ # PRODUCTION: This environment connect to the LIVE calculator API
10
+ ENVIRONMENT = [
11
+ PRODUCTION = 'production'.freeze
12
+ ].freeze
13
+ end
14
+
15
+ # An enum for API servers.
16
+ class Server
17
+ SERVER = [
18
+ CALCULATOR = 'Calculator'.freeze
19
+ ].freeze
20
+ end
21
+
22
+ # All configuration including auth info and base URI for the API access
23
+ # are configured in this class.
24
+ class Configuration
25
+ # The attribute readers for properties.
26
+ attr_reader :http_client, :connection, :timeout, :max_retries, :retry_interval, :backoff_factor,
27
+ :retry_statuses, :retry_methods, :environment
28
+
29
+ class << self
30
+ attr_reader :environments
31
+ end
32
+
33
+ def initialize(connection: nil, timeout: 60, max_retries: 0,
34
+ retry_interval: 1, backoff_factor: 2,
35
+ retry_statuses: [408, 413, 429, 500, 502, 503, 504, 521, 522, 524],
36
+ retry_methods: %i[get put],
37
+ environment: Environment::PRODUCTION)
38
+ # The Faraday connection object passed by the SDK user for making requests
39
+ @connection = connection
40
+
41
+ # The value to use for connection timeout
42
+ @timeout = timeout
43
+
44
+ # The number of times to retry an endpoint call if it fails
45
+ @max_retries = max_retries
46
+
47
+ # Pause in seconds between retries
48
+ @retry_interval = retry_interval
49
+
50
+ # The amount to multiply each successive retry's interval amount
51
+ # by in order to provide backoff
52
+ @backoff_factor = backoff_factor
53
+
54
+ # A list of HTTP statuses to retry
55
+ @retry_statuses = retry_statuses
56
+
57
+ # A list of HTTP methods to retry
58
+ @retry_methods = retry_methods
59
+
60
+ # Current API environment
61
+ @environment = String(environment)
62
+
63
+ # The Http Client to use for making requests.
64
+ @http_client = create_http_client
65
+ end
66
+
67
+ def clone_with(connection: nil, timeout: nil, max_retries: nil,
68
+ retry_interval: nil, backoff_factor: nil,
69
+ retry_statuses: nil, retry_methods: nil, environment: nil)
70
+ connection ||= self.connection
71
+ timeout ||= self.timeout
72
+ max_retries ||= self.max_retries
73
+ retry_interval ||= self.retry_interval
74
+ backoff_factor ||= self.backoff_factor
75
+ retry_statuses ||= self.retry_statuses
76
+ retry_methods ||= self.retry_methods
77
+ environment ||= self.environment
78
+
79
+ Configuration.new(connection: connection, timeout: timeout,
80
+ max_retries: max_retries,
81
+ retry_interval: retry_interval,
82
+ backoff_factor: backoff_factor,
83
+ retry_statuses: retry_statuses,
84
+ retry_methods: retry_methods, environment: environment)
85
+ end
86
+
87
+ def create_http_client
88
+ FaradayClient.new(timeout: timeout, max_retries: max_retries,
89
+ retry_interval: retry_interval,
90
+ backoff_factor: backoff_factor,
91
+ retry_statuses: retry_statuses,
92
+ retry_methods: retry_methods, connection: connection)
93
+ end
94
+
95
+ # All the environments the SDK can run in.
96
+ ENVIRONMENTS = {
97
+ Environment::PRODUCTION => {
98
+ Server::CALCULATOR => 'https://examples.apimatic.io/apps/calculator'
99
+ }
100
+ }.freeze
101
+
102
+ # Generates the appropriate base URI for the environment and the server.
103
+ # @param [Configuration::Server] The server enum for which the base URI is
104
+ # required.
105
+ # @return [String] The base URI.
106
+ def get_base_uri(server = Server::CALCULATOR)
107
+ ENVIRONMENTS[environment][server].clone
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,52 @@
1
+ # apimatic_calculator
2
+ #
3
+ # This file was automatically generated by APIMATIC v2.0
4
+ # ( https://apimatic.io ).
5
+
6
+ module ApimaticCalculator
7
+ # BaseController.
8
+ class BaseController
9
+ attr_accessor :config, :http_call_back
10
+
11
+ def initialize(config, http_call_back: nil)
12
+ @config = config
13
+ @http_call_back = http_call_back
14
+
15
+ @global_headers = {
16
+ 'user-agent' => get_user_agent
17
+ }
18
+ end
19
+
20
+ def validate_parameters(args)
21
+ args.each do |_name, value|
22
+ raise ArgumentError, "Required parameter #{_name} cannot be nil." if value.nil?
23
+ end
24
+ end
25
+
26
+ def execute_request(request, binary: false)
27
+ @http_call_back&.on_before_request(request)
28
+
29
+ APIHelper.clean_hash(request.headers)
30
+ request.headers.merge!(@global_headers)
31
+
32
+ response = if binary
33
+ config.http_client.execute_as_binary(request)
34
+ else
35
+ config.http_client.execute_as_string(request)
36
+ end
37
+ @http_call_back&.on_after_response(response)
38
+
39
+ response
40
+ end
41
+
42
+ def validate_response(response)
43
+ raise APIException.new 'HTTP Response Not OK', response unless
44
+ response.status_code.between?(200, 208) # [200,208] = HTTP OK
45
+ end
46
+
47
+ def get_user_agent
48
+ user_agent = 'APIMATIC 3.0'
49
+ user_agent
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,45 @@
1
+ # apimatic_calculator
2
+ #
3
+ # This file was automatically generated by APIMATIC v2.0
4
+ # ( https://apimatic.io ).
5
+
6
+ module ApimaticCalculator
7
+ # SimpleCalculatorController
8
+ class SimpleCalculatorController < BaseController
9
+ def initialize(config, http_call_back: nil)
10
+ super(config, http_call_back: http_call_back)
11
+ end
12
+
13
+ # Calculates the expression using the specified operation.
14
+ # @param [OperationTypeEnum] operation Required parameter: The operator to
15
+ # apply on the variables
16
+ # @param [Float] x Required parameter: The LHS value
17
+ # @param [Float] y Required parameter: The RHS value
18
+ # @return [Float] response from the API call
19
+ def get_calculate(options = {})
20
+ # Prepare query url.
21
+ _query_builder = config.get_base_uri
22
+ _query_builder << '/{operation}'
23
+ _query_builder = APIHelper.append_url_with_template_parameters(
24
+ _query_builder,
25
+ 'operation' => { 'value' => options['operation'], 'encode' => true }
26
+ )
27
+ _query_builder = APIHelper.append_url_with_query_parameters(
28
+ _query_builder,
29
+ 'x' => options['x'],
30
+ 'y' => options['y']
31
+ )
32
+ _query_url = APIHelper.clean_url _query_builder
33
+
34
+ # Prepare and execute HttpRequest.
35
+ _request = config.http_client.get(
36
+ _query_url
37
+ )
38
+ _response = execute_request(_request)
39
+ validate_response(_response)
40
+
41
+ # Return appropriate response type.
42
+ _response.raw_body.to_f
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,20 @@
1
+ # apimatic_calculator
2
+ #
3
+ # This file was automatically generated by APIMATIC v2.0
4
+ # ( https://apimatic.io ).
5
+
6
+ module ApimaticCalculator
7
+ # Class for exceptions when there is a network error, status code error, etc.
8
+ class APIException < StandardError
9
+ attr_reader :response, :response_code
10
+
11
+ # The constructor.
12
+ # @param [String] The reason for raising an exception.
13
+ # @param [HttpResponse] The HttpReponse of the API call.
14
+ def initialize(reason, response)
15
+ super(reason)
16
+ @response = response
17
+ @response_code = response.status_code
18
+ end
19
+ end
20
+ end