eventmachine 0.12.0-i386-mswin32

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.
Files changed (104) hide show
  1. data/COPYING +60 -0
  2. data/DEFERRABLES +138 -0
  3. data/EPOLL +141 -0
  4. data/GNU +281 -0
  5. data/KEYBOARD +38 -0
  6. data/LEGAL +25 -0
  7. data/LIGHTWEIGHT_CONCURRENCY +72 -0
  8. data/PURE_RUBY +77 -0
  9. data/README +74 -0
  10. data/RELEASE_NOTES +96 -0
  11. data/SMTP +9 -0
  12. data/SPAWNED_PROCESSES +93 -0
  13. data/TODO +10 -0
  14. data/ext/Makefile +180 -0
  15. data/ext/binder.cpp +126 -0
  16. data/ext/binder.h +48 -0
  17. data/ext/cmain.cpp +527 -0
  18. data/ext/cplusplus.cpp +172 -0
  19. data/ext/ed.cpp +1442 -0
  20. data/ext/ed.h +351 -0
  21. data/ext/em.cpp +1781 -0
  22. data/ext/em.h +167 -0
  23. data/ext/emwin.cpp +300 -0
  24. data/ext/emwin.h +94 -0
  25. data/ext/epoll.cpp +26 -0
  26. data/ext/epoll.h +25 -0
  27. data/ext/eventmachine.h +83 -0
  28. data/ext/eventmachine_cpp.h +94 -0
  29. data/ext/extconf.rb +203 -0
  30. data/ext/files.cpp +94 -0
  31. data/ext/files.h +65 -0
  32. data/ext/kb.cpp +368 -0
  33. data/ext/mkmf.log +129 -0
  34. data/ext/page.cpp +107 -0
  35. data/ext/page.h +51 -0
  36. data/ext/pipe.cpp +327 -0
  37. data/ext/project.h +119 -0
  38. data/ext/rubyeventmachine-i386-mswin32.def +2 -0
  39. data/ext/rubyeventmachine-i386-mswin32.exp +0 -0
  40. data/ext/rubyeventmachine-i386-mswin32.lib +0 -0
  41. data/ext/rubyeventmachine-i386-mswin32.pdb +0 -0
  42. data/ext/rubyeventmachine.so +0 -0
  43. data/ext/rubymain.cpp +630 -0
  44. data/ext/sigs.cpp +89 -0
  45. data/ext/sigs.h +32 -0
  46. data/ext/ssl.cpp +408 -0
  47. data/ext/ssl.h +86 -0
  48. data/ext/vc60.pdb +0 -0
  49. data/lib/em/deferrable.rb +208 -0
  50. data/lib/em/eventable.rb +39 -0
  51. data/lib/em/future.rb +62 -0
  52. data/lib/em/messages.rb +66 -0
  53. data/lib/em/processes.rb +68 -0
  54. data/lib/em/spawnable.rb +88 -0
  55. data/lib/em/streamer.rb +112 -0
  56. data/lib/eventmachine.rb +1621 -0
  57. data/lib/eventmachine_version.rb +31 -0
  58. data/lib/evma.rb +32 -0
  59. data/lib/evma/callback.rb +32 -0
  60. data/lib/evma/container.rb +75 -0
  61. data/lib/evma/factory.rb +77 -0
  62. data/lib/evma/protocol.rb +87 -0
  63. data/lib/evma/reactor.rb +48 -0
  64. data/lib/jeventmachine.rb +106 -0
  65. data/lib/pr_eventmachine.rb +1011 -0
  66. data/lib/protocols/buftok.rb +127 -0
  67. data/lib/protocols/header_and_content.rb +123 -0
  68. data/lib/protocols/httpcli2.rb +784 -0
  69. data/lib/protocols/httpclient.rb +253 -0
  70. data/lib/protocols/line_and_text.rb +122 -0
  71. data/lib/protocols/linetext2.rb +145 -0
  72. data/lib/protocols/saslauth.rb +179 -0
  73. data/lib/protocols/smtpclient.rb +308 -0
  74. data/lib/protocols/smtpserver.rb +543 -0
  75. data/lib/protocols/stomp.rb +127 -0
  76. data/lib/protocols/tcptest.rb +57 -0
  77. data/lib/rubyeventmachine.so +0 -0
  78. data/tests/test_basic.rb +142 -0
  79. data/tests/test_defer.rb +63 -0
  80. data/tests/test_epoll.rb +168 -0
  81. data/tests/test_errors.rb +82 -0
  82. data/tests/test_eventables.rb +78 -0
  83. data/tests/test_exc.rb +58 -0
  84. data/tests/test_futures.rb +214 -0
  85. data/tests/test_hc.rb +221 -0
  86. data/tests/test_httpclient.rb +194 -0
  87. data/tests/test_httpclient2.rb +133 -0
  88. data/tests/test_kb.rb +61 -0
  89. data/tests/test_ltp.rb +190 -0
  90. data/tests/test_ltp2.rb +261 -0
  91. data/tests/test_next_tick.rb +58 -0
  92. data/tests/test_processes.rb +56 -0
  93. data/tests/test_pure.rb +128 -0
  94. data/tests/test_running.rb +47 -0
  95. data/tests/test_sasl.rb +73 -0
  96. data/tests/test_send_file.rb +238 -0
  97. data/tests/test_servers.rb +90 -0
  98. data/tests/test_smtpclient.rb +81 -0
  99. data/tests/test_smtpserver.rb +93 -0
  100. data/tests/test_spawn.rb +329 -0
  101. data/tests/test_timers.rb +138 -0
  102. data/tests/test_ud.rb +43 -0
  103. data/tests/testem.rb +5 -0
  104. metadata +170 -0
