usher 0.6.7 → 0.6.8
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/README.rdoc +4 -0
- data/VERSION.yml +2 -2
- data/lib/usher/interface/rails23.rb +13 -3
- data/lib/usher/route.rb +3 -3
- data/lib/usher/util/parser.rb +8 -6
- data/lib/usher/util/rack-mixins.rb +13 -4
- data/lib/usher/util/rails.rb +34 -0
- data/lib/usher/util.rb +1 -0
- data/lib/usher.rb +7 -7
- data/spec/private/rails2_2/recognize_spec.rb +10 -11
- data/spec/private/rails2_3/recognize_spec.rb +10 -11
- metadata +3 -3
- data/rails/init.rb +0 -24
data/README.rdoc
CHANGED
@@ -108,6 +108,10 @@ For instance, the path, <tt>/path/something(.xml|.html)</tt> would only match <t
|
|
108
108
|
|
109
109
|
script/plugin install git://github.com/joshbuddy/usher.git
|
110
110
|
|
111
|
+
In your config/initializers/usher.rb (create if it doesn't exist) add:
|
112
|
+
|
113
|
+
Usher::Util::Rails.activate
|
114
|
+
|
111
115
|
== Rack
|
112
116
|
|
113
117
|
=== config.ru
|
data/VERSION.yml
CHANGED
@@ -9,10 +9,11 @@ class Usher
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def add_named_route(name, route, options = {})
|
12
|
-
|
12
|
+
add_route(route, options).name(name)
|
13
13
|
end
|
14
14
|
|
15
15
|
def add_route(path, options = {})
|
16
|
+
path.gsub!(/(\..*?(?!\)))$/, '(\1)')
|
16
17
|
if !@controller_action_route_added && path =~ %r{^/?:controller/:action/:id$}
|
17
18
|
add_route('/:controller/:action', options.dup)
|
18
19
|
@controller_action_route_added = true
|
@@ -66,6 +67,7 @@ class Usher
|
|
66
67
|
|
67
68
|
def recognize(request)
|
68
69
|
response = @router.recognize(request)
|
70
|
+
request.path_parameters.merge!(response.destination)
|
69
71
|
request.path_parameters.merge!(response.params_as_hash)
|
70
72
|
"#{request.path_parameters[:controller].camelize}Controller".constantize
|
71
73
|
end
|
@@ -73,7 +75,6 @@ class Usher
|
|
73
75
|
def reset!(options={})
|
74
76
|
options[:generator] = options[:generator] || Usher::Util::Generators::URL.new
|
75
77
|
options[:request_methods] = options[:request_methods] || [:protocol, :domain, :port, :query_string, :remote_ip, :user_agent, :referer, :method, :subdomains]
|
76
|
-
|
77
78
|
@router = Usher.new(options)
|
78
79
|
@configuration_files = []
|
79
80
|
@module ||= Module.new
|
@@ -93,11 +94,20 @@ class Usher
|
|
93
94
|
@router.named_routes.keys.each do |name|
|
94
95
|
@module.module_eval <<-end_eval # We use module_eval to avoid leaks
|
95
96
|
def #{name}_url(options = {})
|
96
|
-
ActionController::Routing::
|
97
|
+
ActionController::Routing::Routes.generate(options, {}, :generate, :#{name})
|
98
|
+
end
|
99
|
+
def #{name}_path(options = {})
|
100
|
+
ActionController::Routing::Routes.generate(options, {}, :generate, :#{name})
|
97
101
|
end
|
98
102
|
end_eval
|
99
103
|
end
|
100
104
|
d.__send__(:include, @module)
|
105
|
+
@router.named_routes.instance_eval "
|
106
|
+
def helpers
|
107
|
+
{ }
|
108
|
+
end
|
109
|
+
"
|
110
|
+
@router.named_routes.helpers.__send__(:extend, @module)
|
101
111
|
end
|
102
112
|
end
|
103
113
|
|
data/lib/usher/route.rb
CHANGED
@@ -6,7 +6,7 @@ require File.join('usher', 'route', 'request_method')
|
|
6
6
|
|
7
7
|
class Usher
|
8
8
|
class Route
|
9
|
-
attr_reader :paths, :requirements, :conditions, :named, :generate_with, :default_values, :match_partially, :destination, :priority
|
9
|
+
attr_reader :original_path, :paths, :requirements, :conditions, :named, :generate_with, :default_values, :match_partially, :destination, :priority
|
10
10
|
attr_accessor :parent_route, :router, :recognizable
|
11
11
|
|
12
12
|
class GenerateWith < Struct.new(:scheme, :port, :host)
|
@@ -15,8 +15,8 @@ class Usher
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
def initialize(parsed_paths, router, conditions, requirements, default_values, generate_with, match_partially, priority)
|
19
|
-
@router, @requirements, @conditions, @default_values, @match_partially, @priority = router, requirements, conditions, default_values, match_partially, priority
|
18
|
+
def initialize(original_path, parsed_paths, router, conditions, requirements, default_values, generate_with, match_partially, priority)
|
19
|
+
@original_path, @router, @requirements, @conditions, @default_values, @match_partially, @priority = original_path, router, requirements, conditions, default_values, match_partially, priority
|
20
20
|
@recognizable = true
|
21
21
|
@paths = parsed_paths.collect {|path| Path.new(self, path)}
|
22
22
|
@generate_with = GenerateWith.new(generate_with[:scheme], generate_with[:port], generate_with[:host]) if generate_with
|
data/lib/usher/util/parser.rb
CHANGED
@@ -21,25 +21,26 @@ class Usher
|
|
21
21
|
|
22
22
|
def generate_route(unprocessed_path, conditions, requirements, default_values, generate_with, priority)
|
23
23
|
match_partially = false
|
24
|
+
processed_path = unprocessed_path
|
24
25
|
case unprocessed_path
|
25
26
|
when String
|
26
27
|
if unprocessed_path[-1] == ?*
|
27
28
|
unprocessed_path.slice!(-1)
|
28
29
|
match_partially = true
|
29
30
|
end
|
30
|
-
|
31
|
+
processed_path = parse(unprocessed_path, requirements, default_values)
|
31
32
|
when Regexp
|
32
|
-
|
33
|
+
processed_path = [Route::Static::Greedy.new(unprocessed_path)]
|
33
34
|
else
|
34
35
|
match_partially = false
|
35
36
|
end
|
36
37
|
|
37
|
-
unless
|
38
|
+
unless processed_path.first.is_a?(Route::Util::Group)
|
38
39
|
group = Usher::Route::Util::Group.new(:all, nil)
|
39
|
-
|
40
|
-
|
40
|
+
processed_path.each{|p| group << p}
|
41
|
+
processed_path = group
|
41
42
|
end
|
42
|
-
paths = Route::Util.expand_path(
|
43
|
+
paths = Route::Util.expand_path(processed_path)
|
43
44
|
|
44
45
|
paths.each do |path|
|
45
46
|
path.each_with_index do |part, index|
|
@@ -64,6 +65,7 @@ class Usher
|
|
64
65
|
end
|
65
66
|
|
66
67
|
Route.new(
|
68
|
+
unprocessed_path,
|
67
69
|
paths,
|
68
70
|
router,
|
69
71
|
conditions,
|
@@ -2,30 +2,39 @@ require 'rack'
|
|
2
2
|
|
3
3
|
unless Rack::Utils.respond_to?(:uri_escape)
|
4
4
|
module Rack
|
5
|
-
|
6
5
|
module Utils
|
7
|
-
|
8
6
|
def uri_escape(s)
|
9
7
|
s.to_s.gsub(/([^:\/?\[\]\-_~\.!\$&'\(\)\*\+,;=@a-zA-Z0-9]+)/n) {
|
10
8
|
'%'<<$1.unpack('H2'*$1.size).join('%').upcase
|
11
9
|
}
|
12
10
|
end
|
13
11
|
module_function :uri_escape
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
14
15
|
|
16
|
+
unless Rack::Utils.respond_to?(:uri_escape!)
|
17
|
+
module Rack
|
18
|
+
module Utils
|
15
19
|
def uri_escape!(s)
|
16
20
|
s.to_s.gsub!(/([^:\/?\[\]\-_~\.!\$&'\(\)\*\+,;=@a-zA-Z0-9]+)/n) {
|
17
21
|
'%'<<$1.unpack('H2'*$1.size).join('%').upcase
|
18
22
|
}
|
19
23
|
end
|
20
24
|
module_function :uri_escape!
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
21
28
|
|
29
|
+
unless Rack::Utils.respond_to?(:uri_unescape)
|
30
|
+
module Rack
|
31
|
+
module Utils
|
22
32
|
def uri_unescape(s)
|
23
33
|
gsub(/((?:%[0-9a-fA-F]{2})+)/n){
|
24
34
|
[$1.delete('%')].pack('H*')
|
25
35
|
}
|
26
36
|
end
|
27
37
|
module_function :uri_unescape
|
28
|
-
|
29
38
|
end
|
30
39
|
end
|
31
|
-
end
|
40
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Usher
|
2
|
+
module Util
|
3
|
+
class Rails
|
4
|
+
|
5
|
+
def self.activate
|
6
|
+
rails_version = "#{::Rails::VERSION::MAJOR}.#{::Rails::VERSION::MINOR}"
|
7
|
+
|
8
|
+
case rails_version
|
9
|
+
when '2.3'
|
10
|
+
ActionController::Routing.module_eval "remove_const(:Routes); Routes = Usher::Interface.for(:rails23)"
|
11
|
+
|
12
|
+
when '2.2'
|
13
|
+
class Usher::Interface::Rails22::Mapper
|
14
|
+
include ActionController::Resources
|
15
|
+
end
|
16
|
+
ActionController::Routing.module_eval "remove_const(:Routes); Routes = Usher::Interface.for(:rails22)"
|
17
|
+
|
18
|
+
when '2.0'
|
19
|
+
class Usher::Interface::Rails20::Mapper
|
20
|
+
include ActionController::Resources
|
21
|
+
end
|
22
|
+
|
23
|
+
ActionController::Routing.module_eval <<-CODE
|
24
|
+
remove_const(:Routes);
|
25
|
+
interface = Usher::Interface.for(:rails20);
|
26
|
+
interface.configuration_file = File.join(RAILS_ROOT, 'config', 'routes.rb')
|
27
|
+
Routes = interface;
|
28
|
+
CODE
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/usher/util.rb
CHANGED
data/lib/usher.rb
CHANGED
@@ -71,13 +71,13 @@ class Usher
|
|
71
71
|
# Example, you create a route with a destination of :controller => 'test', :action => 'action'. If you made a call to generator with :controller => 'test',
|
72
72
|
# :action => 'action', it would pick that route to use for generation.
|
73
73
|
def initialize(options = nil)
|
74
|
-
self.generator
|
75
|
-
self.delimiters
|
76
|
-
self.valid_regex
|
77
|
-
self.request_methods
|
78
|
-
self.ignore_trailing_delimiters
|
79
|
-
self.consider_destination_keys
|
80
|
-
self.allow_identical_variable_names
|
74
|
+
self.generator = options && options.delete(:generator)
|
75
|
+
self.delimiters = Delimiters.new(options && options.delete(:delimiters) || ['/', '.'])
|
76
|
+
self.valid_regex = options && options.delete(:valid_regex) || '[0-9A-Za-z\$\-_\+!\*\',]+'
|
77
|
+
self.request_methods = options && options.delete(:request_methods)
|
78
|
+
self.ignore_trailing_delimiters = options && options.key?(:ignore_trailing_delimiters) ? options.delete(:ignore_trailing_delimiters) : false
|
79
|
+
self.consider_destination_keys = options && options.key?(:consider_destination_keys) ? options.delete(:consider_destination_keys) : false
|
80
|
+
self.allow_identical_variable_names = options && options.key?(:allow_identical_variable_names) ? options.delete(:allow_identical_variable_names) : true
|
81
81
|
reset!
|
82
82
|
end
|
83
83
|
|
@@ -1,11 +1,10 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
|
2
2
|
require File.expand_path(File.join(File.dirname(__FILE__), 'compat'))
|
3
3
|
require "usher"
|
4
|
-
require 'action_controller'
|
5
4
|
|
6
5
|
route_set = Usher::Interface.for(:rails22)
|
7
6
|
|
8
|
-
def
|
7
|
+
def build_request_mock_rails22(path, method, params)
|
9
8
|
request = mock "Request"
|
10
9
|
request.should_receive(:path).any_number_of_times.and_return(path)
|
11
10
|
request.should_receive(:method).any_number_of_times.and_return(method)
|
@@ -25,35 +24,35 @@ describe "Usher (for rails 2.2) route recognition" do
|
|
25
24
|
|
26
25
|
it "should recognize a simple request" do
|
27
26
|
route_set.add_route('/sample', :controller => 'sample', :action => 'action')
|
28
|
-
route_set.recognize(
|
27
|
+
route_set.recognize(build_request_mock_rails22('/sample', 'get', {:controller => 'sample', :action => 'action'})).should == SampleController
|
29
28
|
end
|
30
29
|
|
31
30
|
it "should interpolate action :index" do
|
32
31
|
route_set.add_route('/sample', :controller => 'sample')
|
33
|
-
route_set.recognize(
|
32
|
+
route_set.recognize(build_request_mock_rails22('/sample', 'get', {:controller => 'sample', :action => 'index'})).should == SampleController
|
34
33
|
end
|
35
34
|
|
36
35
|
it "should correctly distinguish between multiple request methods" do
|
37
36
|
route_set.add_route('/sample', :controller => 'not_sample', :conditions => {:method => :get})
|
38
37
|
correct_route = route_set.add_route('/sample', :controller => 'sample', :conditions => {:method => :post})
|
39
38
|
route_set.add_route('/sample', :controller => 'not_sample', :conditions => {:method => :put})
|
40
|
-
route_set.recognize(
|
39
|
+
route_set.recognize(build_request_mock_rails22('/sample', :post, {:controller => 'sample', :action => 'index'})).should == SampleController
|
41
40
|
end
|
42
41
|
|
43
42
|
it "should prefer the static route to the dynamic route" do
|
44
43
|
route_set.add_route('/sample/:action', :controller => 'not_sample')
|
45
44
|
route_set.add_route('/sample/test', :controller => 'sample', :action => 'action')
|
46
|
-
route_set.recognize(
|
45
|
+
route_set.recognize(build_request_mock_rails22('/sample/test', 'get', {:controller => 'sample', :action => 'action'})).should == SampleController
|
47
46
|
end
|
48
47
|
|
49
48
|
it "should raise based upon an invalid param" do
|
50
49
|
route_set.add_named_route(:sample, '/sample/:action', :controller => 'sample', :requirements => {:action => /\d+/})
|
51
|
-
proc { route_set.recognize(
|
50
|
+
proc { route_set.recognize(build_request_mock_rails22('/sample/asdqwe', :post, {})) }.should raise_error
|
52
51
|
end
|
53
52
|
|
54
53
|
it "should raise based upon an invalid route" do
|
55
54
|
route_set.add_named_route(:sample, '/sample', :controller => 'sample', :action => 'test')
|
56
|
-
proc { route_set.recognize(
|
55
|
+
proc { route_set.recognize(build_request_mock_rails22('/test/asdqwe', :post, {})) }.should raise_error
|
57
56
|
end
|
58
57
|
|
59
58
|
it "should add /:controller and /:controller/:action if /:controller/:action/:id is added" do
|
@@ -63,17 +62,17 @@ describe "Usher (for rails 2.2) route recognition" do
|
|
63
62
|
|
64
63
|
it "should correctly recognize a format (dynamic path path with . delimiter)" do
|
65
64
|
route_set.add_route('/:controller/:action/:id.:format')
|
66
|
-
route_set.recognize(
|
65
|
+
route_set.recognize(build_request_mock_rails22('/sample/test/123.html', 'get', {:controller => 'sample', :action => 'test', :id => '123', :format => 'html'})).should == SampleController
|
67
66
|
end
|
68
67
|
|
69
68
|
it "should support root nodes" do
|
70
69
|
route_set.add_route('/', :controller => 'sample')
|
71
|
-
route_set.recognize(
|
70
|
+
route_set.recognize(build_request_mock_rails22('/', :get, {:controller => 'sample', :action => 'index'})).should == SampleController
|
72
71
|
end
|
73
72
|
|
74
73
|
it "should default action to 'index' when controller (and not index) is specified" do
|
75
74
|
route_set.add_route('/:controller/:action')
|
76
|
-
route_set.recognize(
|
75
|
+
route_set.recognize(build_request_mock_rails22('/sample', :get, {:controller => 'sample', :action => 'index'})).should == SampleController
|
77
76
|
end
|
78
77
|
|
79
78
|
|
@@ -1,11 +1,10 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
|
2
2
|
require File.expand_path(File.join(File.dirname(__FILE__), 'compat'))
|
3
3
|
require "usher"
|
4
|
-
require 'action_controller'
|
5
4
|
|
6
5
|
route_set = Usher::Interface.for(:rails23)
|
7
6
|
|
8
|
-
def
|
7
|
+
def build_request_mock_rails23(path, method, params)
|
9
8
|
request = mock "Request"
|
10
9
|
request.should_receive(:path).any_number_of_times.and_return(path)
|
11
10
|
request.should_receive(:method).any_number_of_times.and_return(method)
|
@@ -25,35 +24,35 @@ describe "Usher (for rails 2.3) route recognition" do
|
|
25
24
|
|
26
25
|
it "should recognize a simple request" do
|
27
26
|
route_set.add_route('/sample', :controller => 'sample', :action => 'action')
|
28
|
-
route_set.recognize(
|
27
|
+
route_set.recognize(build_request_mock_rails23('/sample', 'get', {:controller => 'sample', :action => 'action'})).should == SampleController
|
29
28
|
end
|
30
29
|
|
31
30
|
it "should interpolate action :index" do
|
32
31
|
route_set.add_route('/sample', :controller => 'sample')
|
33
|
-
route_set.recognize(
|
32
|
+
route_set.recognize(build_request_mock_rails23('/sample', 'get', {:controller => 'sample', :action => 'index'})).should == SampleController
|
34
33
|
end
|
35
34
|
|
36
35
|
it "should correctly distinguish between multiple request methods" do
|
37
36
|
route_set.add_route('/sample', :controller => 'not_sample', :conditions => {:method => :get})
|
38
37
|
correct_route = route_set.add_route('/sample', :controller => 'sample', :conditions => {:method => :post})
|
39
38
|
route_set.add_route('/sample', :controller => 'not_sample', :conditions => {:method => :put})
|
40
|
-
route_set.recognize(
|
39
|
+
route_set.recognize(build_request_mock_rails23('/sample', :post, {:controller => 'sample', :action => 'index'})).should == SampleController
|
41
40
|
end
|
42
41
|
|
43
42
|
it "should prefer the static route to the dynamic route" do
|
44
43
|
route_set.add_route('/sample/:action', :controller => 'not_sample')
|
45
44
|
route_set.add_route('/sample/test', :controller => 'sample', :action => 'action')
|
46
|
-
route_set.recognize(
|
45
|
+
route_set.recognize(build_request_mock_rails23('/sample/test', 'get', {:controller => 'sample', :action => 'action'})).should == SampleController
|
47
46
|
end
|
48
47
|
|
49
48
|
it "should raise based upon an invalid param" do
|
50
49
|
route_set.add_named_route(:sample, '/sample/:action', :controller => 'sample', :requirements => {:action => /\d+/})
|
51
|
-
proc { route_set.recognize(
|
50
|
+
proc { route_set.recognize(build_request_mock_rails23('/sample/asdqwe', :post, {})) }.should raise_error
|
52
51
|
end
|
53
52
|
|
54
53
|
it "should raise based upon an invalid route" do
|
55
54
|
route_set.add_named_route(:sample, '/sample', :controller => 'sample', :action => 'test')
|
56
|
-
proc { route_set.recognize(
|
55
|
+
proc { route_set.recognize(build_request_mock_rails23('/test/asdqwe', :post, {})) }.should raise_error
|
57
56
|
end
|
58
57
|
|
59
58
|
it "should add /:controller and /:controller/:action if /:controller/:action/:id is added" do
|
@@ -63,17 +62,17 @@ describe "Usher (for rails 2.3) route recognition" do
|
|
63
62
|
|
64
63
|
it "should correctly recognize a format (dynamic path path with . delimiter)" do
|
65
64
|
route_set.add_route('/:controller/:action/:id.:format')
|
66
|
-
route_set.recognize(
|
65
|
+
route_set.recognize(build_request_mock_rails23('/sample/test/123.html', 'get', {:controller => 'sample', :action => 'test', :id => '123', :format => 'html'})).should == SampleController
|
67
66
|
end
|
68
67
|
|
69
68
|
it "should support root nodes" do
|
70
69
|
route_set.add_route('/', :controller => 'sample')
|
71
|
-
route_set.recognize(
|
70
|
+
route_set.recognize(build_request_mock_rails23('/', :get, {:controller => 'sample', :action => 'index'})).should == SampleController
|
72
71
|
end
|
73
72
|
|
74
73
|
it "should default action to 'index' when controller (and not index) is specified" do
|
75
74
|
route_set.add_route('/:controller/:action')
|
76
|
-
route_set.recognize(
|
75
|
+
route_set.recognize(build_request_mock_rails23('/sample', :get, {:controller => 'sample', :action => 'index'})).should == SampleController
|
77
76
|
end
|
78
77
|
|
79
78
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: usher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Neighman
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-03-03 00:00:00 -05:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -76,7 +76,7 @@ files:
|
|
76
76
|
- lib/usher/util/mapper.rb
|
77
77
|
- lib/usher/util/parser.rb
|
78
78
|
- lib/usher/util/rack-mixins.rb
|
79
|
-
- rails
|
79
|
+
- lib/usher/util/rails.rb
|
80
80
|
- spec/private/delimiters_spec.rb
|
81
81
|
- spec/private/destination_spec.rb
|
82
82
|
- spec/private/email/recognize_spec.rb
|
data/rails/init.rb
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
rails_version = "#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}"
|
2
|
-
|
3
|
-
case rails_version
|
4
|
-
when '2.3'
|
5
|
-
ActionController::Routing.module_eval "remove_const(:Routes); Routes = Usher::Interface.for(:rails23)"
|
6
|
-
|
7
|
-
when '2.2'
|
8
|
-
class Usher::Interface::Rails22::Mapper
|
9
|
-
include ActionController::Resources
|
10
|
-
end
|
11
|
-
ActionController::Routing.module_eval "remove_const(:Routes); Routes = Usher::Interface.for(:rails22)"
|
12
|
-
|
13
|
-
when '2.0'
|
14
|
-
class Usher::Interface::Rails20::Mapper
|
15
|
-
include ActionController::Resources
|
16
|
-
end
|
17
|
-
|
18
|
-
ActionController::Routing.module_eval <<CODE
|
19
|
-
remove_const(:Routes);
|
20
|
-
interface = Usher::Interface.for(:rails20);
|
21
|
-
interface.configuration_file = File.join(RAILS_ROOT, 'config', 'routes.rb')
|
22
|
-
Routes = interface;
|
23
|
-
CODE
|
24
|
-
end
|