josh-rack-mount 0.0.1
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/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
|