timetree 0.3.2 → 1.0.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/CHANGELOG.md +5 -0
- data/README.md +46 -17
- data/lib/timetree/base_client.rb +52 -0
- data/lib/timetree/calendar_app/access_token.rb +25 -0
- data/lib/timetree/calendar_app/client.rb +228 -0
- data/lib/timetree/configuration.rb +10 -3
- data/lib/timetree/http_command.rb +2 -2
- data/lib/timetree/models/activity.rb +11 -1
- data/lib/timetree/models/application.rb +15 -0
- data/lib/timetree/models/base_model.rb +25 -12
- data/lib/timetree/models/calendar.rb +38 -4
- data/lib/timetree/models/event.rb +27 -3
- data/lib/timetree/oauth_app/client.rb +256 -0
- data/lib/timetree/version.rb +1 -1
- data/timetree.gemspec +1 -0
- metadata +22 -4
- data/lib/timetree/client.rb +0 -298
@@ -5,13 +5,20 @@ require 'logger'
|
|
5
5
|
module TimeTree
|
6
6
|
# TimeTree apis client configuration.
|
7
7
|
class Configuration
|
8
|
-
# @return [String]
|
9
|
-
attr_accessor :
|
8
|
+
# @return [String] OAuthApp's access token
|
9
|
+
attr_accessor :oauth_app_token
|
10
|
+
|
11
|
+
# @return [String] CalendarApp's app id
|
12
|
+
attr_accessor :calendar_app_application_id
|
13
|
+
# @return [String] CalendarApp's private key content#
|
14
|
+
# e.g. File.read('<YOUR_PATH_TO_PEM_FILE>')
|
15
|
+
attr_accessor :calendar_app_private_key
|
16
|
+
|
10
17
|
# @return [Logger]
|
11
18
|
attr_accessor :logger
|
12
19
|
|
13
20
|
def initialize
|
14
|
-
@logger = Logger.new
|
21
|
+
@logger = Logger.new $stdout
|
15
22
|
@logger.level = :warn
|
16
23
|
end
|
17
24
|
end
|
@@ -25,10 +25,10 @@ module TimeTree
|
|
25
25
|
# @param path [String] String or URI to access.
|
26
26
|
# @param body_params [Hash]
|
27
27
|
# The request bodythat will eventually be converted to JSON.
|
28
|
-
def post(path, body_params = {})
|
28
|
+
def post(path, body_params = {}, &block)
|
29
29
|
@logger.debug "POST #{@host}#{path} body:#{body_params}"
|
30
30
|
headers = {'Content-Type' => 'application/json'}
|
31
|
-
res = connection.run_request :post, path, body_params.to_json, headers
|
31
|
+
res = connection.run_request :post, path, body_params.to_json, headers, &block
|
32
32
|
@client.update_ratelimit(res)
|
33
33
|
@logger.debug "Response status:#{res.status}, body:#{res.body}"
|
34
34
|
res
|
@@ -31,7 +31,7 @@ module TimeTree
|
|
31
31
|
# @since 0.0.1
|
32
32
|
def create
|
33
33
|
check_client
|
34
|
-
|
34
|
+
_create
|
35
35
|
end
|
36
36
|
|
37
37
|
#
|
@@ -44,5 +44,15 @@ module TimeTree
|
|
44
44
|
data: {attributes: {content: content}}
|
45
45
|
}
|
46
46
|
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def _create
|
51
|
+
if @client.is_a?(CalendarApp::Client)
|
52
|
+
@client.create_activity(event_id, data_params)
|
53
|
+
else
|
54
|
+
@client.create_activity(calendar_id, event_id, data_params)
|
55
|
+
end
|
56
|
+
end
|
47
57
|
end
|
48
58
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'timetree/models/base_model'
|
4
|
+
|
5
|
+
module TimeTree
|
6
|
+
# Model for TimeTree application.
|
7
|
+
class Application < BaseModel
|
8
|
+
# @return [String]
|
9
|
+
attr_accessor :name
|
10
|
+
# @return [String]
|
11
|
+
attr_accessor :description
|
12
|
+
# @return [String]
|
13
|
+
attr_accessor :image_url
|
14
|
+
end
|
15
|
+
end
|
@@ -15,10 +15,10 @@ module TimeTree
|
|
15
15
|
# @param data [Hash]
|
16
16
|
# TimeTree apis's response data.
|
17
17
|
# @param included [Hash]
|
18
|
-
# @param client [TimeTree::Client]
|
19
|
-
# @return [TimeTree::User, TimeTree::Label, TimeTree::Calendar, TimeTree::Event, TimeTree::Activity]
|
18
|
+
# @param client [TimeTree::OAuthApp::Client, TimeTree::CalendarApp::Client]
|
19
|
+
# @return [TimeTree::User, TimeTree::Label, TimeTree::Calendar, TimeTree::Event, TimeTree::Activity, Hash]
|
20
20
|
# A TimeTree model object that be based on the type.
|
21
|
-
# @raise [TimeTree::Error] if the type property is not set
|
21
|
+
# @raise [TimeTree::Error] if the type property is not set.
|
22
22
|
# @since 0.0.1
|
23
23
|
def self.to_model(data, included: nil, client: nil) # rubocop:disable all
|
24
24
|
id = data[:id]
|
@@ -37,18 +37,22 @@ module TimeTree
|
|
37
37
|
}
|
38
38
|
|
39
39
|
case type
|
40
|
-
when '
|
41
|
-
|
42
|
-
when '
|
43
|
-
|
40
|
+
when 'activity'
|
41
|
+
Activity.new(**params)
|
42
|
+
when 'application'
|
43
|
+
Application.new(**params)
|
44
44
|
when 'calendar'
|
45
45
|
Calendar.new(**params)
|
46
46
|
when 'event'
|
47
47
|
Event.new(**params)
|
48
|
-
when '
|
49
|
-
|
48
|
+
when 'label'
|
49
|
+
Label.new(**params)
|
50
|
+
when 'user'
|
51
|
+
User.new(**params)
|
50
52
|
else
|
51
|
-
|
53
|
+
TimeTree.configuration.logger.warn("type '#{type}' is unknown. id:#{id}")
|
54
|
+
# when unexpected model type, return the 'data' argument.
|
55
|
+
data
|
52
56
|
end
|
53
57
|
end
|
54
58
|
|
@@ -114,8 +118,17 @@ module TimeTree
|
|
114
118
|
item = to_model(data)
|
115
119
|
next unless item
|
116
120
|
|
117
|
-
|
118
|
-
|
121
|
+
if item.is_a? Hash
|
122
|
+
item_id = item[:id]
|
123
|
+
item_type = item[:type]
|
124
|
+
else
|
125
|
+
item_id = item.id
|
126
|
+
item_type = item.type
|
127
|
+
end
|
128
|
+
next unless item_id && item_type
|
129
|
+
|
130
|
+
@_relation_data_dic[item_type] ||= {}
|
131
|
+
@_relation_data_dic[item_type][item_id] = item
|
119
132
|
end
|
120
133
|
detect_relation_data = lambda { |type, id|
|
121
134
|
return unless @_relation_data_dic[type]
|
@@ -32,7 +32,7 @@ module TimeTree
|
|
32
32
|
# @since 0.0.1
|
33
33
|
def event(event_id)
|
34
34
|
check_client
|
35
|
-
|
35
|
+
get_event(event_id)
|
36
36
|
end
|
37
37
|
|
38
38
|
#
|
@@ -48,7 +48,7 @@ module TimeTree
|
|
48
48
|
# @since 0.0.1
|
49
49
|
def upcoming_events(days: 7, timezone: 'UTC')
|
50
50
|
check_client
|
51
|
-
|
51
|
+
get_upcoming_event(days, timezone)
|
52
52
|
end
|
53
53
|
|
54
54
|
#
|
@@ -62,7 +62,7 @@ module TimeTree
|
|
62
62
|
return @members if defined? @members
|
63
63
|
|
64
64
|
check_client
|
65
|
-
@members =
|
65
|
+
@members = get_members
|
66
66
|
end
|
67
67
|
|
68
68
|
#
|
@@ -76,7 +76,41 @@ module TimeTree
|
|
76
76
|
return @labels if defined? @labels
|
77
77
|
|
78
78
|
check_client
|
79
|
-
@labels =
|
79
|
+
@labels = get_labels
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def get_event(event_id)
|
85
|
+
if @client.is_a?(CalendarApp::Client)
|
86
|
+
@client.event(event_id)
|
87
|
+
else
|
88
|
+
@client.event(id, event_id)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def get_upcoming_event(days, timezone)
|
93
|
+
if @client.is_a?(CalendarApp::Client)
|
94
|
+
@client.upcoming_events(days: days, timezone: timezone)
|
95
|
+
else
|
96
|
+
@client.upcoming_events(id, days: days, timezone: timezone)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def get_members
|
101
|
+
if @client.is_a?(CalendarApp::Client)
|
102
|
+
@client.calendar_members
|
103
|
+
else
|
104
|
+
@client.calendar_members(id)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def get_labels
|
109
|
+
if @client.is_a?(CalendarApp::Client)
|
110
|
+
raise Error.new 'CalendarApp does not support label api'
|
111
|
+
else
|
112
|
+
@client.calendar_labels(id)
|
113
|
+
end
|
80
114
|
end
|
81
115
|
end
|
82
116
|
end
|
@@ -56,7 +56,7 @@ module TimeTree
|
|
56
56
|
# @since 0.0.1
|
57
57
|
def create
|
58
58
|
check_client
|
59
|
-
|
59
|
+
_create_event
|
60
60
|
end
|
61
61
|
|
62
62
|
#
|
@@ -68,7 +68,7 @@ module TimeTree
|
|
68
68
|
# @since 0.0.1
|
69
69
|
def update
|
70
70
|
check_client
|
71
|
-
|
71
|
+
_update_event
|
72
72
|
end
|
73
73
|
|
74
74
|
#
|
@@ -80,7 +80,7 @@ module TimeTree
|
|
80
80
|
# @since 0.0.1
|
81
81
|
def delete
|
82
82
|
check_client
|
83
|
-
|
83
|
+
_delete_event
|
84
84
|
end
|
85
85
|
|
86
86
|
#
|
@@ -132,5 +132,29 @@ module TimeTree
|
|
132
132
|
attendees: {data: current_attendees}
|
133
133
|
}
|
134
134
|
end
|
135
|
+
|
136
|
+
def _create_event
|
137
|
+
if @client.is_a?(CalendarApp::Client)
|
138
|
+
@client.create_event(data_params)
|
139
|
+
else
|
140
|
+
@client.create_event(calendar_id, data_params)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def _update_event
|
145
|
+
if @client.is_a?(CalendarApp::Client)
|
146
|
+
@client.update_event(id, data_params)
|
147
|
+
else
|
148
|
+
@client.update_event(calendar_id, id, data_params)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def _delete_event
|
153
|
+
if @client.is_a?(CalendarApp::Client)
|
154
|
+
@client.delete_event(id)
|
155
|
+
else
|
156
|
+
@client.delete_event(calendar_id, id)
|
157
|
+
end
|
158
|
+
end
|
135
159
|
end
|
136
160
|
end
|
@@ -0,0 +1,256 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TimeTree
|
4
|
+
module OAuthApp
|
5
|
+
# TimeTree API OAuthApp client.
|
6
|
+
class Client < BaseClient
|
7
|
+
# @return [String]
|
8
|
+
attr_reader :token
|
9
|
+
|
10
|
+
# @param token [String] a TimeTree's access token.
|
11
|
+
def initialize(token = nil)
|
12
|
+
@token = token || TimeTree.configuration.oauth_app_token
|
13
|
+
check_token
|
14
|
+
@http_cmd = HttpCommand.new(API_HOST, self)
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# Get current user information.
|
19
|
+
#
|
20
|
+
# @return [TimeTree::User]
|
21
|
+
# @raise [TimeTree::ApiError] if the http response status will not success.
|
22
|
+
# @since 0.0.1
|
23
|
+
def current_user
|
24
|
+
res = @http_cmd.get '/user'
|
25
|
+
raise ApiError.new(res) if res.status != 200
|
26
|
+
|
27
|
+
to_model res.body[:data]
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Get a single calendar's information.
|
32
|
+
#
|
33
|
+
# @param cal_id [String] calendar's id.
|
34
|
+
# @param include_relationships [Array<symbol>]
|
35
|
+
# includes association's object in the response.
|
36
|
+
# @return [TimeTree::Calendar]
|
37
|
+
# @raise [TimeTree::Error] if the cal_id arg is empty.
|
38
|
+
# @raise [TimeTree::ApiError] if the http response status will not success.
|
39
|
+
# @since 0.0.1
|
40
|
+
def calendar(cal_id, include_relationships: nil)
|
41
|
+
check_calendar_id cal_id
|
42
|
+
params = relationships_params(include_relationships, Calendar::RELATIONSHIPS)
|
43
|
+
res = @http_cmd.get "/calendars/#{cal_id}", params
|
44
|
+
raise ApiError.new(res) if res.status != 200
|
45
|
+
|
46
|
+
to_model(res.body[:data], included: res.body[:included])
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# Get calendar list that current user can access.
|
51
|
+
#
|
52
|
+
# @param include_relationships [Array<symbol>]
|
53
|
+
# includes association's object in the response.
|
54
|
+
# @return [Array<TimeTree::Calendar>]
|
55
|
+
# @raise [TimeTree::ApiError] if the http response status will not success.
|
56
|
+
# @since 0.0.1
|
57
|
+
def calendars(include_relationships: nil)
|
58
|
+
params = relationships_params(include_relationships, Calendar::RELATIONSHIPS)
|
59
|
+
res = @http_cmd.get '/calendars', params
|
60
|
+
raise ApiError.new(res) if res.status != 200
|
61
|
+
|
62
|
+
included = res.body[:included]
|
63
|
+
res.body[:data].map { |item| to_model(item, included: included) }
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Get a calendar's label information used in event.
|
68
|
+
#
|
69
|
+
# @param cal_id [String] calendar's id.
|
70
|
+
# @return [Array<TimeTree::Label>]
|
71
|
+
# @raise [TimeTree::Error] if the cal_id arg is empty.
|
72
|
+
# @raise [TimeTree::ApiError] if the http response status will not success.
|
73
|
+
# @since 0.0.1
|
74
|
+
def calendar_labels(cal_id)
|
75
|
+
check_calendar_id cal_id
|
76
|
+
res = @http_cmd.get "/calendars/#{cal_id}/labels"
|
77
|
+
raise ApiError.new(res) if res.status != 200
|
78
|
+
|
79
|
+
res.body[:data].map { |item| to_model(item) }
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# Get a calendar's member information.
|
84
|
+
#
|
85
|
+
# @param cal_id [String] calendar's id.
|
86
|
+
# @return [Array<TimeTree::User>]
|
87
|
+
# @raise [TimeTree::Error] if the cal_id arg is empty.
|
88
|
+
# @raise [TimeTree::ApiError] if the http response status will not success.
|
89
|
+
# @since 0.0.1
|
90
|
+
def calendar_members(cal_id)
|
91
|
+
check_calendar_id cal_id
|
92
|
+
res = @http_cmd.get "/calendars/#{cal_id}/members"
|
93
|
+
raise ApiError.new(res) if res.status != 200
|
94
|
+
|
95
|
+
res.body[:data].map { |item| to_model item }
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Get the event's information.
|
100
|
+
#
|
101
|
+
# @param cal_id [String] calendar's id.
|
102
|
+
# @param event_id [String] event's id.
|
103
|
+
# @param include_relationships [Array<symbol>]
|
104
|
+
# includes association's object in the response.
|
105
|
+
# @return [TimeTree::Event]
|
106
|
+
# @raise [TimeTree::Error] if the cal_id arg is empty.
|
107
|
+
# @raise [TimeTree::Error] if the event_id arg is empty.
|
108
|
+
# @raise [TimeTree::ApiError] if the http response status will not success.
|
109
|
+
# @since 0.0.1
|
110
|
+
def event(cal_id, event_id, include_relationships: nil)
|
111
|
+
check_calendar_id cal_id
|
112
|
+
check_event_id event_id
|
113
|
+
params = relationships_params(include_relationships, Event::RELATIONSHIPS)
|
114
|
+
res = @http_cmd.get "/calendars/#{cal_id}/events/#{event_id}", params
|
115
|
+
raise ApiError.new(res) if res.status != 200
|
116
|
+
|
117
|
+
ev = to_model(res.body[:data], included: res.body[:included])
|
118
|
+
ev.calendar_id = cal_id
|
119
|
+
ev
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
# Get the events' information after a request date.
|
124
|
+
#
|
125
|
+
# @param cal_id[String] calendar's id.
|
126
|
+
# @param days [Integer] The number of days to get.
|
127
|
+
# @param timezone [String] Timezone.
|
128
|
+
# @param include_relationships [Array<symbol>]
|
129
|
+
# includes association's object in the response.
|
130
|
+
# @return [Array<TimeTree::Event>]
|
131
|
+
# @raise [TimeTree::Error] if the cal_id arg is empty.
|
132
|
+
# @raise [TimeTree::ApiError] if the http response status will not success.
|
133
|
+
# @since 0.0.1
|
134
|
+
def upcoming_events(cal_id, days: 7, timezone: 'UTC', include_relationships: nil)
|
135
|
+
check_calendar_id cal_id
|
136
|
+
params = relationships_params(include_relationships, Event::RELATIONSHIPS)
|
137
|
+
params.merge!(days: days, timezone: timezone)
|
138
|
+
res = @http_cmd.get "/calendars/#{cal_id}/upcoming_events", params
|
139
|
+
raise ApiError.new(res) if res.status != 200
|
140
|
+
|
141
|
+
included = res.body[:included]
|
142
|
+
res.body[:data].map do |item|
|
143
|
+
ev = to_model(item, included: included)
|
144
|
+
ev.calendar_id = cal_id
|
145
|
+
ev
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
#
|
150
|
+
# Creates an event to the calendar.
|
151
|
+
#
|
152
|
+
# @param cal_id [String] calendar's id.
|
153
|
+
# @param params [Hash] TimeTree request body format.
|
154
|
+
# @return [TimeTree::Event]
|
155
|
+
# @raise [TimeTree::Error] if the cal_id arg is empty.
|
156
|
+
# @raise [TimeTree::ApiError] if the http response status will not success.
|
157
|
+
# @since 0.0.1
|
158
|
+
def create_event(cal_id, params)
|
159
|
+
check_calendar_id cal_id
|
160
|
+
res = @http_cmd.post "/calendars/#{cal_id}/events", params
|
161
|
+
raise ApiError.new(res) if res.status != 201
|
162
|
+
|
163
|
+
ev = to_model res.body[:data]
|
164
|
+
ev.calendar_id = cal_id
|
165
|
+
ev
|
166
|
+
end
|
167
|
+
|
168
|
+
#
|
169
|
+
# Updates an event.
|
170
|
+
#
|
171
|
+
# @param cal_id [String] calendar's id.
|
172
|
+
# @param event_id [String] event's id.
|
173
|
+
# @param params [Hash]
|
174
|
+
# event's information specified in TimeTree request body format.
|
175
|
+
# @return [TimeTree::Event]
|
176
|
+
# @raise [TimeTree::Error] if the cal_id arg is empty.
|
177
|
+
# @raise [TimeTree::Error] if the event_id arg is empty.
|
178
|
+
# @raise [TimeTree::ApiError] if the http response status will not success.
|
179
|
+
# @since 0.0.1
|
180
|
+
def update_event(cal_id, event_id, params)
|
181
|
+
check_calendar_id cal_id
|
182
|
+
check_event_id event_id
|
183
|
+
res = @http_cmd.put "/calendars/#{cal_id}/events/#{event_id}", params
|
184
|
+
raise ApiError.new(res) if res.status != 200
|
185
|
+
|
186
|
+
ev = to_model res.body[:data]
|
187
|
+
ev.calendar_id = cal_id
|
188
|
+
ev
|
189
|
+
end
|
190
|
+
|
191
|
+
#
|
192
|
+
# Deletes an event.
|
193
|
+
#
|
194
|
+
# @param cal_id [String] calendar's id.
|
195
|
+
# @param event_id [String] event's id.
|
196
|
+
# @return [true] if the operation succeeded.
|
197
|
+
# @raise [TimeTree::Error] if the cal_id arg is empty.
|
198
|
+
# @raise [TimeTree::Error] if the event_id arg is empty.
|
199
|
+
# @raise [TimeTree::ApiError] if the http response status will not success.
|
200
|
+
# @since 0.0.1
|
201
|
+
def delete_event(cal_id, event_id)
|
202
|
+
check_calendar_id cal_id
|
203
|
+
check_event_id event_id
|
204
|
+
res = @http_cmd.delete "/calendars/#{cal_id}/events/#{event_id}"
|
205
|
+
raise ApiError.new(res) if res.status != 204
|
206
|
+
|
207
|
+
true
|
208
|
+
end
|
209
|
+
|
210
|
+
#
|
211
|
+
# Creates comment to an event.
|
212
|
+
#
|
213
|
+
# @param cal_id [String] calendar's id.
|
214
|
+
# @param event_id [String] event's id.
|
215
|
+
# @param params [Hash]
|
216
|
+
# comment's information specified in TimeTree request body format.
|
217
|
+
# @return [TimeTree::Activity]
|
218
|
+
# @raise [TimeTree::Error] if the cal_id arg is empty.
|
219
|
+
# @raise [TimeTree::Error] if the event_id arg is empty.
|
220
|
+
# @raise [TimeTree::ApiError] if the http response status is not success.
|
221
|
+
# @since 0.0.1
|
222
|
+
def create_activity(cal_id, event_id, params)
|
223
|
+
check_calendar_id cal_id
|
224
|
+
check_event_id event_id
|
225
|
+
res = @http_cmd.post "/calendars/#{cal_id}/events/#{event_id}/activities", params
|
226
|
+
raise ApiError.new(res) if res.status != 201
|
227
|
+
|
228
|
+
activity = to_model res.body[:data]
|
229
|
+
activity.calendar_id = cal_id
|
230
|
+
activity.event_id = event_id
|
231
|
+
activity
|
232
|
+
end
|
233
|
+
|
234
|
+
def inspect
|
235
|
+
limit_info = nil
|
236
|
+
if defined?(@ratelimit_limit) && @ratelimit_limit
|
237
|
+
limit_info = " ratelimit:#{ratelimit_remaining}/#{ratelimit_limit}"
|
238
|
+
end
|
239
|
+
if defined?(@ratelimit_reset_at) && @ratelimit_reset_at
|
240
|
+
limit_info = "#{limit_info}, reset_at:#{ratelimit_reset_at.strftime('%m/%d %R')}"
|
241
|
+
end
|
242
|
+
"\#<#{self.class}:#{object_id}#{limit_info}>"
|
243
|
+
end
|
244
|
+
|
245
|
+
private
|
246
|
+
|
247
|
+
def check_token
|
248
|
+
check_required_property(@token, 'token')
|
249
|
+
end
|
250
|
+
|
251
|
+
def check_calendar_id(value)
|
252
|
+
check_required_property(value, 'calendar_id')
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|