ntail 0.0.11 → 0.0.12
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +5 -0
- data/Gemfile.lock +37 -4
- data/README.md +158 -0
- data/Rakefile +24 -11
- data/VERSION +1 -1
- data/bin/ntail +1 -1
- data/lib/ntail/application.rb +74 -97
- data/lib/ntail/body_bytes_sent.rb +2 -2
- data/lib/ntail/formatting.rb +157 -44
- data/lib/ntail/formatting.treetop +44 -15
- data/lib/ntail/http_method.rb +2 -2
- data/lib/ntail/http_referer.rb +2 -2
- data/lib/ntail/http_user_agent.rb +3 -1
- data/lib/ntail/http_version.rb +20 -5
- data/lib/ntail/known_ip_addresses.rb +2 -2
- data/lib/ntail/local_ip_addresses.rb +2 -2
- data/lib/ntail/log_line.rb +90 -42
- data/lib/ntail/node.rb +3 -0
- data/lib/ntail/options.rb +82 -0
- data/lib/ntail/proxy_addresses.rb +2 -2
- data/lib/ntail/remote_addr.rb +2 -2
- data/lib/ntail/remote_user.rb +2 -2
- data/lib/ntail/request.rb +2 -2
- data/lib/ntail/status.rb +1 -1
- data/lib/ntail/time_local.rb +2 -2
- data/lib/ntail/uri.rb +8 -1
- data/lib/ntail.rb +18 -3
- data/ntail.gemspec +29 -6
- data/spec/application_spec.rb +23 -0
- data/spec/spec_helper.rb +9 -0
- data/test/helper.rb +4 -1
- data/test/ntail/test_formatting.rb +72 -0
- data/test/ntail/test_http_version.rb +36 -0
- data/test/ntail/test_log_line.rb +5 -4
- data/test/ntail/test_remote_addr.rb +25 -3
- data/test/test_ntail.rb +7 -0
- metadata +125 -43
- data/README.rdoc +0 -99
data/lib/ntail/formatting.rb
CHANGED
@@ -96,15 +96,15 @@ module Formatting
|
|
96
96
|
end
|
97
97
|
|
98
98
|
i0 = index
|
99
|
-
r1 =
|
99
|
+
r1 = _nt_remote_addr
|
100
100
|
if r1
|
101
101
|
r0 = r1
|
102
102
|
else
|
103
|
-
r2 =
|
103
|
+
r2 = _nt_remote_user
|
104
104
|
if r2
|
105
105
|
r0 = r2
|
106
106
|
else
|
107
|
-
r3 =
|
107
|
+
r3 = _nt_time_local
|
108
108
|
if r3
|
109
109
|
r0 = r3
|
110
110
|
else
|
@@ -116,12 +116,27 @@ module Formatting
|
|
116
116
|
if r5
|
117
117
|
r0 = r5
|
118
118
|
else
|
119
|
-
r6 =
|
119
|
+
r6 = _nt_body_bytes_sent
|
120
120
|
if r6
|
121
121
|
r0 = r6
|
122
122
|
else
|
123
|
-
|
124
|
-
|
123
|
+
r7 = _nt_http_referer
|
124
|
+
if r7
|
125
|
+
r0 = r7
|
126
|
+
else
|
127
|
+
r8 = _nt_http_user_agent
|
128
|
+
if r8
|
129
|
+
r0 = r8
|
130
|
+
else
|
131
|
+
r9 = _nt_proxy_addresses
|
132
|
+
if r9
|
133
|
+
r0 = r9
|
134
|
+
else
|
135
|
+
@index = i0
|
136
|
+
r0 = nil
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
125
140
|
end
|
126
141
|
end
|
127
142
|
end
|
@@ -134,16 +149,21 @@ module Formatting
|
|
134
149
|
r0
|
135
150
|
end
|
136
151
|
|
137
|
-
module
|
152
|
+
module RemoteAddr0
|
138
153
|
def value(log_line, color)
|
139
|
-
|
154
|
+
if color && Sickill::Rainbow.enabled
|
155
|
+
# 24 = 15 + 9, the extra amount of bytes required for the ANSI escape codes...
|
156
|
+
"%24s" % foreground(log_line.remote_address, color)
|
157
|
+
else
|
158
|
+
"%15s" % log_line.remote_address
|
159
|
+
end
|
140
160
|
end
|
141
161
|
end
|
142
162
|
|
143
|
-
def
|
163
|
+
def _nt_remote_addr
|
144
164
|
start_index = index
|
145
|
-
if node_cache[:
|
146
|
-
cached = node_cache[:
|
165
|
+
if node_cache[:remote_addr].has_key?(index)
|
166
|
+
cached = node_cache[:remote_addr][index]
|
147
167
|
if cached
|
148
168
|
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
149
169
|
@index = cached.interval.end
|
@@ -151,30 +171,30 @@ module Formatting
|
|
151
171
|
return cached
|
152
172
|
end
|
153
173
|
|
154
|
-
if has_terminal?('%
|
174
|
+
if has_terminal?('%a', false, index)
|
155
175
|
r0 = instantiate_node(Node,input, index...(index + 2))
|
156
|
-
r0.extend(
|
176
|
+
r0.extend(RemoteAddr0)
|
157
177
|
@index += 2
|
158
178
|
else
|
159
|
-
terminal_parse_failure('%
|
179
|
+
terminal_parse_failure('%a')
|
160
180
|
r0 = nil
|
161
181
|
end
|
162
182
|
|
163
|
-
node_cache[:
|
183
|
+
node_cache[:remote_addr][start_index] = r0
|
164
184
|
|
165
185
|
r0
|
166
186
|
end
|
167
187
|
|
168
|
-
module
|
188
|
+
module RemoteUser0
|
169
189
|
def value(log_line, color)
|
170
|
-
log_line.
|
190
|
+
foreground(log_line.remote_user, color)
|
171
191
|
end
|
172
192
|
end
|
173
193
|
|
174
|
-
def
|
194
|
+
def _nt_remote_user
|
175
195
|
start_index = index
|
176
|
-
if node_cache[:
|
177
|
-
cached = node_cache[:
|
196
|
+
if node_cache[:remote_user].has_key?(index)
|
197
|
+
cached = node_cache[:remote_user][index]
|
178
198
|
if cached
|
179
199
|
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
180
200
|
@index = cached.interval.end
|
@@ -182,30 +202,30 @@ module Formatting
|
|
182
202
|
return cached
|
183
203
|
end
|
184
204
|
|
185
|
-
if has_terminal?('%
|
205
|
+
if has_terminal?('%u', false, index)
|
186
206
|
r0 = instantiate_node(Node,input, index...(index + 2))
|
187
|
-
r0.extend(
|
207
|
+
r0.extend(RemoteUser0)
|
188
208
|
@index += 2
|
189
209
|
else
|
190
|
-
terminal_parse_failure('%
|
210
|
+
terminal_parse_failure('%u')
|
191
211
|
r0 = nil
|
192
212
|
end
|
193
213
|
|
194
|
-
node_cache[:
|
214
|
+
node_cache[:remote_user][start_index] = r0
|
195
215
|
|
196
216
|
r0
|
197
217
|
end
|
198
218
|
|
199
|
-
module
|
219
|
+
module TimeLocal0
|
200
220
|
def value(log_line, color)
|
201
|
-
|
221
|
+
foreground(log_line.to_date_s, color)
|
202
222
|
end
|
203
223
|
end
|
204
224
|
|
205
|
-
def
|
225
|
+
def _nt_time_local
|
206
226
|
start_index = index
|
207
|
-
if node_cache[:
|
208
|
-
cached = node_cache[:
|
227
|
+
if node_cache[:time_local].has_key?(index)
|
228
|
+
cached = node_cache[:time_local][index]
|
209
229
|
if cached
|
210
230
|
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
211
231
|
@index = cached.interval.end
|
@@ -213,23 +233,23 @@ module Formatting
|
|
213
233
|
return cached
|
214
234
|
end
|
215
235
|
|
216
|
-
if has_terminal?('%
|
236
|
+
if has_terminal?('%t', false, index)
|
217
237
|
r0 = instantiate_node(Node,input, index...(index + 2))
|
218
|
-
r0.extend(
|
238
|
+
r0.extend(TimeLocal0)
|
219
239
|
@index += 2
|
220
240
|
else
|
221
|
-
terminal_parse_failure('%
|
241
|
+
terminal_parse_failure('%t')
|
222
242
|
r0 = nil
|
223
243
|
end
|
224
244
|
|
225
|
-
node_cache[:
|
245
|
+
node_cache[:time_local][start_index] = r0
|
226
246
|
|
227
247
|
r0
|
228
248
|
end
|
229
249
|
|
230
250
|
module Request0
|
231
251
|
def value(log_line, color)
|
232
|
-
|
252
|
+
foreground(log_line.to_request_s, color)
|
233
253
|
end
|
234
254
|
end
|
235
255
|
|
@@ -260,7 +280,7 @@ module Formatting
|
|
260
280
|
|
261
281
|
module Status0
|
262
282
|
def value(log_line, color)
|
263
|
-
log_line.status
|
283
|
+
foreground(log_line.status, color)
|
264
284
|
end
|
265
285
|
end
|
266
286
|
|
@@ -289,16 +309,16 @@ module Formatting
|
|
289
309
|
r0
|
290
310
|
end
|
291
311
|
|
292
|
-
module
|
312
|
+
module BodyBytesSent0
|
293
313
|
def value(log_line, color)
|
294
|
-
log_line.
|
314
|
+
foreground(log_line.body_bytes_sent, color)
|
295
315
|
end
|
296
316
|
end
|
297
317
|
|
298
|
-
def
|
318
|
+
def _nt_body_bytes_sent
|
299
319
|
start_index = index
|
300
|
-
if node_cache[:
|
301
|
-
cached = node_cache[:
|
320
|
+
if node_cache[:body_bytes_sent].has_key?(index)
|
321
|
+
cached = node_cache[:body_bytes_sent][index]
|
302
322
|
if cached
|
303
323
|
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
304
324
|
@index = cached.interval.end
|
@@ -306,16 +326,109 @@ module Formatting
|
|
306
326
|
return cached
|
307
327
|
end
|
308
328
|
|
309
|
-
if has_terminal?('%
|
329
|
+
if has_terminal?('%b', false, index)
|
310
330
|
r0 = instantiate_node(Node,input, index...(index + 2))
|
311
|
-
r0.extend(
|
331
|
+
r0.extend(BodyBytesSent0)
|
312
332
|
@index += 2
|
313
333
|
else
|
314
|
-
terminal_parse_failure('%
|
334
|
+
terminal_parse_failure('%b')
|
335
|
+
r0 = nil
|
336
|
+
end
|
337
|
+
|
338
|
+
node_cache[:body_bytes_sent][start_index] = r0
|
339
|
+
|
340
|
+
r0
|
341
|
+
end
|
342
|
+
|
343
|
+
module HttpReferer0
|
344
|
+
def value(log_line, color)
|
345
|
+
foreground(log_line.to_referer_s, color)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def _nt_http_referer
|
350
|
+
start_index = index
|
351
|
+
if node_cache[:http_referer].has_key?(index)
|
352
|
+
cached = node_cache[:http_referer][index]
|
353
|
+
if cached
|
354
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
355
|
+
@index = cached.interval.end
|
356
|
+
end
|
357
|
+
return cached
|
358
|
+
end
|
359
|
+
|
360
|
+
if has_terminal?('%R', false, index)
|
361
|
+
r0 = instantiate_node(Node,input, index...(index + 2))
|
362
|
+
r0.extend(HttpReferer0)
|
363
|
+
@index += 2
|
364
|
+
else
|
365
|
+
terminal_parse_failure('%R')
|
366
|
+
r0 = nil
|
367
|
+
end
|
368
|
+
|
369
|
+
node_cache[:http_referer][start_index] = r0
|
370
|
+
|
371
|
+
r0
|
372
|
+
end
|
373
|
+
|
374
|
+
module HttpUserAgent0
|
375
|
+
def value(log_line, color)
|
376
|
+
foreground(log_line.to_agent_s, color)
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
def _nt_http_user_agent
|
381
|
+
start_index = index
|
382
|
+
if node_cache[:http_user_agent].has_key?(index)
|
383
|
+
cached = node_cache[:http_user_agent][index]
|
384
|
+
if cached
|
385
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
386
|
+
@index = cached.interval.end
|
387
|
+
end
|
388
|
+
return cached
|
389
|
+
end
|
390
|
+
|
391
|
+
if has_terminal?('%U', false, index)
|
392
|
+
r0 = instantiate_node(Node,input, index...(index + 2))
|
393
|
+
r0.extend(HttpUserAgent0)
|
394
|
+
@index += 2
|
395
|
+
else
|
396
|
+
terminal_parse_failure('%U')
|
397
|
+
r0 = nil
|
398
|
+
end
|
399
|
+
|
400
|
+
node_cache[:http_user_agent][start_index] = r0
|
401
|
+
|
402
|
+
r0
|
403
|
+
end
|
404
|
+
|
405
|
+
module ProxyAddresses0
|
406
|
+
def value(log_line, color)
|
407
|
+
(log_line.proxy_addresses || []).join(", ").foreground(color)
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
def _nt_proxy_addresses
|
412
|
+
start_index = index
|
413
|
+
if node_cache[:proxy_addresses].has_key?(index)
|
414
|
+
cached = node_cache[:proxy_addresses][index]
|
415
|
+
if cached
|
416
|
+
cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
417
|
+
@index = cached.interval.end
|
418
|
+
end
|
419
|
+
return cached
|
420
|
+
end
|
421
|
+
|
422
|
+
if has_terminal?('%p', false, index)
|
423
|
+
r0 = instantiate_node(Node,input, index...(index + 2))
|
424
|
+
r0.extend(ProxyAddresses0)
|
425
|
+
@index += 2
|
426
|
+
else
|
427
|
+
terminal_parse_failure('%p')
|
315
428
|
r0 = nil
|
316
429
|
end
|
317
430
|
|
318
|
-
node_cache[:
|
431
|
+
node_cache[:proxy_addresses][start_index] = r0
|
319
432
|
|
320
433
|
r0
|
321
434
|
end
|
@@ -14,29 +14,34 @@ grammar Formatting
|
|
14
14
|
end
|
15
15
|
|
16
16
|
rule token
|
17
|
-
|
17
|
+
remote_addr / remote_user / time_local / request / status / body_bytes_sent / http_referer / http_user_agent / proxy_addresses
|
18
18
|
end
|
19
19
|
|
20
|
-
rule
|
21
|
-
'%
|
20
|
+
rule remote_addr
|
21
|
+
'%a' <Node> {
|
22
22
|
def value(log_line, color)
|
23
|
-
|
23
|
+
if color && Sickill::Rainbow.enabled
|
24
|
+
# 24 = 15 + 9, the extra amount of bytes required for the ANSI escape codes...
|
25
|
+
"%24s" % foreground(log_line.remote_address, color)
|
26
|
+
else
|
27
|
+
"%15s" % log_line.remote_address
|
28
|
+
end
|
24
29
|
end
|
25
30
|
}
|
26
31
|
end
|
27
32
|
|
28
|
-
rule
|
29
|
-
'%
|
33
|
+
rule remote_user
|
34
|
+
'%u' <Node> {
|
30
35
|
def value(log_line, color)
|
31
|
-
log_line.
|
36
|
+
foreground(log_line.remote_user, color)
|
32
37
|
end
|
33
38
|
}
|
34
39
|
end
|
35
40
|
|
36
|
-
rule
|
37
|
-
'%
|
41
|
+
rule time_local
|
42
|
+
'%t' <Node> {
|
38
43
|
def value(log_line, color)
|
39
|
-
|
44
|
+
foreground(log_line.to_date_s, color)
|
40
45
|
end
|
41
46
|
}
|
42
47
|
end
|
@@ -44,7 +49,7 @@ grammar Formatting
|
|
44
49
|
rule request
|
45
50
|
'%r' <Node> {
|
46
51
|
def value(log_line, color)
|
47
|
-
|
52
|
+
foreground(log_line.to_request_s, color)
|
48
53
|
end
|
49
54
|
}
|
50
55
|
end
|
@@ -52,15 +57,39 @@ grammar Formatting
|
|
52
57
|
rule status
|
53
58
|
'%s' <Node> {
|
54
59
|
def value(log_line, color)
|
55
|
-
log_line.status
|
60
|
+
foreground(log_line.status, color)
|
56
61
|
end
|
57
62
|
}
|
58
63
|
end
|
59
64
|
|
60
|
-
rule
|
61
|
-
'%
|
65
|
+
rule body_bytes_sent
|
66
|
+
'%b' <Node> {
|
67
|
+
def value(log_line, color)
|
68
|
+
foreground(log_line.body_bytes_sent, color)
|
69
|
+
end
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
rule http_referer
|
74
|
+
'%R' <Node> {
|
75
|
+
def value(log_line, color)
|
76
|
+
foreground(log_line.to_referer_s, color)
|
77
|
+
end
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
rule http_user_agent
|
82
|
+
'%U' <Node> {
|
83
|
+
def value(log_line, color)
|
84
|
+
foreground(log_line.to_agent_s, color)
|
85
|
+
end
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
rule proxy_addresses
|
90
|
+
'%p' <Node> {
|
62
91
|
def value(log_line, color)
|
63
|
-
log_line.
|
92
|
+
(log_line.proxy_addresses || []).join(", ").foreground(color)
|
64
93
|
end
|
65
94
|
}
|
66
95
|
end
|
data/lib/ntail/http_method.rb
CHANGED
@@ -21,7 +21,7 @@ module NginxTail
|
|
21
21
|
end
|
22
22
|
|
23
23
|
# this ensures the below module methods actually make sense...
|
24
|
-
raise "Class #{base.name} should implement instance method 'http_method'" unless base.instance_methods.include? 'http_method'
|
24
|
+
raise "Class #{base.name} should implement instance method 'http_method'" unless base.instance_methods.map(&:to_s).include? 'http_method'
|
25
25
|
|
26
26
|
end
|
27
27
|
end
|
@@ -31,4 +31,4 @@ module NginxTail
|
|
31
31
|
end
|
32
32
|
|
33
33
|
end
|
34
|
-
end
|
34
|
+
end
|
data/lib/ntail/http_referer.rb
CHANGED
@@ -56,7 +56,7 @@ module NginxTail
|
|
56
56
|
end
|
57
57
|
|
58
58
|
# this ensures the below module methods actually make sense...
|
59
|
-
raise "Class #{base.name} should implement instance method 'http_referer'" unless base.instance_methods.include? 'http_referer'
|
59
|
+
raise "Class #{base.name} should implement instance method 'http_referer'" unless base.instance_methods.map(&:to_s).include? 'http_referer'
|
60
60
|
|
61
61
|
end
|
62
62
|
end
|
@@ -78,4 +78,4 @@ module NginxTail
|
|
78
78
|
end
|
79
79
|
|
80
80
|
end
|
81
|
-
end
|
81
|
+
end
|
@@ -16,6 +16,7 @@ class SearchBot < Agent
|
|
16
16
|
end
|
17
17
|
|
18
18
|
#
|
19
|
+
# Feedfetcher-Google; (+http://www.google.com/feedfetcher.html; feed-id=17168503030479467473)
|
19
20
|
# Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)
|
20
21
|
# Googlebot-Image/1.0
|
21
22
|
# msnbot/2.0b (+http://search.msn.com/msnbot.htm)
|
@@ -29,6 +30,7 @@ class SearchBot < Agent
|
|
29
30
|
#
|
30
31
|
|
31
32
|
KNOWN_SEARCH_BOTS = [
|
33
|
+
GOOGLE_RSS = Regexp.compile('Feedfetcher-Google.*\/'),
|
32
34
|
GOOGLE_BOT = Regexp.compile('Googlebot.*\/'),
|
33
35
|
MSN_BOT = Regexp.compile('msnbot\/'),
|
34
36
|
YAHOO_BOT = Regexp.compile('Yahoo! Slurp\/?'),
|
@@ -103,7 +105,7 @@ module NginxTail
|
|
103
105
|
end
|
104
106
|
|
105
107
|
# this ensures the below module methods actually make sense...
|
106
|
-
raise "Class #{base.name} should implement instance method 'http_user_agent'" unless base.instance_methods.include? 'http_user_agent'
|
108
|
+
raise "Class #{base.name} should implement instance method 'http_user_agent'" unless base.instance_methods.map(&:to_s).include? 'http_user_agent'
|
107
109
|
|
108
110
|
end
|
109
111
|
end
|
data/lib/ntail/http_version.rb
CHANGED
@@ -4,18 +4,33 @@ module NginxTail
|
|
4
4
|
def self.included(base) # :nodoc:
|
5
5
|
base.class_eval do
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
@@http_version_expression = Regexp.compile('HTTP/([0-9])\.([0-9])')
|
8
|
+
|
9
|
+
def self.minor_version(http_version)
|
10
|
+
return $1 if http_version =~ @@http_version_expression
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.major_version(http_version)
|
14
|
+
return $1 if http_version =~ @@http_version_expression
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.to_http_version_s(http_version, which = :full)
|
18
|
+
# http_version will be nil if $request == "-" (ie. "dodgy" HTTP requests)
|
19
|
+
http_version.nil? ? "" : case which
|
20
|
+
when :full then http_version
|
21
|
+
when :major then self.major_version(http_version)
|
22
|
+
when :minor then self.minor_version(http_version)
|
23
|
+
end
|
9
24
|
end
|
10
25
|
|
11
26
|
# this ensures the below module methods actually make sense...
|
12
|
-
raise "Class #{base.name} should implement instance method 'http_version'" unless base.instance_methods.include? 'http_version'
|
27
|
+
raise "Class #{base.name} should implement instance method 'http_version'" unless base.instance_methods.map(&:to_s).include? 'http_version'
|
13
28
|
|
14
29
|
end
|
15
30
|
end
|
16
31
|
|
17
|
-
def to_http_version_s
|
18
|
-
self.class.to_http_version_s(self.http_version)
|
32
|
+
def to_http_version_s(which = :full)
|
33
|
+
self.class.to_http_version_s(self.http_version, which)
|
19
34
|
end
|
20
35
|
|
21
36
|
end
|
@@ -31,7 +31,7 @@ module NginxTail
|
|
31
31
|
end
|
32
32
|
|
33
33
|
# this ensures the below module methods actually make sense...
|
34
|
-
raise "Class #{base.name} should implement instance method 'remote_addr'" unless base.instance_methods.include? 'remote_addr'
|
34
|
+
raise "Class #{base.name} should implement instance method 'remote_addr'" unless base.instance_methods.map(&:to_s).include? 'remote_addr'
|
35
35
|
|
36
36
|
end
|
37
37
|
end
|
@@ -41,4 +41,4 @@ module NginxTail
|
|
41
41
|
end
|
42
42
|
|
43
43
|
end
|
44
|
-
end
|
44
|
+
end
|
@@ -31,7 +31,7 @@ module NginxTail
|
|
31
31
|
end
|
32
32
|
|
33
33
|
# this ensures the below module methods actually make sense...
|
34
|
-
raise "Class #{base.name} should implement instance method 'remote_addr'" unless base.instance_methods.include? 'remote_addr'
|
34
|
+
raise "Class #{base.name} should implement instance method 'remote_addr'" unless base.instance_methods.map(&:to_s).include? 'remote_addr'
|
35
35
|
|
36
36
|
end
|
37
37
|
end
|
@@ -41,4 +41,4 @@ module NginxTail
|
|
41
41
|
end
|
42
42
|
|
43
43
|
end
|
44
|
-
end
|
44
|
+
end
|