reuters 0.8.1
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 +15 -0
- data/.gitignore +23 -0
- data/.rubocop.enabled.yml +23 -0
- data/.rubocop.yml +15 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +28 -0
- data/CONTRIBUTING.md +37 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +209 -0
- data/Rakefile +5 -0
- data/lib/reuters.rb +98 -0
- data/lib/reuters/builder.rb +117 -0
- data/lib/reuters/client.rb +12 -0
- data/lib/reuters/client/base.rb +132 -0
- data/lib/reuters/client/fundamentals.rb +20 -0
- data/lib/reuters/client/search.rb +19 -0
- data/lib/reuters/client/search/base.rb +26 -0
- data/lib/reuters/client/search/equity.rb +64 -0
- data/lib/reuters/client/token.rb +101 -0
- data/lib/reuters/credentials.rb +79 -0
- data/lib/reuters/namespaces.rb +25 -0
- data/lib/reuters/namespaces/base.rb +47 -0
- data/lib/reuters/namespaces/common.rb +30 -0
- data/lib/reuters/namespaces/fundamentals.rb +31 -0
- data/lib/reuters/namespaces/search.rb +48 -0
- data/lib/reuters/namespaces/search/equity.rb +32 -0
- data/lib/reuters/namespaces/token.rb +30 -0
- data/lib/reuters/response.rb +32 -0
- data/lib/reuters/version.rb +6 -0
- data/lib/reuters/wsdls.rb +25 -0
- data/lib/reuters/wsdls/base.rb +44 -0
- data/lib/reuters/wsdls/fundamentals.rb +21 -0
- data/lib/reuters/wsdls/search.rb +13 -0
- data/lib/reuters/wsdls/search/equity.rb +25 -0
- data/lib/reuters/wsdls/token.rb +22 -0
- data/reuters.gemspec +41 -0
- data/spec/fixtures/responses/token.xml +11 -0
- data/spec/reuters/builder_spec.rb +189 -0
- data/spec/reuters/client/fundamentals_spec.rb +11 -0
- data/spec/reuters/client/search/equity_spec.rb +46 -0
- data/spec/reuters/client/token_spec.rb +91 -0
- data/spec/reuters/client_spec.rb +0 -0
- data/spec/reuters/credentials_spec.rb +68 -0
- data/spec/reuters/namespaces/common_spec.rb +5 -0
- data/spec/reuters/namespaces/fundamentals_spec.rb +5 -0
- data/spec/reuters/namespaces/search/equity_spec.rb +5 -0
- data/spec/reuters/namespaces/search_spec.rb +31 -0
- data/spec/reuters/namespaces/token_spec.rb +5 -0
- data/spec/reuters/namespaces_spec.rb +31 -0
- data/spec/reuters/response_spec.rb +54 -0
- data/spec/reuters/version_spec.rb +9 -0
- data/spec/reuters/wsdls/fundamentals_spec.rb +5 -0
- data/spec/reuters/wsdls/search/equity_spec.rb +5 -0
- data/spec/reuters/wsdls/token_spec.rb +5 -0
- data/spec/reuters/wsdls_spec.rb +31 -0
- data/spec/reuters_spec.rb +10 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/client/search_shared.rb +5 -0
- data/spec/support/client_shared.rb +70 -0
- data/spec/support/configurable_shared.rb +9 -0
- data/spec/support/namespaces_actions_shared.rb +36 -0
- data/spec/support/namespaces_shared.rb +42 -0
- data/spec/support/wsdls_actions_shared.rb +36 -0
- data/spec/support/wsdls_shared.rb +53 -0
- metadata +333 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
module Reuters
|
2
|
+
# This class enables the rapid and relatively painless
|
3
|
+
# construction of the body of Reuters API calls.
|
4
|
+
#
|
5
|
+
# It extends the Hash class, which means that it is
|
6
|
+
# compatible with the Savon request call and includes
|
7
|
+
# all the basic functionality of a Hash.
|
8
|
+
#
|
9
|
+
# @note This class is documented with worked examples
|
10
|
+
# on the main README file.
|
11
|
+
#
|
12
|
+
# @see http://savonrb.com/version2/requests.html
|
13
|
+
class Builder < Hash
|
14
|
+
|
15
|
+
def initialize(body = {}, &block)
|
16
|
+
self[:attributes!] = {}
|
17
|
+
attributes body
|
18
|
+
block.call(self) if block
|
19
|
+
end
|
20
|
+
|
21
|
+
# Assign element attributes to a specific key inside
|
22
|
+
# the hash. All attributes are assigned to the
|
23
|
+
# 'hidden' :attributes! key.
|
24
|
+
#
|
25
|
+
# @example Adding an attribute
|
26
|
+
# req = Reuters::Builder.new
|
27
|
+
# req.attributes(exchange_code: { id: 123 })
|
28
|
+
#
|
29
|
+
# @param [Hash] attribs to add to the attributes! key.
|
30
|
+
# @param [Boolean] camelcase the keys inside the hash?
|
31
|
+
#
|
32
|
+
# @return [Hash] the resulting hash that was added.
|
33
|
+
def attributes(attribs, camelcase = true)
|
34
|
+
camelize_keys(attribs).each do |key, value|
|
35
|
+
hash = camelcase ? camelize_keys(value) : value
|
36
|
+
self[:attributes!][key] ||= {}
|
37
|
+
self[:attributes!][key].merge! hash
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Uses the name of the missing method and adds it
|
42
|
+
# as a new key inside this hash. If the method
|
43
|
+
# is called as an assignment, the value will be
|
44
|
+
# set to the element, otherwise a nested {Builder}
|
45
|
+
# is returned.
|
46
|
+
def method_missing(name, body = nil, camelcase = true, &block)
|
47
|
+
key = camelize name
|
48
|
+
|
49
|
+
if name.match(/\=$/)
|
50
|
+
assign_val(key, body)
|
51
|
+
else
|
52
|
+
assign_body(key, body, camelcase)
|
53
|
+
end
|
54
|
+
|
55
|
+
self[key] ||= Reuters::Builder.new(&block) if block
|
56
|
+
self[key] ||= Reuters::Builder.new
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
# Return all keys inside this Hash object except
|
61
|
+
# for the :attributes! key. This key is hidden
|
62
|
+
# so that it cannot be mistaken for an XML Element.
|
63
|
+
#
|
64
|
+
# @return [Array] All keys except for :attributes!
|
65
|
+
# which will always exist.
|
66
|
+
def keys
|
67
|
+
super - [:attributes!]
|
68
|
+
end
|
69
|
+
|
70
|
+
# Attempts to find a key that exists in the hash.
|
71
|
+
# If the key cannot be found in its current format,
|
72
|
+
# the method attempts to camelcase the key and
|
73
|
+
# search again.
|
74
|
+
#
|
75
|
+
# @return [Boolean] True if the key exists, false otherwise.
|
76
|
+
def key?(key)
|
77
|
+
super || super(camelize(key))
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def assign_val(key, body)
|
83
|
+
self[key] = body
|
84
|
+
end
|
85
|
+
|
86
|
+
def assign_body(key, body, camelcase = true)
|
87
|
+
case body
|
88
|
+
when Hash
|
89
|
+
attributes({ key => body }, camelcase)
|
90
|
+
when Array
|
91
|
+
self[key] ||= Array.new(body.count) { '' }
|
92
|
+
attributes(key => order_attributes(body))
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def order_attributes(attribs)
|
97
|
+
keys = attribs.map(&:keys).flatten.uniq
|
98
|
+
ordered = {}
|
99
|
+
keys.each do |k|
|
100
|
+
ordered[k] = []
|
101
|
+
attribs.each { |a| ordered[k] << (a[k].to_s || '') }
|
102
|
+
end
|
103
|
+
ordered
|
104
|
+
end
|
105
|
+
|
106
|
+
def camelize(key)
|
107
|
+
key.to_s.camelize.gsub(/[^a-z0-9]/i, '')
|
108
|
+
end
|
109
|
+
|
110
|
+
def camelize_keys(hash)
|
111
|
+
new_hash = {}
|
112
|
+
hash.each { |key, val| new_hash[camelize key] = val }
|
113
|
+
new_hash
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'reuters/client/base'
|
2
|
+
require 'reuters/client/token'
|
3
|
+
require 'reuters/client/fundamentals'
|
4
|
+
require 'reuters/client/search'
|
5
|
+
|
6
|
+
module Reuters
|
7
|
+
# Main client for the Reuters Gem. The Client module wraps
|
8
|
+
# all different types of Clients for retrieving data from the
|
9
|
+
# Reuter's API.
|
10
|
+
module Client
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module Reuters
|
2
|
+
module Client
|
3
|
+
# The base class for the client is not meant to be directly
|
4
|
+
# initialized but instead contains common functionality
|
5
|
+
# shared by most classes inside the Client.
|
6
|
+
#
|
7
|
+
# This class also handles authenticating the user with the
|
8
|
+
# Reuter's API and handling the resulting token provided.
|
9
|
+
class Base
|
10
|
+
|
11
|
+
delegate :operations, to: :client
|
12
|
+
|
13
|
+
delegate :app_id, :token, :header, to: :@token
|
14
|
+
|
15
|
+
# Initialize the base client class and set a token
|
16
|
+
# that can be retrieved upon request.
|
17
|
+
def initialize
|
18
|
+
@wsdl = Reuters::Wsdls.const_get client_name
|
19
|
+
@namespace = Reuters::Namespaces.const_get client_name
|
20
|
+
@token = Reuters::Client::Token.new
|
21
|
+
end
|
22
|
+
|
23
|
+
# Retrieves a new instance of a Savon client that
|
24
|
+
# has been correctly configured for sending requests
|
25
|
+
# to the Reuter's API.
|
26
|
+
#
|
27
|
+
# @see http://savonrb.com/version2/client.html
|
28
|
+
#
|
29
|
+
# @return [Object] client to make requests through.
|
30
|
+
def client
|
31
|
+
Savon.client options
|
32
|
+
end
|
33
|
+
|
34
|
+
# Attempts to call an operation for this client through
|
35
|
+
# the determined set of operations that have been retrieved
|
36
|
+
# from the WSDL.
|
37
|
+
def method_missing(op, *args)
|
38
|
+
if client.operations.include?(op)
|
39
|
+
request op, *args
|
40
|
+
else
|
41
|
+
fail NoMethodError, op
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Send a correctly formatted request to the Reuter's
|
46
|
+
# API. This method makes an authenticated request to
|
47
|
+
# the API, assuming that a token object has been defined
|
48
|
+
# by the extending Class.
|
49
|
+
#
|
50
|
+
# @note This request method calls the Savon Client #call
|
51
|
+
# method.
|
52
|
+
#
|
53
|
+
# @see http://savonrb.com/version2/requests.html
|
54
|
+
#
|
55
|
+
# @param [Symbol] type of request to send to Reuters API.
|
56
|
+
# @param [Reuters::Builder] message contents to send to the API.
|
57
|
+
# @param [Hash] attribs to attach to the request object.
|
58
|
+
# @param [Boolean] auth defaults to true if a token is required
|
59
|
+
# for this request
|
60
|
+
#
|
61
|
+
# @return [Object] The corresponding Response object.
|
62
|
+
def request(type, message, attribs = {}, auth = true)
|
63
|
+
|
64
|
+
content = {
|
65
|
+
attributes: attribs.merge('xmlns' => @namespace.endpoint),
|
66
|
+
message: before_request.call(message, type)
|
67
|
+
}
|
68
|
+
|
69
|
+
content[:soap_header] = header if auth
|
70
|
+
|
71
|
+
data = client.call(type, content).body
|
72
|
+
|
73
|
+
Reuters::Response.new after_request.call(data[data.keys.first])
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
# Yields a block that is called before a request
|
78
|
+
# is sent to Savon. The hash that is returned from
|
79
|
+
# this block is used as the message part of the
|
80
|
+
# Savon request.
|
81
|
+
#
|
82
|
+
# @note By default this request hook has no effect.
|
83
|
+
#
|
84
|
+
# @yield [req, type] The request object that has been
|
85
|
+
# sent to the client. The operation name is passed
|
86
|
+
# as the second parameter.
|
87
|
+
#
|
88
|
+
# @yieldparam req [Reuters::Builder] The request object.
|
89
|
+
# @yieldparam type [Symbol] The intended operation to perform.
|
90
|
+
def before_request(&block)
|
91
|
+
@before_request = block if block
|
92
|
+
@before_request || proc { |a| a }
|
93
|
+
end
|
94
|
+
|
95
|
+
# Yields a block that is called after a successful
|
96
|
+
# request to Reuters. The object that is returned from
|
97
|
+
# this is request is subsequently passed onto this Clients
|
98
|
+
# corresponding response class.
|
99
|
+
#
|
100
|
+
# @note By default this request hook has no effect.
|
101
|
+
#
|
102
|
+
# @yield [req] The raw response object that
|
103
|
+
# has been received and parsed by Savon.
|
104
|
+
def after_request(&block)
|
105
|
+
@after_request = block if block
|
106
|
+
@after_request || proc { |a| a }
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def client_name
|
112
|
+
self.class.name.demodulize
|
113
|
+
end
|
114
|
+
|
115
|
+
def common
|
116
|
+
Reuters::Namespaces::Common
|
117
|
+
end
|
118
|
+
|
119
|
+
def options
|
120
|
+
{
|
121
|
+
wsdl: @wsdl.endpoint,
|
122
|
+
ssl_version: :SSLv3,
|
123
|
+
namespace_identifier: nil,
|
124
|
+
ssl_verify_mode: :none,
|
125
|
+
log: Reuters.debug,
|
126
|
+
pretty_print_xml: true
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Reuters
|
2
|
+
module Client
|
3
|
+
# Performs a Fundamentals Reuters API request. Fundamentals
|
4
|
+
# can be used to retrieve snapshot information about a specific
|
5
|
+
# company ID and Ticker.
|
6
|
+
#
|
7
|
+
# @example Making a fundamentals request
|
8
|
+
# req = Reuters::Client::Fundamentals.new
|
9
|
+
# msg = Reuters::Builder.new
|
10
|
+
# puts req.operations #=> [:get_snapshot_reports_1, ...]
|
11
|
+
# response = req.get_snapshot_reports_1(msg, {
|
12
|
+
# 'companyId' => id,
|
13
|
+
# 'companyIdType' => type,
|
14
|
+
# 'countryCode' => code,
|
15
|
+
# 'includeMedians' => medians
|
16
|
+
# })
|
17
|
+
class Fundamentals < Base
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'reuters/client/search/base'
|
2
|
+
require 'reuters/client/search/equity'
|
3
|
+
|
4
|
+
module Reuters
|
5
|
+
module Client
|
6
|
+
# Search is a namespace for sub-clients, such as
|
7
|
+
# Equity-based searches on the Reuters API. This
|
8
|
+
# client cannot be initialized directly.
|
9
|
+
# A specific sub-client should be initialized.
|
10
|
+
#
|
11
|
+
# @note Each client behaves in the same way
|
12
|
+
# as every other client.
|
13
|
+
#
|
14
|
+
# @example Initializing an Equity Search Client
|
15
|
+
# cli = Reuters::Client::Search::Equity.new
|
16
|
+
module Search
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Reuters
|
2
|
+
module Client
|
3
|
+
module Search
|
4
|
+
# The base class for the search client is not meant to be
|
5
|
+
# directly initialized but instead contains common functionality
|
6
|
+
# shared by all Search Clients.
|
7
|
+
class Base < Reuters::Client::Base
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
# Re-define namespace and wsdl to be scoped to search
|
11
|
+
@namespace = Reuters::Namespaces::Search.const_get client_name
|
12
|
+
@wsdl = Reuters::Wsdls::Search.const_get client_name
|
13
|
+
@token = Reuters::Client::Token.new
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def ns_definition(*args)
|
19
|
+
Reuters::Namespaces::Search.define(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Reuters
|
2
|
+
module Client
|
3
|
+
module Search
|
4
|
+
# Performs a Equity Search Reuters API request. Equity
|
5
|
+
# searches are intended to vary in complexity but allow
|
6
|
+
# you to retrieve financial information about a
|
7
|
+
# company on a specific exchange.
|
8
|
+
#
|
9
|
+
# @note All Equity Search XML Requests require that the
|
10
|
+
# appropriate xmlns attribute is present on elements
|
11
|
+
# inside the request element. This class uses the
|
12
|
+
# before_request hook to inject the appropriate attribute.
|
13
|
+
class Equity < Base
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
# process equity request objects before they are sent.
|
17
|
+
before_request do |req|
|
18
|
+
|
19
|
+
if req.key?(:query_header)
|
20
|
+
req.attributes({ query_header: { 'xmlns' => data_type } }, false)
|
21
|
+
end
|
22
|
+
|
23
|
+
[:filter, :query].each do |section|
|
24
|
+
if req.key?(section)
|
25
|
+
|
26
|
+
sec = req.send(section)
|
27
|
+
|
28
|
+
sec.keys.each do |key|
|
29
|
+
sec.attributes({ key => { 'xmlns' => query_spec } }, false)
|
30
|
+
sec[key].keys.each do |type|
|
31
|
+
sec[key].attributes({ type => { 'xmlns' => data_type } }, false)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
req
|
39
|
+
end
|
40
|
+
|
41
|
+
super
|
42
|
+
end
|
43
|
+
|
44
|
+
# Retrieve the correctly formatted Namespace Data Type
|
45
|
+
# for an Equity-based Search Reuters Request.
|
46
|
+
#
|
47
|
+
# @return [String] the fully resolved namespace endpoint.
|
48
|
+
def data_type
|
49
|
+
ns_definition(:query_spec_data_types)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Retrieve the correctly formatted Namespace Query
|
53
|
+
# Specification for an Equity-based Search.
|
54
|
+
#
|
55
|
+
# @return [String] the fully resolved namespace endpoint.
|
56
|
+
def query_spec
|
57
|
+
ns_definition(:equity_quote, :query_spec, 1)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Reuters
|
2
|
+
module Client
|
3
|
+
# Retrieves a new Token from the Reuter's API using the
|
4
|
+
# credentials that have been globally configured.
|
5
|
+
#
|
6
|
+
# @example Retrieving a new token
|
7
|
+
# Reuters::Credentials.configure do |c|
|
8
|
+
# c.username = 'username'
|
9
|
+
# c.password = 'pass'
|
10
|
+
# c.app_id = 'MyAppID'
|
11
|
+
# end
|
12
|
+
# authentication = Reuters::Client::Token.new
|
13
|
+
# authentication.token #=> "raj2ja89djf98aj3jjoadjowajdoiaj"
|
14
|
+
class Token < Base
|
15
|
+
|
16
|
+
# @!attribute [r] token
|
17
|
+
# The token that has been retrieved from the Reuter's API.
|
18
|
+
# @return [String, Nil] the token contents, or nil if not set.
|
19
|
+
|
20
|
+
# @!attribute [r] expiration
|
21
|
+
# The timestamp at which the associated token will expire.
|
22
|
+
# @return [Integer, Nil] the expiry time of the token,
|
23
|
+
# or nil if not set.
|
24
|
+
|
25
|
+
# @!attribute [r] username
|
26
|
+
# The username that will be used to authenticate with
|
27
|
+
# the Reuter's api.
|
28
|
+
# @return [String, Nil] the username, or nil if one has
|
29
|
+
# not been configured.
|
30
|
+
|
31
|
+
# @!attribute [r] password
|
32
|
+
# The password that will be used to authenticate with
|
33
|
+
# the Reuter's api.
|
34
|
+
# @return [String, Nil] the password, or nil if one has
|
35
|
+
# not been configured.
|
36
|
+
|
37
|
+
# @!attribute [r] app_id
|
38
|
+
# The application id that will be used to authenticate with
|
39
|
+
# the Reuter's api.
|
40
|
+
# @return [String, Nil] the application id, or nil if one has
|
41
|
+
# not been configured.
|
42
|
+
|
43
|
+
delegate :token, :expiration, to: :current_response
|
44
|
+
|
45
|
+
delegate :username, :password, :app_id, to: :credentials
|
46
|
+
|
47
|
+
# Override {Base} constructor and avoid setting a token
|
48
|
+
# as it will crash the client.
|
49
|
+
def initialize
|
50
|
+
@wsdl = Reuters::Wsdls.const_get client_name
|
51
|
+
@namespace = Reuters::Namespaces.const_get client_name
|
52
|
+
end
|
53
|
+
|
54
|
+
# Build the authentication message that will be sent to Reuters.
|
55
|
+
#
|
56
|
+
# @return [Hash] the contents of the body of the message.
|
57
|
+
def message
|
58
|
+
Reuters::Builder.new do |body|
|
59
|
+
body.application_id = app_id
|
60
|
+
body.username = username
|
61
|
+
body.password = password
|
62
|
+
body.application_id({ xmlns: common.endpoint }, false)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Authenticates with Reuters and automatically
|
67
|
+
# attempts to retieve a new token from the Reuter's API.
|
68
|
+
#
|
69
|
+
# @return [Token] an initialized instance of {Token}
|
70
|
+
def authenticate
|
71
|
+
@response = create_service_token_1(message, {}, false)
|
72
|
+
end
|
73
|
+
|
74
|
+
def header
|
75
|
+
response = current_response
|
76
|
+
Reuters::Builder.new do |body|
|
77
|
+
body.authorization.application_id = app_id
|
78
|
+
body.authorization.token = response.token
|
79
|
+
body.authorization({ 'xmlns' => common.endpoint }, false)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def credentials
|
86
|
+
Reuters::Credentials
|
87
|
+
end
|
88
|
+
|
89
|
+
def current_response
|
90
|
+
if @response && Time.parse(@response.expiration) > Time.now
|
91
|
+
@response
|
92
|
+
else
|
93
|
+
fail 'Token has expired.'
|
94
|
+
end
|
95
|
+
rescue
|
96
|
+
authenticate
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|