@@ -0,0 +1,253 @@
1
+ # $Id: httpclient.rb 668 2008-01-04 23:00:34Z blackhedd $
2
+ #
3
+ # Author:: Francis Cianfrocca (gmail: blackhedd)
4
+ # Homepage:: http://rubyeventmachine.com
5
+ # Date:: 16 July 2006
6
+ #
7
+ # See EventMachine and EventMachine::Connection for documentation and
8
+ # usage examples.
9
+ #
10
+ #----------------------------------------------------------------------------
11
+ #
12
+ # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
13
+ # Gmail: blackhedd
14
+ #
15
+ # This program is free software; you can redistribute it and/or modify
16
+ # it under the terms of either: 1) the GNU General Public License
17
+ # as published by the Free Software Foundation; either version 2 of the
18
+ # License, or (at your option) any later version; or 2) Ruby's License.
19
+ #
20
+ # See the file COPYING for complete licensing information.
21
+ #
22
+ #---------------------------------------------------------------------------
23
+ #
24
+ #
25
+
26
+
27
+
28
+ module EventMachine
29
+ module Protocols
30
+
31
+ class HttpClient < Connection
32
+ include EventMachine::Deferrable
33
+
34
+
35
+ MaxPostContentLength = 20 * 1024 * 1024
36
+
37
+ # USAGE SAMPLE:
38
+ #
39
+ # EventMachine.run {
40
+ # http = EventMachine::Protocols::HttpClient.request(
41
+ # :host => server,
42
+ # :port => 80,
43
+ # :request => "/index.html",
44
+ # :query_string => "parm1=value1&parm2=value2"
45
+ # )
46
+ # http.callback {|response|
47
+ # puts response[:status]
48
+ # puts response[:headers]
49
+ # puts response[:content]
50
+ # }
51
+ # }
52
+ #
53
+
54
+ # TODO:
55
+ # Add streaming so we can support enormous POSTs. Current max is 20meg.
56
+ # Timeout for connections that run too long or hang somewhere in the middle.
57
+ # Persistent connections (HTTP/1.1), may need a associated delegate object.
58
+ # DNS: Some way to cache DNS lookups for hostnames we connect to. Ruby's
59
+ # DNS lookups are unbelievably slow.
60
+ # HEAD requests.
61
+ # Chunked transfer encoding.
62
+ # Convenience methods for requests. get, post, url, etc.
63
+ # SSL.
64
+ # Handle status codes like 304, 100, etc.
65
+ # Refactor this code so that protocol errors all get handled one way (an exception?),
66
+ # instead of sprinkling set_deferred_status :failed calls everywhere.
67
+
68
+ def self.request( args = {} )
69
+ args[:port] ||= 80
70
+ EventMachine.connect( args[:host], args[:port], self ) {|c|
71
+ # According to the docs, we will get here AFTER post_init is called.
72
+ c.instance_eval {@args = args}
73
+ }
74
+ end
75
+
76
+ def post_init
77
+ @start_time = Time.now
78
+ @data = ""
79
+ @read_state = :base
80
+ end
81
+
82
+ # We send the request when we get a connection.
83
+ # AND, we set an instance variable to indicate we passed through here.
84
+ # That allows #unbind to know whether there was a successful connection.
85
+ # NB: This naive technique won't work when we have to support multiple
86
+ # requests on a single connection.
87
+ def connection_completed
88
+ @connected = true
89
+ send_request @args
90
+ end
91
+
92
+ def send_request args
93
+ args[:verb] ||= args[:method] # Support :method as an alternative to :verb.
94
+ args[:verb] ||= :get # IS THIS A GOOD IDEA, to default to GET if nothing was specified?
95
+
96
+ verb = args[:verb].to_s.upcase
97
+ unless ["GET", "POST", "PUT", "DELETE", "HEAD"].include?(verb)
98
+ set_deferred_status :failed, {:status => 0} # TODO, not signalling the error type
99
+ return # NOTE THE EARLY RETURN, we're not sending any data.
100
+ end
101
+
102
+ request = args[:request] || "/"
103
+ unless request[0,1] == "/"
104
+ request = "/" + request
105
+ end
106
+
107
+ qs = args[:query_string] || ""
108
+ if qs.length > 0 and qs[0,1] != '?'
109
+ qs = "?" + qs
110
+ end
111
+
112
+ # Allow an override for the host header if it's not the connect-string.
113
+ host = args[:host_header] || args[:host] || "_"
114
+ # For now, ALWAYS tuck in the port string, although we may want to omit it if it's the default.
115
+ port = args[:port]
116
+
117
+ # POST items.
118
+ postcontenttype = args[:contenttype] || "application/octet-stream"
119
+ postcontent = args[:content] || ""
120
+ raise "oversized content in HTTP POST" if postcontent.length > MaxPostContentLength
121
+
122
+ # ESSENTIAL for the request's line-endings to be CRLF, not LF. Some servers misbehave otherwise.
123
+ # TODO: We ASSUME the caller wants to send a 1.1 request. May not be a good assumption.
124
+ req = [
125
+ "#{verb} #{request}#{qs} HTTP/1.1",
126
+ "Host: #{host}:#{port}",
127
+ "User-agent: Ruby EventMachine",
128
+ ]
129
+
130
+ if verb == "POST" || verb == "PUT"
131
+ req << "Content-type: #{postcontenttype}"
132
+ req << "Content-length: #{postcontent.length}"
133
+ end
134
+
135
+ # TODO, this cookie handler assumes it's getting a single, semicolon-delimited string.
136
+ # Eventually we will want to deal intelligently with arrays and hashes.
137
+ if args[:cookie]
138
+ req << "Cookie: #{args[:cookie]}"
139
+ end
140
+
141
+ req << ""
142
+ reqstring = req.map {|l| "#{l}\r\n"}.join
143
+ send_data reqstring
144
+
145
+ if verb == "POST" || verb == "PUT"
146
+ send_data postcontent
147
+ end
148
+ end
149
+
150
+
151
+ def receive_data data
152
+ while data and data.length > 0
153
+ case @read_state
154
+ when :base
155
+ # Perform any per-request initialization here and don't consume any data.
156
+ @data = ""
157
+ @headers = []
158
+ @content_length = nil # not zero
159
+ @content = ""
160
+ @status = nil
161
+ @read_state = :header
162
+ when :header
163
+ ary = data.split( /\r?\n/m, 2 )
164
+ if ary.length == 2
165
+ data = ary.last
166
+ if ary.first == ""
167
+ if @content_length and @content_length > 0
168
+ @read_state = :content
169
+ else
170
+ dispatch_response
171
+ @read_state = :base
172
+ end
173
+ else
174
+ @headers << ary.first
175
+ if @headers.length == 1
176
+ parse_response_line
177
+ elsif ary.first =~ /\Acontent-length:\s*/i
178
+ # Only take the FIRST content-length header that appears,
179
+ # which we can distinguish because @content_length is nil.
180
+ # TODO, it's actually a fatal error if there is more than one
181
+ # content-length header, because the caller is presumptively
182
+ # a bad guy. (There is an exploit that depends on multiple
183
+ # content-length headers.)
184
+ @content_length ||= $'.to_i
185
+ end
186
+ end
187
+ else
188
+ @data << data
189
+ data = ""
190
+ end
191
+ when :content
192
+ # If there was no content-length header, we have to wait until the connection
193
+ # closes. Everything we get until that point is content.
194
+ # TODO: Must impose a content-size limit, and also must implement chunking.
195
+ # Also, must support either temporary files for large content, or calling
196
+ # a content-consumer block supplied by the user.
197
+ if @content_length
198
+ bytes_needed = @content_length - @content.length
199
+ @content += data[0, bytes_needed]
200
+ data = data[bytes_needed..-1] || ""
201
+ if @content_length == @content.length
202
+ dispatch_response
203
+ @read_state = :base
204
+ end
205
+ else
206
+ @content << data
207
+ data = ""
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+
214
+ # We get called here when we have received an HTTP response line.
215
+ # It's an opportunity to throw an exception or trigger other exceptional
216
+ # handling.
217
+ def parse_response_line
218
+ if @headers.first =~ /\AHTTP\/1\.[01] ([\d]{3})/
219
+ @status = $1.to_i
220
+ else
221
+ set_deferred_status :failed, {
222
+ :status => 0 # crappy way of signifying an unrecognized response. TODO, find a better way to do this.
223
+ }
224
+ close_connection
225
+ end
226
+ end
227
+ private :parse_response_line
228
+
229
+ def dispatch_response
230
+ @read_state = :base
231
+ set_deferred_status :succeeded, {
232
+ :content => @content,
233
+ :headers => @headers,
234
+ :status => @status
235
+ }
236
+ # TODO, we close the connection for now, but this is wrong for persistent clients.
237
+ close_connection
238
+ end
239
+
240
+ def unbind
241
+ if !@connected
242
+ set_deferred_status :failed, {:status => 0} # YECCCCH. Find a better way to signal no-connect/network error.
243
+ elsif (@read_state == :content and @content_length == nil)
244
+ dispatch_response
245
+ end
246
+ end
247
+ end
248
+
249
+
250
+ end
251
+ end
252
+
253
+
@@ -0,0 +1,122 @@
1
+ # $Id: line_and_text.rb 668 2008-01-04 23:00:34Z blackhedd $
2
+ #
3
+ # Author:: Francis Cianfrocca (gmail: blackhedd)
4
+ # Homepage:: http://rubyeventmachine.com
5
+ # Date:: 15 November 2006
6
+ #
7
+ # See EventMachine and EventMachine::Connection for documentation and
8
+ # usage examples.
9
+ #
10
+ #----------------------------------------------------------------------------
11
+ #
12
+ # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
13
+ # Gmail: blackhedd
14
+ #
15
+ # This program is free software; you can redistribute it and/or modify
16
+ # it under the terms of either: 1) the GNU General Public License
17
+ # as published by the Free Software Foundation; either version 2 of the
18
+ # License, or (at your option) any later version; or 2) Ruby's License.
19
+ #
20
+ # See the file COPYING for complete licensing information.
21
+ #
22
+ #---------------------------------------------------------------------------
23
+ #
24
+ #
25
+ #
26
+ require File.dirname(__FILE__) + '/buftok'
27
+
28
+ module EventMachine
29
+ module Protocols
30
+
31
+ class LineAndTextProtocol < Connection
32
+ MaxLineLength = 16*1024
33
+ MaxBinaryLength = 32*1024*1024
34
+
35
+ def initialize *args
36
+ super
37
+ lbp_init_line_state
38
+ end
39
+ def receive_data data
40
+ if @lbp_mode == :lines
41
+ begin
42
+ @lpb_buffer.extract(data).each do |line|
43
+ receive_line(line.chomp) if respond_to?(:receive_line)
44
+ end
45
+ rescue Exception
46
+ receive_error('overlength line') if respond_to?(:receive_error)
47
+ close_connection
48
+ return
49
+ end
50
+ else
51
+ if @lbp_binary_limit > 0
52
+ wanted = @lbp_binary_limit - @lbp_binary_bytes_received
53
+ chunk = nil
54
+ if data.length > wanted
55
+ chunk = data.slice!(0...wanted)
56
+ else
57
+ chunk = data
58
+ data = ""
59
+ end
60
+ @lbp_binary_buffer[@lbp_binary_bytes_received...(@lbp_binary_bytes_received+chunk.length)] = chunk
61
+ @lbp_binary_bytes_received += chunk.length
62
+ if @lbp_binary_bytes_received == @lbp_binary_limit
63
+ receive_binary_data(@lbp_binary_buffer) if respond_to?(:receive_binary_data)
64
+ lbp_init_line_state
65
+ end
66
+ receive_data(data) if data.length > 0
67
+ else
68
+ receive_binary_data(data) if respond_to?(:receive_binary_data)
69
+ data = ""
70
+ end
71
+ end
72
+ end
73
+
74
+ def unbind
75
+ if @lbp_mode == :binary and @lbp_binary_limit > 0
76
+ if respond_to?(:receive_binary_data)
77
+ receive_binary_data( @lbp_binary_buffer[0...@lbp_binary_bytes_received] )
78
+ end
79
+ end
80
+ end
81
+
82
+ # Set up to read the supplied number of binary bytes.
83
+ # This recycles all the data currently waiting in the line buffer, if any.
84
+ # If the limit is nil, then ALL subsequent data will be treated as binary
85
+ # data and passed to the upstream protocol handler as we receive it.
86
+ # If a limit is given, we'll hold the incoming binary data and not
87
+ # pass it upstream until we've seen it all, or until there is an unbind
88
+ # (in which case we'll pass up a partial).
89
+ # Specifying nil for the limit (the default) means there is no limit.
90
+ # Specifiyng zero for the limit will cause an immediate transition back to line mode.
91
+ #
92
+ def set_binary_mode size = nil
93
+ if @lbp_mode == :lines
94
+ if size == 0
95
+ receive_binary_data("") if respond_to?(:receive_binary_data)
96
+ # Do no more work here. Stay in line mode and keep consuming data.
97
+ else
98
+ @lbp_binary_limit = size.to_i # (nil will be stored as zero)
99
+ if @lbp_binary_limit > 0
100
+ raise "Overlength" if @lbp_binary_limit > MaxBinaryLength # arbitrary sanity check
101
+ @lbp_binary_buffer = "\0" * @lbp_binary_limit
102
+ @lbp_binary_bytes_received = 0
103
+ end
104
+
105
+ @lbp_mode = :binary
106
+ receive_data @lpb_buffer.flush
107
+ end
108
+ else
109
+ raise "invalid operation"
110
+ end
111
+ end
112
+
113
+ #--
114
+ # For internal use, establish protocol baseline for handling lines.
115
+ def lbp_init_line_state
116
+ @lpb_buffer = BufferedTokenizer.new("\n", MaxLineLength)
117
+ @lbp_mode = :lines
118
+ end
119
+ private :lbp_init_line_state
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,145 @@
1
+ # $Id: linetext2.rb 668 2008-01-04 23:00:34Z blackhedd $
2
+ #
3
+ # Author:: Francis Cianfrocca (gmail: blackhedd)
4
+ # Homepage:: http://rubyeventmachine.com
5
+ # Date:: 15 November 2006
6
+ #
7
+ # See EventMachine and EventMachine::Connection for documentation and
8
+ # usage examples.
9
+ #
10
+ #----------------------------------------------------------------------------
11
+ #
12
+ # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
13
+ # Gmail: blackhedd
14
+ #
15
+ # This program is free software; you can redistribute it and/or modify
16
+ # it under the terms of either: 1) the GNU General Public License
17
+ # as published by the Free Software Foundation; either version 2 of the
18
+ # License, or (at your option) any later version; or 2) Ruby's License.
19
+ #
20
+ # See the file COPYING for complete licensing information.
21
+ #
22
+ #---------------------------------------------------------------------------
23
+ #
24
+ #
25
+ #
26
+
27
+
28
+ # In the grand, time-honored tradition of re-inventing the wheel, we offer
29
+ # here YET ANOTHER protocol that handles line-oriented data with interspersed
30
+ # binary text. This one trades away some of the performance optimizations of
31
+ # EventMachine::Protocols::LineAndTextProtocol in order to get better correctness
32
+ # with regard to binary text blocks that can switch back to line mode. It also
33
+ # permits the line-delimiter to change in midstream.
34
+ # This was originally written to support Stomp.
35
+
36
+ # TODO! We're not enforcing the limits on header lengths and text-lengths.
37
+ # When we get around to that, call #receive_error if the user defined it, otherwise
38
+ # throw exceptions.
39
+
40
+ module EventMachine
41
+ module Protocols
42
+ module LineText2
43
+ MaxLineLength = 16*1024
44
+ MaxBinaryLength = 32*1024*1024
45
+
46
+ #--
47
+ # Will be called recursively until there's no data to read.
48
+ # That way the user-defined handlers we call can modify the
49
+ # handling characteristics on a per-token basis.
50
+ #
51
+ def receive_data data
52
+ return unless (data and data.length > 0)
53
+
54
+ # Do this stuff in lieu of a constructor.
55
+ @lt2_mode ||= :lines
56
+ @lt2_delimiter ||= "\n"
57
+ @lt2_linebuffer ||= []
58
+
59
+ if @lt2_mode == :lines
60
+ if ix = data.index( @lt2_delimiter )
61
+ @lt2_linebuffer << data[0...ix]
62
+ ln = @lt2_linebuffer.join
63
+ @lt2_linebuffer.clear
64
+ if @lt2_delimiter == "\n"
65
+ ln.chomp!
66
+ end
67
+ receive_line ln
68
+ receive_data data[(ix+@lt2_delimiter.length)..-1]
69
+ else
70
+ @lt2_linebuffer << data
71
+ end
72
+ elsif @lt2_mode == :text
73
+ if @lt2_textsize
74
+ needed = @lt2_textsize - @lt2_textpos
75
+ will_take = if data.length > needed
76
+ needed
77
+ else
78
+ data.length
79
+ end
80
+
81
+ @lt2_textbuffer << data[0...will_take]
82
+ tail = data[will_take..-1]
83
+
84
+ @lt2_textpos += will_take
85
+ if @lt2_textpos >= @lt2_textsize
86
+ receive_binary_data @lt2_textbuffer.join
87
+ set_line_mode
88
+ end
89
+
90
+ receive_data tail
91
+ else
92
+ receive_binary_data data
93
+ end
94
+ end
95
+ end
96
+
97
+
98
+ def set_delimiter delim
99
+ @lt2_delimiter = delim.to_s
100
+ end
101
+
102
+ # Called internally but also exposed to user code, for the case in which
103
+ # processing of binary data creates a need to transition back to line mode.
104
+ # We support an optional parameter to "throw back" some data, which might
105
+ # be an umprocessed chunk of the transmitted binary data, or something else
106
+ # entirely.
107
+ def set_line_mode data=""
108
+ @lt2_mode = :lines
109
+ (@lt2_linebuffer ||= []).clear
110
+ receive_data data.to_s
111
+ end
112
+
113
+ def set_text_mode size=nil
114
+ if size == 0
115
+ set_line_mode
116
+ else
117
+ @lt2_mode = :text
118
+ (@lt2_textbuffer ||= []).clear
119
+ @lt2_textsize = size # which can be nil, signifying no limit
120
+ @lt2_textpos = 0
121
+ end
122
+ end
123
+
124
+ # In case of a dropped connection, we'll send a partial buffer to user code
125
+ # when in sized text mode. User overrides of #receive_binary_data need to
126
+ # be aware that they may get a short buffer.
127
+ def unbind
128
+ if @lt2_mode == :text and @lt2_textpos > 0
129
+ receive_binary_data @lt2_textbuffer.join
130
+ end
131
+ end
132
+
133
+ # Stub. Should be subclassed by user code.
134
+ def receive_line ln
135
+ # no-op
136
+ end
137
+
138
+ # Stub. Should be subclassed by user code.
139
+ def receive_binary_data data
140
+ # no-op
141
+ end
142
+ end
143
+ end
144
+ end
145
+