josh-rack-mount 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +28 -0
- data/lib/rack/mount.rb +18 -0
- data/lib/rack/mount/const.rb +39 -0
- data/lib/rack/mount/exceptions.rb +5 -0
- data/lib/rack/mount/generation.rb +9 -0
- data/lib/rack/mount/generation/optimizations.rb +83 -0
- data/lib/rack/mount/generation/route.rb +108 -0
- data/lib/rack/mount/generation/route_set.rb +60 -0
- data/lib/rack/mount/mappers/merb.rb +143 -0
- data/lib/rack/mount/mappers/rails_classic.rb +164 -0
- data/lib/rack/mount/mappers/rails_draft.rb +179 -0
- data/lib/rack/mount/mappers/simple.rb +37 -0
- data/lib/rack/mount/nested_set.rb +106 -0
- data/lib/rack/mount/path_prefix.rb +20 -0
- data/lib/rack/mount/recognition.rb +8 -0
- data/lib/rack/mount/recognition/route.rb +94 -0
- data/lib/rack/mount/recognition/route_set.rb +54 -0
- data/lib/rack/mount/regexp_with_named_groups.rb +38 -0
- data/lib/rack/mount/request.rb +30 -0
- data/lib/rack/mount/route.rb +63 -0
- data/lib/rack/mount/route_set.rb +34 -0
- data/lib/rack/mount/utils.rb +149 -0
- data/rails/init.rb +2 -0
- metadata +86 -0
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'action_controller'
|
2
|
+
|
3
|
+
module ActionController
|
4
|
+
module Routing
|
5
|
+
class RouteSet
|
6
|
+
NotFound = lambda { |env|
|
7
|
+
raise RoutingError, "No route matches #{env[::Rack::Mount::Const::PATH_INFO].inspect} with #{env.inspect}"
|
8
|
+
}
|
9
|
+
|
10
|
+
class Dispatcher
|
11
|
+
def initialize(options = {})
|
12
|
+
defaults = options[:defaults]
|
13
|
+
@glob_param = options.delete(:glob)
|
14
|
+
@app = controller(defaults) if bind_controller_const?
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
params = env[::Rack::Mount::Const::RACK_ROUTING_ARGS]
|
19
|
+
app = @app || controller(params)
|
20
|
+
merge_default_action!(params)
|
21
|
+
split_glob_param!(params) if @glob_param
|
22
|
+
|
23
|
+
# TODO: Rails response is not finalized by the controller
|
24
|
+
app.call(env).to_a
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def bind_controller_const?
|
29
|
+
if defined? Rails
|
30
|
+
Rails.env.production?
|
31
|
+
else
|
32
|
+
true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def controller(params)
|
37
|
+
if params && params.has_key?(:controller)
|
38
|
+
controller = "#{params[:controller].camelize}Controller"
|
39
|
+
ActiveSupport::Inflector.constantize(controller)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def merge_default_action!(params)
|
44
|
+
params[:action] ||= 'index'
|
45
|
+
end
|
46
|
+
|
47
|
+
def split_glob_param!(params)
|
48
|
+
params[@glob_param] = params[@glob_param].split('/')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module RouteExtensions
|
53
|
+
def segment_keys
|
54
|
+
path.names.compact.map(&:to_sym)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class NamedRouteCollection
|
59
|
+
private
|
60
|
+
def generate_optimisation_block(*args)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def draw
|
65
|
+
yield Mapper.new(self)
|
66
|
+
@set.add_route(NotFound, :path => /.*/)
|
67
|
+
install_helpers
|
68
|
+
@set.freeze
|
69
|
+
end
|
70
|
+
|
71
|
+
def clear!
|
72
|
+
routes.clear
|
73
|
+
named_routes.clear
|
74
|
+
@combined_regexp = nil
|
75
|
+
@routes_by_controller = nil
|
76
|
+
@set = ::Rack::Mount::RouteSet.new
|
77
|
+
end
|
78
|
+
|
79
|
+
def add_route(path, options = {})
|
80
|
+
clear! unless @set
|
81
|
+
|
82
|
+
if path.is_a?(String)
|
83
|
+
path = path.gsub('.:format', '(.:format)')
|
84
|
+
path = optionalize_trailing_dynamic_segments(path)
|
85
|
+
end
|
86
|
+
|
87
|
+
if conditions = options.delete(:conditions)
|
88
|
+
method = conditions.delete(:method)
|
89
|
+
end
|
90
|
+
|
91
|
+
name = options.delete(:name)
|
92
|
+
|
93
|
+
requirements = options.delete(:requirements) || {}
|
94
|
+
defaults = {}
|
95
|
+
options.each do |k, v|
|
96
|
+
if v.is_a?(Regexp)
|
97
|
+
requirements[k.to_sym] = options.delete(k)
|
98
|
+
else
|
99
|
+
defaults[k.to_sym] = options.delete(k)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
if path.is_a?(String)
|
104
|
+
glob = $1.to_sym if path =~ /\/\*(\w+)$/
|
105
|
+
path = ::Rack::Mount::Utils.convert_segment_string_to_regexp(path, requirements, %w( / . ? ))
|
106
|
+
end
|
107
|
+
|
108
|
+
app = Dispatcher.new(:defaults => defaults, :glob => glob)
|
109
|
+
|
110
|
+
conditions = { :method => method, :path => path }
|
111
|
+
route = @set.add_route(app, conditions, defaults, name)
|
112
|
+
route.extend(RouteExtensions)
|
113
|
+
route
|
114
|
+
end
|
115
|
+
|
116
|
+
def add_named_route(name, path, options = {})
|
117
|
+
options[:name] = name
|
118
|
+
named_routes[name.to_sym] = add_route(path, options)
|
119
|
+
end
|
120
|
+
|
121
|
+
def generate(options, recall = {}, method = :generate)
|
122
|
+
named_route = options.delete(:use_route)
|
123
|
+
expire_on = build_expiry(options, recall)
|
124
|
+
expire_on.each { |k, v| recall.delete(k) unless v }
|
125
|
+
options = recall.merge(options)
|
126
|
+
options.each { |k, v| options[k] = v.to_param }
|
127
|
+
@set.url_for(named_route, options)
|
128
|
+
end
|
129
|
+
|
130
|
+
def url_for(*args)
|
131
|
+
@set.url_for(*args)
|
132
|
+
end
|
133
|
+
|
134
|
+
def call(env)
|
135
|
+
@set.call(env)
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
def optionalize_trailing_dynamic_segments(path)
|
140
|
+
path = (path =~ /^\//) ? path.dup : "/#{path}"
|
141
|
+
optional, segments = true, []
|
142
|
+
|
143
|
+
old_segments = path.split('/')
|
144
|
+
old_segments.shift
|
145
|
+
length = old_segments.length
|
146
|
+
|
147
|
+
old_segments.reverse.each_with_index do |segment, index|
|
148
|
+
if optional && !(segment =~ /^:\w+$/) && !(segment =~ /^:\w+\(\.:format\)$/)
|
149
|
+
optional = false
|
150
|
+
end
|
151
|
+
|
152
|
+
if optional && index < length - 1
|
153
|
+
segments.unshift('(/', segment)
|
154
|
+
segments.push(')')
|
155
|
+
else
|
156
|
+
segments.unshift('/', segment)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
segments.join
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Mount
|
5
|
+
class RouteSet
|
6
|
+
def new_draw(&block)
|
7
|
+
mapper = Mappers::RailsDraft.new(self)
|
8
|
+
mapper.instance_eval(&block)
|
9
|
+
add_route(Mappers::RailsDraft::NotFound, :path => /.*/)
|
10
|
+
freeze
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Mappers
|
15
|
+
class RailsDraft
|
16
|
+
class RoutingError < StandardError; end
|
17
|
+
|
18
|
+
NotFound = lambda { |env|
|
19
|
+
raise RoutingError, "No route matches #{env[Const::PATH_INFO].inspect} with #{env.inspect}"
|
20
|
+
}
|
21
|
+
|
22
|
+
DynamicController = lambda { |env|
|
23
|
+
app = "#{env[Const::RACK_ROUTING_ARGS][:controller].camelize}Controller"
|
24
|
+
app = ActiveSupport::Inflector.constantize(app)
|
25
|
+
app.call(env)
|
26
|
+
}
|
27
|
+
|
28
|
+
def initialize(set)
|
29
|
+
require 'action_controller'
|
30
|
+
@set = set
|
31
|
+
@scope_stack = []
|
32
|
+
end
|
33
|
+
|
34
|
+
def get(path, options = {})
|
35
|
+
match(path, options.merge(:via => :get))
|
36
|
+
end
|
37
|
+
|
38
|
+
def post(path, options = {})
|
39
|
+
match(path, options.merge(:via => :post))
|
40
|
+
end
|
41
|
+
|
42
|
+
def put(path, options = {})
|
43
|
+
match(path, options.merge(:via => :put))
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete(path, options = {})
|
47
|
+
match(path, options.merge(:via => :delete))
|
48
|
+
end
|
49
|
+
|
50
|
+
def match(path, options = {}, &block)
|
51
|
+
if block
|
52
|
+
@scope_stack.push(options.merge({:path => path}))
|
53
|
+
begin
|
54
|
+
instance_eval(&block)
|
55
|
+
ensure
|
56
|
+
@scope_stack.pop
|
57
|
+
end
|
58
|
+
|
59
|
+
return
|
60
|
+
end
|
61
|
+
|
62
|
+
new_options = {}
|
63
|
+
method = options.delete(:via)
|
64
|
+
requirements = options.delete(:constraints) || {}
|
65
|
+
defaults = {}
|
66
|
+
|
67
|
+
if path.is_a?(Symbol) && scope_options.has_key?(:path)
|
68
|
+
defaults[:action] = path.to_s
|
69
|
+
path = scope_options[:path]
|
70
|
+
elsif path.is_a?(Regexp)
|
71
|
+
else
|
72
|
+
scoped_path = @scope_stack.map { |scope| scope[:path] }.compact
|
73
|
+
scoped_path << path if path.is_a?(String)
|
74
|
+
scoped_path.map! { |path| path =~ /^\// ? path : "/#{path}" }
|
75
|
+
path = scoped_path.join
|
76
|
+
end
|
77
|
+
|
78
|
+
if path.is_a?(String)
|
79
|
+
path = optionalize_trailing_dynamic_segments(path)
|
80
|
+
end
|
81
|
+
|
82
|
+
if controller = scope_options[:controller]
|
83
|
+
defaults[:controller] = controller.to_s
|
84
|
+
end
|
85
|
+
|
86
|
+
if to = options.delete(:to)
|
87
|
+
controller, action = to.to_s.split('#')
|
88
|
+
|
89
|
+
if controller && action && defaults[:controller]
|
90
|
+
defaults[:controller] = "#{defaults[:controller]}#{controller}"
|
91
|
+
defaults[:action] = action
|
92
|
+
elsif !action && defaults[:controller]
|
93
|
+
defaults[:action] = controller if controller
|
94
|
+
else
|
95
|
+
defaults[:controller] = controller if controller
|
96
|
+
defaults[:action] = action if action
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
app = defaults.has_key?(:controller) ?
|
101
|
+
ActiveSupport::Inflector.constantize("#{defaults[:controller].camelize}Controller") :
|
102
|
+
DynamicController
|
103
|
+
|
104
|
+
if path.is_a?(String)
|
105
|
+
path = Utils.convert_segment_string_to_regexp(path, requirements, %w( / . ? ))
|
106
|
+
end
|
107
|
+
conditions = { :method => method, :path => path }
|
108
|
+
@set.add_route(app, conditions, defaults)
|
109
|
+
end
|
110
|
+
|
111
|
+
def controller(controller, &block)
|
112
|
+
@scope_stack.push(:controller => controller)
|
113
|
+
begin
|
114
|
+
instance_eval(&block)
|
115
|
+
ensure
|
116
|
+
@scope_stack.pop
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def namespace(namespace, &block)
|
121
|
+
@scope_stack.push(:path => namespace.to_s, :controller => "#{namespace}/")
|
122
|
+
begin
|
123
|
+
instance_eval(&block)
|
124
|
+
ensure
|
125
|
+
@scope_stack.pop
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def resources(*entities, &block)
|
130
|
+
options = entities.extract_options!
|
131
|
+
entities.each { |entity| map_resource(entity, options.dup, &block) }
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
def scope_options
|
136
|
+
options = {}
|
137
|
+
@scope_stack.each { |opts| options.merge!(opts) }
|
138
|
+
options
|
139
|
+
end
|
140
|
+
|
141
|
+
def map_resource(entities, options = {}, &block)
|
142
|
+
resource = ActionController::Resources::Resource.new(entities, options)
|
143
|
+
|
144
|
+
get(resource.path, :to => "#{resource.controller}#index")
|
145
|
+
post(resource.path, :to => "#{resource.controller}#create")
|
146
|
+
get(resource.new_path, :to => "#{resource.controller}#new")
|
147
|
+
get("#{resource.member_path}/edit", :to => "#{resource.controller}#edit")
|
148
|
+
get(resource.member_path, :to => "#{resource.controller}#show")
|
149
|
+
put(resource.member_path, :to => "#{resource.controller}#update")
|
150
|
+
delete(resource.member_path, :to => "#{resource.controller}#destroy")
|
151
|
+
end
|
152
|
+
|
153
|
+
def optionalize_trailing_dynamic_segments(path)
|
154
|
+
path = (path =~ /^\//) ? path.dup : "/#{path}"
|
155
|
+
optional, segments = true, []
|
156
|
+
|
157
|
+
old_segments = path.split('/')
|
158
|
+
old_segments.shift
|
159
|
+
length = old_segments.length
|
160
|
+
|
161
|
+
old_segments.reverse.each_with_index do |segment, index|
|
162
|
+
if optional && !(segment =~ /^:\w+$/) && !(segment =~ /^:\w+\(\.:format\)$/)
|
163
|
+
optional = false
|
164
|
+
end
|
165
|
+
|
166
|
+
if optional && index < length - 1
|
167
|
+
segments.unshift('(/', segment)
|
168
|
+
segments.push(')')
|
169
|
+
else
|
170
|
+
segments.unshift('/', segment)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
segments.join
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Rack
|
2
|
+
module Mount
|
3
|
+
class RouteSet
|
4
|
+
def prepare
|
5
|
+
map = Mappers::Simple.new(self)
|
6
|
+
yield map
|
7
|
+
freeze
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Mappers
|
12
|
+
class Simple
|
13
|
+
def initialize(set)
|
14
|
+
@set = set
|
15
|
+
end
|
16
|
+
|
17
|
+
def map(*args)
|
18
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
19
|
+
|
20
|
+
app = options[:to]
|
21
|
+
path = args[0]
|
22
|
+
method = args[1]
|
23
|
+
defaults = options[:with]
|
24
|
+
|
25
|
+
requirements = options[:conditions] || {}
|
26
|
+
requirements.each { |k,v| requirements[k] = v.to_s unless v.is_a?(Regexp) }
|
27
|
+
|
28
|
+
if path.is_a?(String)
|
29
|
+
path = Utils.convert_segment_string_to_regexp(path, requirements, %w( / . ? ))
|
30
|
+
end
|
31
|
+
conditions = { :method => method, :path => path }
|
32
|
+
@set.add_route(app, conditions, defaults)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Rack
|
2
|
+
module Mount
|
3
|
+
class NestedSet < Hash #:nodoc:
|
4
|
+
class List < Array #:nodoc:
|
5
|
+
def freeze
|
6
|
+
each { |e| e.freeze }
|
7
|
+
super
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(default = List.new)
|
12
|
+
super(default)
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method :at, :[]
|
16
|
+
|
17
|
+
WILD_REGEXP = /.*/.freeze
|
18
|
+
|
19
|
+
def []=(*args)
|
20
|
+
args = args.flatten
|
21
|
+
value = args.pop
|
22
|
+
key = args.shift.freeze
|
23
|
+
key = WILD_REGEXP if key.nil?
|
24
|
+
keys = args.freeze
|
25
|
+
|
26
|
+
raise ArgumentError, 'missing value' unless value
|
27
|
+
|
28
|
+
case key
|
29
|
+
when Regexp
|
30
|
+
if keys.empty?
|
31
|
+
each { |k, v| v << value if key =~ k }
|
32
|
+
default << value
|
33
|
+
else
|
34
|
+
each { |k, v| v[keys.dup] = value if key =~ k }
|
35
|
+
self.default = NestedSet.new(default) if default.is_a?(List)
|
36
|
+
default[keys.dup] = value
|
37
|
+
end
|
38
|
+
when String
|
39
|
+
v = at(key)
|
40
|
+
v = v.dup if v.equal?(default)
|
41
|
+
|
42
|
+
if keys.empty?
|
43
|
+
v << value
|
44
|
+
else
|
45
|
+
v = NestedSet.new(v) if v.is_a?(List)
|
46
|
+
v[keys.dup] = value
|
47
|
+
end
|
48
|
+
|
49
|
+
super(key, v)
|
50
|
+
else
|
51
|
+
raise ArgumentError, 'unsupported key'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def [](*keys)
|
56
|
+
result, i = self, 0
|
57
|
+
until result.is_a?(Array)
|
58
|
+
result = result.at(keys[i])
|
59
|
+
i += 1
|
60
|
+
end
|
61
|
+
result
|
62
|
+
end
|
63
|
+
|
64
|
+
def <<(value)
|
65
|
+
values_with_default.each { |e| e << value }
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def values_with_default
|
70
|
+
values.push(default)
|
71
|
+
end
|
72
|
+
|
73
|
+
def inspect
|
74
|
+
super.gsub(/\}$/, ", nil => #{default.inspect}}")
|
75
|
+
end
|
76
|
+
|
77
|
+
def freeze
|
78
|
+
values_with_default.each { |v| v.freeze }
|
79
|
+
super
|
80
|
+
end
|
81
|
+
|
82
|
+
def lists
|
83
|
+
descendants = []
|
84
|
+
values_with_default.each do |v|
|
85
|
+
if v.is_a?(NestedSet)
|
86
|
+
v.lists.each do |descendant|
|
87
|
+
descendants << descendant
|
88
|
+
end
|
89
|
+
else
|
90
|
+
descendants << v
|
91
|
+
end
|
92
|
+
end
|
93
|
+
descendants
|
94
|
+
end
|
95
|
+
|
96
|
+
def height
|
97
|
+
longest_list_descendant.length
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
def longest_list_descendant
|
102
|
+
lists.max { |a, b| a.length <=> b.length }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|