merb-core 0.9.8 → 0.9.9
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.
- data/CONTRIBUTORS +33 -0
- data/README +7 -3
- data/Rakefile +3 -3
- data/lib/merb-core.rb +165 -94
- data/lib/merb-core/bootloader.rb +469 -100
- data/lib/merb-core/config.rb +79 -3
- data/lib/merb-core/constants.rb +24 -2
- data/lib/merb-core/controller/abstract_controller.rb +172 -67
- data/lib/merb-core/controller/exceptions.rb +50 -6
- data/lib/merb-core/controller/merb_controller.rb +215 -108
- data/lib/merb-core/controller/mime.rb +36 -12
- data/lib/merb-core/controller/mixins/authentication.rb +52 -7
- data/lib/merb-core/controller/mixins/conditional_get.rb +14 -0
- data/lib/merb-core/controller/mixins/controller.rb +90 -58
- data/lib/merb-core/controller/mixins/render.rb +34 -10
- data/lib/merb-core/controller/mixins/responder.rb +40 -16
- data/lib/merb-core/controller/template.rb +37 -16
- data/lib/merb-core/core_ext/hash.rb +9 -0
- data/lib/merb-core/core_ext/kernel.rb +92 -41
- data/lib/merb-core/dispatch/dispatcher.rb +29 -45
- data/lib/merb-core/dispatch/request.rb +186 -82
- data/lib/merb-core/dispatch/router.rb +141 -53
- data/lib/merb-core/dispatch/router/behavior.rb +296 -139
- data/lib/merb-core/dispatch/router/resources.rb +51 -19
- data/lib/merb-core/dispatch/router/route.rb +76 -23
- data/lib/merb-core/dispatch/session.rb +80 -36
- data/lib/merb-core/dispatch/session/container.rb +31 -15
- data/lib/merb-core/dispatch/session/cookie.rb +51 -22
- data/lib/merb-core/dispatch/session/memcached.rb +10 -6
- data/lib/merb-core/dispatch/session/memory.rb +17 -5
- data/lib/merb-core/dispatch/session/store_container.rb +21 -9
- data/lib/merb-core/dispatch/worker.rb +16 -2
- data/lib/merb-core/gem_ext/erubis.rb +4 -0
- data/lib/merb-core/plugins.rb +13 -0
- data/lib/merb-core/rack.rb +1 -0
- data/lib/merb-core/rack/adapter.rb +1 -0
- data/lib/merb-core/rack/adapter/abstract.rb +95 -17
- data/lib/merb-core/rack/adapter/irb.rb +50 -5
- data/lib/merb-core/rack/application.rb +27 -5
- data/lib/merb-core/rack/handler/mongrel.rb +6 -6
- data/lib/merb-core/rack/helpers.rb +33 -0
- data/lib/merb-core/rack/middleware/conditional_get.rb +1 -1
- data/lib/merb-core/rack/middleware/path_prefix.rb +3 -3
- data/lib/merb-core/rack/middleware/static.rb +11 -7
- data/lib/merb-core/server.rb +134 -69
- data/lib/merb-core/tasks/gem_management.rb +153 -80
- data/lib/merb-core/tasks/merb_rake_helper.rb +12 -4
- data/lib/merb-core/tasks/stats.rake +1 -1
- data/lib/merb-core/test/helpers/mock_request_helper.rb +29 -22
- data/lib/merb-core/test/helpers/request_helper.rb +1 -1
- data/lib/merb-core/test/helpers/route_helper.rb +50 -4
- data/lib/merb-core/test/matchers/request_matchers.rb +2 -36
- data/lib/merb-core/test/matchers/view_matchers.rb +32 -22
- data/lib/merb-core/test/run_specs.rb +6 -5
- data/lib/merb-core/test/test_ext/rspec.rb +6 -19
- data/lib/merb-core/version.rb +1 -1
- metadata +5 -4
@@ -3,10 +3,24 @@ module Merb
|
|
3
3
|
|
4
4
|
attr_accessor :thread
|
5
5
|
|
6
|
+
# ==== Returns
|
7
|
+
# Merb::Worker:: instance of a worker.
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
def self.start
|
11
|
+
new
|
12
|
+
end
|
13
|
+
|
14
|
+
# Creates a new worker thread that loops over the work queue.
|
15
|
+
#
|
16
|
+
# @api private
|
6
17
|
def initialize
|
7
18
|
@thread = Thread.new { loop { process_queue } }
|
8
19
|
end
|
9
20
|
|
21
|
+
# Processes tasks in the Merb::Dispatcher.work_queue.
|
22
|
+
#
|
23
|
+
# @api private
|
10
24
|
def process_queue
|
11
25
|
begin
|
12
26
|
while blk = Merb::Dispatcher.work_queue.pop
|
@@ -21,8 +35,8 @@ module Merb
|
|
21
35
|
rescue Exception => e
|
22
36
|
Merb.logger.warn! %Q!Worker Thread Crashed with Exception:\n#{Merb.exception(e)}\nRestarting Worker Thread!
|
23
37
|
retry
|
24
|
-
end
|
38
|
+
end
|
25
39
|
end
|
26
40
|
|
27
41
|
end
|
28
|
-
end
|
42
|
+
end
|
data/lib/merb-core/plugins.rb
CHANGED
@@ -2,10 +2,15 @@ module Merb
|
|
2
2
|
|
3
3
|
module Plugins
|
4
4
|
|
5
|
+
# Returns the configuration settings hash for plugins. This is prepopulated from
|
6
|
+
# Merb.root / "config/plugins.yml" if it is present.
|
7
|
+
#
|
5
8
|
# ==== Returns
|
6
9
|
# Hash::
|
7
10
|
# The configuration loaded from Merb.root / "config/plugins.yml" or, if
|
8
11
|
# the load fails, an empty hash whose default value is another Hash.
|
12
|
+
#
|
13
|
+
# @api plugin
|
9
14
|
def self.config
|
10
15
|
@config ||= begin
|
11
16
|
# this is so you can do Merb.plugins.config[:helpers][:awesome] = "bar"
|
@@ -25,12 +30,16 @@ module Merb
|
|
25
30
|
|
26
31
|
# ==== Returns
|
27
32
|
# Array(String):: All Rakefile load paths Merb uses for plugins.
|
33
|
+
#
|
34
|
+
# @api plugin
|
28
35
|
def self.rakefiles
|
29
36
|
Merb.rakefiles
|
30
37
|
end
|
31
38
|
|
32
39
|
# ==== Returns
|
33
40
|
# Array(String):: All Generator load paths Merb uses for plugins.
|
41
|
+
#
|
42
|
+
# @api plugin
|
34
43
|
def self.generators
|
35
44
|
Merb.generators
|
36
45
|
end
|
@@ -49,6 +58,8 @@ module Merb
|
|
49
58
|
# if defined(Merb::Plugins)
|
50
59
|
# Merb::Plugins.add_rakefiles "merb_sequel" / "merbtasks"
|
51
60
|
# end
|
61
|
+
#
|
62
|
+
# @api plugin
|
52
63
|
def self.add_rakefiles(*rakefiles)
|
53
64
|
Merb.add_rakefiles(*rakefiles)
|
54
65
|
end
|
@@ -60,6 +71,8 @@ module Merb
|
|
60
71
|
#
|
61
72
|
# This is the recommended way to register your plugin's generators
|
62
73
|
# in Merb.
|
74
|
+
#
|
75
|
+
# @api plugin
|
63
76
|
def self.add_generators(*generators)
|
64
77
|
Merb.add_generators(*generators)
|
65
78
|
end
|
data/lib/merb-core/rack.rb
CHANGED
@@ -23,5 +23,6 @@ module Merb
|
|
23
23
|
autoload :ConditionalGet, 'merb-core/rack/middleware/conditional_get'
|
24
24
|
autoload :Csrf, 'merb-core/rack/middleware/csrf'
|
25
25
|
autoload :StreamWrapper, 'merb-core/rack/stream_wrapper'
|
26
|
+
autoload :Helpers, 'merb-core/rack/helpers'
|
26
27
|
end # Rack
|
27
28
|
end # Merb
|
@@ -2,30 +2,83 @@ module Merb
|
|
2
2
|
module Rack
|
3
3
|
class AbstractAdapter
|
4
4
|
|
5
|
+
# This method is designed to be overridden in a rack adapter. It
|
6
|
+
# will be called to start a server created with the new_server method.
|
7
|
+
# This is called from the AbstractAdapter start method.
|
8
|
+
#
|
9
|
+
# @api plugin
|
10
|
+
# @overridable
|
11
|
+
def self.start_server
|
12
|
+
raise NotImplemented
|
13
|
+
end
|
14
|
+
|
15
|
+
# This method is designed to be overridden in a rack adapter. It will
|
16
|
+
# be called to create a new instance of the server for the adapter to
|
17
|
+
# start. The adapter should attempt to bind to a port at this point.
|
18
|
+
# This is called from the AbstractAdapter start method.
|
19
|
+
#
|
20
|
+
# ==== Parameters
|
21
|
+
# port<Integer>:: The port the server should listen on
|
22
|
+
#
|
23
|
+
# @api plugin
|
24
|
+
# @overridable
|
25
|
+
def self.new_server(port)
|
26
|
+
raise NotImplemented
|
27
|
+
end
|
28
|
+
|
29
|
+
# This method is designed to be overridden in a rack adapter. It will
|
30
|
+
# be called to stop the adapter server.
|
31
|
+
#
|
32
|
+
# ==== Parameters
|
33
|
+
# status<Integer>:: The exit status the adapter should exit with.
|
34
|
+
#
|
35
|
+
# ==== Returns
|
36
|
+
# Boolean:: True if the server was properly stopped.
|
37
|
+
#
|
38
|
+
# @api plugin
|
39
|
+
# @overridable
|
40
|
+
def self.stop(status)
|
41
|
+
raise NotImplemented
|
42
|
+
end
|
43
|
+
|
5
44
|
# Spawn a new worker process at a port.
|
45
|
+
#
|
46
|
+
# ==== Parameters
|
47
|
+
# port<Integer>:: The port to start the worker process on.
|
48
|
+
#
|
49
|
+
# @api private
|
6
50
|
def self.spawn_worker(port)
|
7
|
-
|
8
|
-
start_at_port(port, @opts) unless
|
51
|
+
worker_pid = Kernel.fork
|
52
|
+
start_at_port(port, @opts) unless worker_pid
|
9
53
|
|
10
|
-
# If we have a
|
11
|
-
throw(:new_worker) unless
|
54
|
+
# If we have a worker_pid, we're in the parent.
|
55
|
+
throw(:new_worker) unless worker_pid
|
12
56
|
|
13
|
-
@pids[port] =
|
14
|
-
$
|
57
|
+
@pids[port] = worker_pid
|
58
|
+
$WORKERS = @pids.values
|
15
59
|
end
|
16
60
|
|
17
61
|
# The main start method for bootloaders that support forking.
|
62
|
+
# This method launches the adapters which inherit using the
|
63
|
+
# new_server and start_server methods. This method should not
|
64
|
+
# be overridden in adapters which want to fork.
|
65
|
+
#
|
66
|
+
# ==== Parameters
|
67
|
+
# opts<Hash>:: A hash of options
|
68
|
+
# socket: the socket to bind to
|
69
|
+
# port: the port to bind to
|
70
|
+
# cluster: the number
|
71
|
+
#
|
72
|
+
# @api private
|
18
73
|
def self.start(opts={})
|
19
74
|
@opts = opts
|
20
|
-
$
|
75
|
+
$WORKERS ||= []
|
21
76
|
parent = nil
|
22
77
|
|
23
78
|
@pids = {}
|
24
79
|
port = (opts[:socket] || opts[:port]).to_i
|
25
80
|
max_port = Merb::Config[:cluster] ? Merb::Config[:cluster] - 1 : 0
|
26
81
|
|
27
|
-
Merb.logger.warn! "Cluster: #{max_port}"
|
28
|
-
|
29
82
|
# If we only have a single merb, just start it up and dispense with
|
30
83
|
# the spawner/worker setup.
|
31
84
|
if max_port == 0
|
@@ -36,7 +89,7 @@ module Merb
|
|
36
89
|
$0 = process_title(:spawner, port)
|
37
90
|
|
38
91
|
# For each port, spawn a new worker. The parent will continue in
|
39
|
-
# the loop, while the
|
92
|
+
# the loop, while the worker will throw :new_worker and be booted
|
40
93
|
# out of the loop.
|
41
94
|
catch(:new_worker) do
|
42
95
|
0.upto(max_port) do |i|
|
@@ -64,7 +117,7 @@ module Merb
|
|
64
117
|
ensure
|
65
118
|
# If there was no worker with that PID, the status was non-0
|
66
119
|
# (we send back a status of 128 when ABRT is called on a
|
67
|
-
#
|
120
|
+
# worker, and Merb.fatal! exits with a status of 1), or if
|
68
121
|
# Merb is in the process of exiting, *then* don't respawn.
|
69
122
|
# Note that processes killed with kill -9 will return no
|
70
123
|
# exitstatus, and we respawn them.
|
@@ -88,11 +141,21 @@ module Merb
|
|
88
141
|
|
89
142
|
end
|
90
143
|
|
144
|
+
# Fork a server on the specified port and start the app.
|
145
|
+
#
|
146
|
+
# ==== Parameters
|
147
|
+
# port<Integer>:: The port to start the server on
|
148
|
+
# opts<Hash>:: The hash of options, defaults to the @opts
|
149
|
+
# instance variable.
|
150
|
+
#
|
151
|
+
# @api private
|
91
152
|
def self.start_at_port(port, opts = @opts)
|
92
153
|
at_exit do
|
93
154
|
Merb::Server.remove_pid(port)
|
94
155
|
end
|
95
156
|
|
157
|
+
Merb::Worker.start
|
158
|
+
|
96
159
|
# If Merb is daemonized, trap INT. If it's not daemonized,
|
97
160
|
# we let the master process' ctrl-c control the cluster
|
98
161
|
# of workers.
|
@@ -111,6 +174,7 @@ module Merb
|
|
111
174
|
# In daemonized mode or not, support HUPing the process to
|
112
175
|
# restart it.
|
113
176
|
Merb.trap('HUP') do
|
177
|
+
Merb.exiting = true
|
114
178
|
stop
|
115
179
|
Merb.logger.warn! "Exiting port #{port} on #{Process.pid}\n"
|
116
180
|
exit_process
|
@@ -118,6 +182,7 @@ module Merb
|
|
118
182
|
|
119
183
|
# ABRTing the process will kill it, and it will not be respawned.
|
120
184
|
Merb.trap('ABRT') do
|
185
|
+
Merb.exiting = true
|
121
186
|
stopped = stop(128)
|
122
187
|
Merb.logger.warn! "Exiting port #{port}\n" if stopped
|
123
188
|
exit_process(128)
|
@@ -129,9 +194,9 @@ module Merb
|
|
129
194
|
# Store the PID for this worker
|
130
195
|
Merb::Server.store_pid(port)
|
131
196
|
|
132
|
-
Merb::Config[:log_delimiter] = "#{
|
197
|
+
Merb::Config[:log_delimiter] = "#{process_title(:worker, port)} ~ "
|
133
198
|
|
134
|
-
Merb.
|
199
|
+
Merb.reset_logger!
|
135
200
|
Merb.logger.warn!("Starting #{self.name.split("::").last} at port #{port}")
|
136
201
|
|
137
202
|
# If we can't connect to the port, keep trying until we can. Print
|
@@ -144,8 +209,8 @@ module Merb
|
|
144
209
|
new_server(port)
|
145
210
|
rescue Errno::EADDRINUSE
|
146
211
|
unless printed_warning
|
147
|
-
Merb.logger.warn! "
|
148
|
-
|
212
|
+
Merb.logger.warn! "Port #{port} is in use, " \
|
213
|
+
"Waiting for it to become available."
|
149
214
|
printed_warning = true
|
150
215
|
end
|
151
216
|
|
@@ -163,11 +228,24 @@ module Merb
|
|
163
228
|
start_server
|
164
229
|
end
|
165
230
|
|
166
|
-
#
|
231
|
+
# Exit the process with the specified status.
|
232
|
+
#
|
233
|
+
# ==== Parameters
|
234
|
+
# status<Integer>:: The exit code of the process.
|
235
|
+
#
|
236
|
+
# @api private
|
167
237
|
def self.exit_process(status = 0)
|
168
238
|
exit(status)
|
169
239
|
end
|
170
240
|
|
241
|
+
# Set the process title.
|
242
|
+
#
|
243
|
+
# ==== Parameters
|
244
|
+
# whoami<Symbol>:: Either :spawner for the master process or :worker for any of the worker
|
245
|
+
# processes.
|
246
|
+
# port<Integer>:: The base port that the app is running on.
|
247
|
+
#
|
248
|
+
# @api private
|
171
249
|
def self.process_title(whoami, port)
|
172
250
|
name = Merb::Config[:name]
|
173
251
|
app = "merb#{" : #{name}" if (name && name != "merb")}"
|
@@ -179,7 +257,7 @@ module Merb
|
|
179
257
|
"socket#{'s' if max_port > 0 && whoami != :worker} #{numbers} "\
|
180
258
|
"#{file ? file : "#{Merb.log_path}/#{name}.#{port}.sock"}"
|
181
259
|
else
|
182
|
-
"port#{'s' if max_port > 0 && whoami != :worker}"
|
260
|
+
"port#{'s' if max_port > 0 && whoami != :worker} #{port}"
|
183
261
|
end
|
184
262
|
"#{app} : #{whoami} (#{listening_on})"
|
185
263
|
end
|
@@ -1,15 +1,60 @@
|
|
1
1
|
module Merb
|
2
2
|
module Rack
|
3
3
|
class Console
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
4
|
+
# There are three possible ways to use this method. First, if you have a named route,
|
5
|
+
# you can specify the route as the first parameter as a symbol and any paramters in a
|
6
|
+
# hash. Second, you can generate the default route by just passing the params hash,
|
7
|
+
# just passing the params hash. Finally, you can use the anonymous parameters. This
|
8
|
+
# allows you to specify the parameters to a named route in the order they appear in the
|
9
|
+
# router.
|
10
|
+
#
|
11
|
+
# ==== Parameters(Named Route)
|
12
|
+
# name<Symbol>::
|
13
|
+
# The name of the route.
|
14
|
+
# args<Hash>::
|
15
|
+
# Parameters for the route generation.
|
16
|
+
#
|
17
|
+
# ==== Parameters(Default Route)
|
18
|
+
# args<Hash>::
|
19
|
+
# Parameters for the route generation. This route will use the default route.
|
20
|
+
#
|
21
|
+
# ==== Parameters(Anonymous Parameters)
|
22
|
+
# name<Symbol>::
|
23
|
+
# The name of the route.
|
24
|
+
# args<Array>::
|
25
|
+
# An array of anonymous parameters to generate the route
|
26
|
+
# with. These parameters are assigned to the route parameters
|
27
|
+
# in the order that they are passed.
|
7
28
|
#
|
8
29
|
# ==== Returns
|
9
30
|
# String:: The generated URL.
|
10
31
|
#
|
11
|
-
# ====
|
12
|
-
#
|
32
|
+
# ==== Examples
|
33
|
+
# Named Route
|
34
|
+
#
|
35
|
+
# Merb::Router.prepare do
|
36
|
+
# match("/articles/:title").to(:controller => :articles, :action => :show).name("articles")
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# url(:articles, :title => "new_article")
|
40
|
+
#
|
41
|
+
# Default Route
|
42
|
+
#
|
43
|
+
# Merb::Router.prepare do
|
44
|
+
# default_routes
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# url(:controller => "articles", :action => "new")
|
48
|
+
#
|
49
|
+
# Anonymous Paramters
|
50
|
+
#
|
51
|
+
# Merb::Router.prepare do
|
52
|
+
# match("/articles/:year/:month/:title").to(:controller => :articles, :action => :show).name("articles")
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# url(:articles, 2008, 10, "test_article")
|
56
|
+
#
|
57
|
+
# @api public
|
13
58
|
def url(name, *args)
|
14
59
|
args << {}
|
15
60
|
Merb::Router.url(name, *args)
|
@@ -2,24 +2,46 @@ module Merb
|
|
2
2
|
module Rack
|
3
3
|
class Application
|
4
4
|
|
5
|
+
# The main rack application call method. This is the entry point from rack (and the webserver)
|
6
|
+
# to your application.
|
7
|
+
#
|
8
|
+
# ==== Parameters
|
9
|
+
# env<Hash>:: A rack request of parameters.
|
10
|
+
#
|
11
|
+
# ==== Returns
|
12
|
+
# <Array>:: A rack response of [status<Integer>, headers<Hash>, body<String, Stream>]
|
13
|
+
#
|
14
|
+
# @api private
|
5
15
|
def call(env)
|
6
16
|
begin
|
7
|
-
|
17
|
+
rack_response = ::Merb::Dispatcher.handle(Merb::Request.new(env))
|
8
18
|
rescue Object => e
|
9
|
-
return [500, {Merb::Const::CONTENT_TYPE =>
|
19
|
+
return [500, {Merb::Const::CONTENT_TYPE => Merb::Const::TEXT_SLASH_HTML}, e.message + Merb::Const::BREAK_TAG + e.backtrace.join(Merb::Const::BREAK_TAG)]
|
10
20
|
end
|
11
|
-
Merb.logger.info
|
21
|
+
Merb.logger.info Merb::Const::DOUBLE_NEWLINE
|
12
22
|
Merb.logger.flush
|
13
23
|
|
14
24
|
# unless controller.headers[Merb::Const::DATE]
|
15
25
|
# require "time"
|
16
26
|
# controller.headers[Merb::Const::DATE] = Time.now.rfc2822.to_s
|
17
27
|
# end
|
18
|
-
|
28
|
+
rack_response
|
19
29
|
end
|
20
30
|
|
31
|
+
# Determines whether this request is a "deferred_action", usually a long request.
|
32
|
+
# Rack uses this method to detemine whether to use an evented request or a deferred
|
33
|
+
# request in evented rack handlers.
|
34
|
+
#
|
35
|
+
# ==== Parameters
|
36
|
+
# env<Hash>:: The rack request
|
37
|
+
#
|
38
|
+
# ==== Returns
|
39
|
+
# Boolean::
|
40
|
+
# True if the request should be deferred.
|
41
|
+
#
|
42
|
+
# @api private
|
21
43
|
def deferred?(env)
|
22
|
-
path = env[Merb::Const::PATH_INFO] ? env[Merb::Const::PATH_INFO].chomp(
|
44
|
+
path = env[Merb::Const::PATH_INFO] ? env[Merb::Const::PATH_INFO].chomp(Merb::Const::SLASH) : Merb::Const::EMPTY_STRING
|
23
45
|
if path =~ Merb.deferred_actions
|
24
46
|
Merb.logger.info! "Deferring Request: #{path}"
|
25
47
|
true
|
@@ -54,10 +54,10 @@ module Merb
|
|
54
54
|
# response<HTTPResponse>:: The response object to write response to.
|
55
55
|
def process(request, response)
|
56
56
|
env = {}.replace(request.params)
|
57
|
-
env.delete
|
58
|
-
env.delete
|
57
|
+
env.delete Merb::Const::HTTP_CONTENT_TYPE
|
58
|
+
env.delete Merb::Const::HTTP_CONTENT_LENGTH
|
59
59
|
|
60
|
-
env[
|
60
|
+
env[Merb::Const::SCRIPT_NAME] = Merb::Const::EMPTY_STRING if env[Merb::Const::SCRIPT_NAME] == Merb::Const::SLASH
|
61
61
|
|
62
62
|
env.update({"rack.version" => [0,1],
|
63
63
|
"rack.input" => request.body || StringIO.new(""),
|
@@ -69,8 +69,8 @@ module Merb
|
|
69
69
|
|
70
70
|
"rack.url_scheme" => "http"
|
71
71
|
})
|
72
|
-
env[
|
73
|
-
env.delete
|
72
|
+
env[Merb::Const::QUERY_STRING] ||= ""
|
73
|
+
env.delete Merb::Const::PATH_INFO if env[Merb::Const::PATH_INFO] == Merb::Const::EMPTY_STRING
|
74
74
|
|
75
75
|
status, headers, body = @app.call(env)
|
76
76
|
|
@@ -93,4 +93,4 @@ module Merb
|
|
93
93
|
end
|
94
94
|
end
|
95
95
|
end
|
96
|
-
end
|
96
|
+
end
|