joe-merb-core 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/CHANGELOG +992 -0
  2. data/CONTRIBUTORS +94 -0
  3. data/LICENSE +20 -0
  4. data/PUBLIC_CHANGELOG +142 -0
  5. data/README +21 -0
  6. data/Rakefile +456 -0
  7. data/TODO +0 -0
  8. data/bin/merb +11 -0
  9. data/bin/merb-specs +5 -0
  10. data/lib/merb-core.rb +648 -0
  11. data/lib/merb-core/autoload.rb +31 -0
  12. data/lib/merb-core/bootloader.rb +889 -0
  13. data/lib/merb-core/config.rb +380 -0
  14. data/lib/merb-core/constants.rb +45 -0
  15. data/lib/merb-core/controller/abstract_controller.rb +620 -0
  16. data/lib/merb-core/controller/exceptions.rb +302 -0
  17. data/lib/merb-core/controller/merb_controller.rb +283 -0
  18. data/lib/merb-core/controller/mime.rb +111 -0
  19. data/lib/merb-core/controller/mixins/authentication.rb +123 -0
  20. data/lib/merb-core/controller/mixins/conditional_get.rb +83 -0
  21. data/lib/merb-core/controller/mixins/controller.rb +316 -0
  22. data/lib/merb-core/controller/mixins/render.rb +513 -0
  23. data/lib/merb-core/controller/mixins/responder.rb +469 -0
  24. data/lib/merb-core/controller/template.rb +254 -0
  25. data/lib/merb-core/core_ext.rb +9 -0
  26. data/lib/merb-core/core_ext/hash.rb +7 -0
  27. data/lib/merb-core/core_ext/kernel.rb +345 -0
  28. data/lib/merb-core/dispatch/cookies.rb +130 -0
  29. data/lib/merb-core/dispatch/default_exception/default_exception.rb +93 -0
  30. data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +200 -0
  31. data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +77 -0
  32. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +98 -0
  33. data/lib/merb-core/dispatch/dispatcher.rb +172 -0
  34. data/lib/merb-core/dispatch/request.rb +718 -0
  35. data/lib/merb-core/dispatch/router.rb +228 -0
  36. data/lib/merb-core/dispatch/router/behavior.rb +610 -0
  37. data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
  38. data/lib/merb-core/dispatch/router/resources.rb +220 -0
  39. data/lib/merb-core/dispatch/router/route.rb +560 -0
  40. data/lib/merb-core/dispatch/session.rb +222 -0
  41. data/lib/merb-core/dispatch/session/container.rb +74 -0
  42. data/lib/merb-core/dispatch/session/cookie.rb +173 -0
  43. data/lib/merb-core/dispatch/session/memcached.rb +68 -0
  44. data/lib/merb-core/dispatch/session/memory.rb +99 -0
  45. data/lib/merb-core/dispatch/session/store_container.rb +150 -0
  46. data/lib/merb-core/dispatch/worker.rb +28 -0
  47. data/lib/merb-core/gem_ext/erubis.rb +77 -0
  48. data/lib/merb-core/logger.rb +215 -0
  49. data/lib/merb-core/plugins.rb +67 -0
  50. data/lib/merb-core/rack.rb +27 -0
  51. data/lib/merb-core/rack/adapter.rb +47 -0
  52. data/lib/merb-core/rack/adapter/ebb.rb +24 -0
  53. data/lib/merb-core/rack/adapter/evented_mongrel.rb +13 -0
  54. data/lib/merb-core/rack/adapter/fcgi.rb +17 -0
  55. data/lib/merb-core/rack/adapter/irb.rb +119 -0
  56. data/lib/merb-core/rack/adapter/mongrel.rb +33 -0
  57. data/lib/merb-core/rack/adapter/runner.rb +28 -0
  58. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +14 -0
  59. data/lib/merb-core/rack/adapter/thin.rb +40 -0
  60. data/lib/merb-core/rack/adapter/thin_turbo.rb +17 -0
  61. data/lib/merb-core/rack/adapter/webrick.rb +72 -0
  62. data/lib/merb-core/rack/application.rb +32 -0
  63. data/lib/merb-core/rack/handler/mongrel.rb +96 -0
  64. data/lib/merb-core/rack/middleware.rb +20 -0
  65. data/lib/merb-core/rack/middleware/conditional_get.rb +29 -0
  66. data/lib/merb-core/rack/middleware/content_length.rb +18 -0
  67. data/lib/merb-core/rack/middleware/csrf.rb +73 -0
  68. data/lib/merb-core/rack/middleware/path_prefix.rb +31 -0
  69. data/lib/merb-core/rack/middleware/profiler.rb +19 -0
  70. data/lib/merb-core/rack/middleware/static.rb +45 -0
  71. data/lib/merb-core/rack/middleware/tracer.rb +20 -0
  72. data/lib/merb-core/server.rb +321 -0
  73. data/lib/merb-core/tasks/audit.rake +68 -0
  74. data/lib/merb-core/tasks/gem_management.rb +252 -0
  75. data/lib/merb-core/tasks/merb.rb +2 -0
  76. data/lib/merb-core/tasks/merb_rake_helper.rb +51 -0
  77. data/lib/merb-core/tasks/stats.rake +71 -0
  78. data/lib/merb-core/test.rb +17 -0
  79. data/lib/merb-core/test/helpers.rb +10 -0
  80. data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
  81. data/lib/merb-core/test/helpers/multipart_request_helper.rb +176 -0
  82. data/lib/merb-core/test/helpers/request_helper.rb +61 -0
  83. data/lib/merb-core/test/helpers/route_helper.rb +47 -0
  84. data/lib/merb-core/test/helpers/view_helper.rb +121 -0
  85. data/lib/merb-core/test/matchers.rb +10 -0
  86. data/lib/merb-core/test/matchers/controller_matchers.rb +108 -0
  87. data/lib/merb-core/test/matchers/route_matchers.rb +137 -0
  88. data/lib/merb-core/test/matchers/view_matchers.rb +393 -0
  89. data/lib/merb-core/test/run_specs.rb +141 -0
  90. data/lib/merb-core/test/tasks/spectasks.rb +68 -0
  91. data/lib/merb-core/test/test_ext/hpricot.rb +32 -0
  92. data/lib/merb-core/test/test_ext/object.rb +14 -0
  93. data/lib/merb-core/test/test_ext/string.rb +14 -0
  94. data/lib/merb-core/vendor/facets.rb +2 -0
  95. data/lib/merb-core/vendor/facets/dictionary.rb +433 -0
  96. data/lib/merb-core/vendor/facets/inflect.rb +342 -0
  97. data/lib/merb-core/version.rb +3 -0
  98. metadata +253 -0
@@ -0,0 +1,52 @@
1
+ module Merb
2
+
3
+ class Router
4
+ # Cache procs for future reference in eval statement
5
+ class CachedProc
6
+ @@index = 0
7
+ @@list = []
8
+
9
+ attr_accessor :cache, :index
10
+
11
+ # ==== Parameters
12
+ # cache<Proc>:: The block of code to cache.
13
+ def initialize(cache)
14
+ @cache, @index = cache, CachedProc.register(self)
15
+ end
16
+
17
+ # ==== Returns
18
+ # String:: The CachedProc object in a format embeddable within a string.
19
+ def to_s
20
+ "CachedProc[#{@index}].cache"
21
+ end
22
+
23
+ class << self
24
+
25
+ # ==== Parameters
26
+ # cached_code<CachedProc>:: The cached code to register.
27
+ #
28
+ # ==== Returns
29
+ # Fixnum:: The index of the newly registered CachedProc.
30
+ def register(cached_code)
31
+ CachedProc[@@index] = cached_code
32
+ @@index += 1
33
+ @@index - 1
34
+ end
35
+
36
+ # Sets the cached code for a specific index.
37
+ #
38
+ # ==== Parameters
39
+ # index<Fixnum>:: The index of the cached code to set.
40
+ # code<CachedProc>:: The cached code to set.
41
+ def []=(index, code) @@list[index] = code end
42
+
43
+ # ==== Parameters
44
+ # index<Fixnum>:: The index of the cached code to retrieve.
45
+ #
46
+ # ==== Returns
47
+ # CachedProc:: The cached code at index.
48
+ def [](index) @@list[index] end
49
+ end
50
+ end # CachedProc
51
+ end
52
+ end
@@ -0,0 +1,220 @@
1
+ module Merb
2
+ class Router
3
+ class Behavior
4
+ module Resources
5
+
6
+ # Behavior#+resources+ is a route helper for defining a collection of
7
+ # RESTful resources. It yields to a block for child routes.
8
+ #
9
+ # ==== Parameters
10
+ # name<String, Symbol>:: The name of the resources
11
+ # options<Hash>::
12
+ # Ovverides and parameters to be associated with the route
13
+ #
14
+ # ==== Options (options)
15
+ # :namespace<~to_s>: The namespace for this route.
16
+ # :name_prefix<~to_s>:
17
+ # A prefix for the named routes. If a namespace is passed and there
18
+ # isn't a name prefix, the namespace will become the prefix.
19
+ # :controller<~to_s>: The controller for this route
20
+ # :collection<~to_s>: Special settings for the collections routes
21
+ # :member<Hash>:
22
+ # Special settings and resources related to a specific member of this
23
+ # resource.
24
+ # :keys<Array>:
25
+ # A list of the keys to be used instead of :id with the resource in the order of the url.
26
+ # :singular<Symbol>
27
+ #
28
+ # ==== Block parameters
29
+ # next_level<Behavior>:: The child behavior.
30
+ #
31
+ # ==== Returns
32
+ # Array::
33
+ # Routes which will define the specified RESTful collection of resources
34
+ #
35
+ # ==== Examples
36
+ #
37
+ # r.resources :posts # will result in the typical RESTful CRUD
38
+ # # lists resources
39
+ # # GET /posts/?(\.:format)? :action => "index"
40
+ # # GET /posts/index(\.:format)? :action => "index"
41
+ #
42
+ # # shows new resource form
43
+ # # GET /posts/new :action => "new"
44
+ #
45
+ # # creates resource
46
+ # # POST /posts/?(\.:format)?, :action => "create"
47
+ #
48
+ # # shows resource
49
+ # # GET /posts/:id(\.:format)? :action => "show"
50
+ #
51
+ # # shows edit form
52
+ # # GET /posts/:id/edit :action => "edit"
53
+ #
54
+ # # updates resource
55
+ # # PUT /posts/:id(\.:format)? :action => "update"
56
+ #
57
+ # # shows deletion confirmation page
58
+ # # GET /posts/:id/delete :action => "delete"
59
+ #
60
+ # # destroys resources
61
+ # # DELETE /posts/:id(\.:format)? :action => "destroy"
62
+ #
63
+ # # Nesting resources
64
+ # r.resources :posts do |posts|
65
+ # posts.resources :comments
66
+ # end
67
+ #---
68
+ # @public
69
+ def resources(name, *args, &block)
70
+ name = name.to_s
71
+ options = extract_options_from_args!(args) || {}
72
+ singular = options[:singular] ? options[:singular].to_s : Extlib::Inflection.singularize(name)
73
+ klass = args.first ? args.first.to_s : Extlib::Inflection.classify(singular)
74
+ keys = [ options.delete(:keys) || options.delete(:key) || :id ].flatten
75
+ params = { :controller => options.delete(:controller) || name }
76
+ collection = options.delete(:collection) || {}
77
+ member = { :edit => :get, :delete => :get }.merge(options.delete(:member) || {})
78
+
79
+ # Try pulling :namespace out of options for backwards compatibility
80
+ options[:name_prefix] ||= nil # Don't use a name_prefix if not needed
81
+ options[:resource_prefix] ||= nil # Don't use a resource_prefix if not needed
82
+ options[:controller_prefix] ||= options.delete(:namespace)
83
+
84
+ self.namespace(name, options).to(params) do |resource|
85
+ root_keys = keys.map { |k| ":#{k}" }.join("/")
86
+
87
+ # => index
88
+ resource.match("(/index)(.:format)", :method => :get).to(:action => "index").
89
+ name(name).register_resource(name)
90
+
91
+ # => create
92
+ resource.match("(.:format)", :method => :post).to(:action => "create")
93
+
94
+ # => new
95
+ resource.match("/new(.:format)", :method => :get).to(:action => "new").
96
+ name("new", singular).register_resource(name, "new")
97
+
98
+ # => user defined collection routes
99
+ collection.each_pair do |action, method|
100
+ action = action.to_s
101
+ resource.match("/#{action}(.:format)", :method => method).to(:action => "#{action}").
102
+ name(action, name).register_resource(name, action)
103
+ end
104
+
105
+ # => show
106
+ resource.match("/#{root_keys}(.:format)", :method => :get).to(:action => "show").
107
+ name(singular).register_resource(klass)
108
+
109
+ # => user defined member routes
110
+ member.each_pair do |action, method|
111
+ action = action.to_s
112
+ resource.match("/#{root_keys}/#{action}(.:format)", :method => method).
113
+ to(:action => "#{action}").name(action, singular).register_resource(klass, action)
114
+ end
115
+
116
+ # => update
117
+ resource.match("/#{root_keys}(.:format)", :method => :put).
118
+ to(:action => "update")
119
+
120
+ # => destroy
121
+ resource.match("/#{root_keys}(.:format)", :method => :delete).
122
+ to(:action => "destroy")
123
+
124
+ if block_given?
125
+ nested_keys = keys.map { |k| k.to_s == "id" ? ":#{singular}_id" : ":#{k}" }.join("/")
126
+ resource.options(:name_prefix => singular, :resource_prefix => klass).match("/#{nested_keys}", &block)
127
+ end
128
+
129
+ end
130
+ end
131
+
132
+ # Behavior#+resource+ is a route helper for defining a singular RESTful
133
+ # resource. It yields to a block for child routes.
134
+ #
135
+ # ==== Parameters
136
+ # name<String, Symbol>:: The name of the resource.
137
+ # options<Hash>::
138
+ # Overides and parameters to be associated with the route.
139
+ #
140
+ # ==== Options (options)
141
+ # :namespace<~to_s>: The namespace for this route.
142
+ # :name_prefix<~to_s>:
143
+ # A prefix for the named routes. If a namespace is passed and there
144
+ # isn't a name prefix, the namespace will become the prefix.
145
+ # :controller<~to_s>: The controller for this route
146
+ #
147
+ # ==== Block parameters
148
+ # next_level<Behavior>:: The child behavior.
149
+ #
150
+ # ==== Returns
151
+ # Array:: Routes which define a RESTful single resource.
152
+ #
153
+ # ==== Examples
154
+ #
155
+ # r.resource :account # will result in the typical RESTful CRUD
156
+ # # shows new resource form
157
+ # # GET /account/new :action => "new"
158
+ #
159
+ # # creates resource
160
+ # # POST /account/?(\.:format)?, :action => "create"
161
+ #
162
+ # # shows resource
163
+ # # GET /account/(\.:format)? :action => "show"
164
+ #
165
+ # # shows edit form
166
+ # # GET /account//edit :action => "edit"
167
+ #
168
+ # # updates resource
169
+ # # PUT /account/(\.:format)? :action => "update"
170
+ #
171
+ # # shows deletion confirmation page
172
+ # # GET /account//delete :action => "delete"
173
+ #
174
+ # # destroys resources
175
+ # # DELETE /account/(\.:format)? :action => "destroy"
176
+ #
177
+ # You can optionally pass :namespace and :controller to refine the routing
178
+ # or pass a block to nest resources.
179
+ #
180
+ # r.resource :account, :namespace => "admin" do |account|
181
+ # account.resources :preferences, :controller => "settings"
182
+ # end
183
+ # ---
184
+ # @public
185
+ def resource(name, *args, &block)
186
+ name = name.to_s
187
+ options = extract_options_from_args!(args) || {}
188
+ params = { :controller => options.delete(:controller) || name.pluralize }
189
+
190
+ options[:name_prefix] ||= nil # Don't use a name_prefix if not needed
191
+ options[:resource_prefix] ||= nil # Don't use a resource_prefix if not needed
192
+ options[:controller_prefix] ||= options.delete(:namespace)
193
+
194
+ self.namespace(name, options).to(params) do |resource|
195
+ resource.match("(.:format)", :method => :get ).to(:action => "show" ).name(name).register_resource(name)
196
+ resource.match("(.:format)", :method => :post ).to(:action => "create" )
197
+ resource.match("(.:format)", :method => :put ).to(:action => "update" )
198
+ resource.match("(.:format)", :method => :delete).to(:action => "destroy")
199
+ resource.match("/new(.:format)", :method => :get ).to(:action => "new" ).name(:new, name).register_resource(name, "new")
200
+ resource.match("/edit(.:format)", :method => :get ).to(:action => "edit" ).name(:edit, name).register_resource(name, "edit")
201
+ resource.match("/delete(.:format)", :method => :get ).to(:action => "delete" ).name(:delete, name).register_resource(name, "delete")
202
+
203
+ resource.options(:name_prefix => name, :resource_prefix => name, &block) if block_given?
204
+ end
205
+ end
206
+
207
+ protected
208
+
209
+ def register_resource(*key)
210
+ key = [@options[:resource_prefix], key].flatten.compact
211
+ @route.resource = key
212
+ self
213
+ end
214
+
215
+ end
216
+
217
+ include Resources
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,560 @@
1
+ module Merb
2
+
3
+ class Router
4
+ # This entire class is private and should never be accessed outside of
5
+ # Merb::Router and Behavior
6
+ class Route #:nodoc:
7
+ SEGMENT_REGEXP = /(:([a-z](_?[a-z0-9])*))/
8
+ OPTIONAL_SEGMENT_REGEX = /^.*?([\(\)])/i
9
+ SEGMENT_REGEXP_WITH_BRACKETS = /(:[a-z_]+)(\[(\d+)\])?/
10
+ JUST_BRACKETS = /\[(\d+)\]/
11
+ SEGMENT_CHARACTERS = "[^\/.,;?]".freeze
12
+
13
+ attr_reader :conditions, :params, :segments
14
+ attr_reader :index, :variables, :name
15
+ attr_accessor :fixation
16
+
17
+ def initialize(conditions, params, deferred_procs, options = {})
18
+ @conditions, @params = conditions, params
19
+
20
+ if options[:redirects]
21
+ @redirects = true
22
+ @redirect_status = @params[:status]
23
+ @redirect_url = @params[:url]
24
+ @defaults = {}
25
+ else
26
+ @defaults = options[:defaults] || {}
27
+ end
28
+
29
+ # @conditional_block = conditional_block
30
+
31
+ @identifiers = options[:identifiers]
32
+ @deferred_procs = deferred_procs
33
+ @segments = []
34
+ @symbol_conditions = {}
35
+ @placeholders = {}
36
+ compile
37
+ end
38
+
39
+ def regexp?
40
+ @regexp
41
+ end
42
+
43
+ def allow_fixation?
44
+ @fixation
45
+ end
46
+
47
+ def to_s
48
+ regexp? ?
49
+ "/#{conditions[:path].source}/" :
50
+ segment_level_to_s(segments)
51
+ end
52
+
53
+ alias_method :inspect, :to_s
54
+
55
+ def register
56
+ @index = Merb::Router.routes.size
57
+ Merb::Router.routes << self
58
+ self
59
+ end
60
+
61
+ # Sets the route as a resource route with the given key as the
62
+ # lookup key.
63
+ def resource=(key)
64
+ Router.resource_routes[key] = self
65
+ key
66
+ end
67
+
68
+ def name=(name)
69
+ @name = name.to_sym
70
+ Router.named_routes[@name] = self
71
+ @name
72
+ end
73
+
74
+ # === Compiled method ===
75
+ def generate(args = [], defaults = {})
76
+ raise GenerationError, "Cannot generate regexp Routes" if regexp?
77
+
78
+ params = extract_options_from_args!(args) || { }
79
+
80
+ # Support for anonymous params
81
+ unless args.empty?
82
+ # First, let's determine which variables are missing
83
+ variables = @variables - params.keys
84
+
85
+ raise GenerationError, "The route has #{@variables.length} variables: #{@variables.inspect}" if args.length > variables.length
86
+
87
+ args.each_with_index do |param, i|
88
+ params[variables[i]] ||= param
89
+ end
90
+ end
91
+
92
+ uri = @generator[params, defaults] or raise GenerationError, "Named route #{name} could not be generated with #{params.inspect}"
93
+ uri = Merb::Config[:path_prefix] + uri if Merb::Config[:path_prefix]
94
+ uri
95
+ end
96
+
97
+ def compiled_statement(first)
98
+ els_if = first ? ' if ' : ' elsif '
99
+
100
+ code = ""
101
+ code << els_if << condition_statements.join(" && ") << "\n"
102
+
103
+ # First, we need to always return the value of the
104
+ # deferred block if it explicitly matched the route
105
+ if @redirects && @deferred_procs.any?
106
+ code << " return [#{@index.inspect}, block_result] if request.matched?" << "\n"
107
+ code << " request.redirects!" << "\n"
108
+ code << " [#{@index.inspect}, { :url => #{@redirect_url.inspect}, :status => #{@redirect_status.inspect} }]" << "\n"
109
+ elsif @redirects
110
+ code << " request.redirects!" << "\n"
111
+ code << " [#{@index.inspect}, { :url => #{@redirect_url.inspect}, :status => #{@redirect_status.inspect} }]" << "\n"
112
+ elsif @deferred_procs.any?
113
+ code << " [#{@index.inspect}, block_result]" << "\n"
114
+ else
115
+ code << " [#{@index.inspect}, #{params_as_string}]" << "\n"
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ # === Compilation ===
122
+
123
+ def compile
124
+ compile_conditions
125
+ compile_params
126
+ @generator = Generator.new(@segments, @symbol_conditions, @identifiers).compiled
127
+ end
128
+
129
+ # The Generator class handles compiling the route down to a lambda that
130
+ # can generate the URL from a params hash and a default params hash.
131
+ class Generator #:nodoc:
132
+
133
+ def initialize(segments, symbol_conditions, identifiers)
134
+ @segments = segments
135
+ @symbol_conditions = symbol_conditions
136
+ @identifiers = identifiers
137
+ @stack = []
138
+ @opt_segment_count = 0
139
+ @opt_segment_stack = [[]]
140
+ end
141
+
142
+ def compiled
143
+ ruby = ""
144
+ ruby << "lambda do |params, defaults|\n"
145
+ ruby << " fragment = params.delete(:fragment)\n"
146
+ ruby << " query_params = params.dup\n"
147
+
148
+ with(@segments) do
149
+ ruby << " include_defaults = true\n"
150
+ ruby << " return unless url = #{block_for_level}\n"
151
+ end
152
+
153
+ ruby << " query_params.delete_if { |key, value| value.nil? }\n"
154
+ ruby << " unless query_params.empty?\n"
155
+ ruby << ' url << "?#{Merb::Request.params_to_query_string(query_params)}"' << "\n"
156
+ ruby << " end\n"
157
+ ruby << ' url << "##{fragment}" if fragment' << "\n"
158
+ ruby << " url\n"
159
+ ruby << "end\n"
160
+
161
+ eval(ruby)
162
+ end
163
+
164
+ private
165
+
166
+ # Cleans up methods a bunch. We don't need to pass the current segment
167
+ # level around everywhere anymore. It's kept track for us in the stack.
168
+ def with(segments, &block)
169
+ @stack.push(segments)
170
+ retval = yield
171
+ @stack.pop
172
+ retval
173
+ end
174
+
175
+ def segments
176
+ @stack.last || []
177
+ end
178
+
179
+ def symbol_segments
180
+ segments.flatten.select { |s| s.is_a?(Symbol) }
181
+ end
182
+
183
+ def current_segments
184
+ segments.select { |s| s.is_a?(Symbol) }
185
+ end
186
+
187
+ def nested_segments
188
+ segments.select { |s| s.is_a?(Array) }.flatten.select { |s| s.is_a?(Symbol) }
189
+ end
190
+
191
+ def block_for_level
192
+ ruby = ""
193
+ ruby << "if #{segment_level_matches_conditions}\n"
194
+ ruby << " #{remove_used_segments_in_query_path}\n"
195
+ ruby << " #{generate_optional_segments}\n"
196
+ ruby << %{ "#{combine_required_and_optional_segments}"\n}
197
+ ruby << "end"
198
+ end
199
+
200
+ def check_if_defaults_should_be_included
201
+ ruby = ""
202
+ ruby << "include_defaults = "
203
+ symbol_segments.each { |s| ruby << "params[#{s.inspect}] || " }
204
+ ruby << "false"
205
+ end
206
+
207
+ # --- Not so pretty ---
208
+ def segment_level_matches_conditions
209
+ conditions = current_segments.map do |segment|
210
+ condition = "(cached_#{segment} = params[#{segment.inspect}] || include_defaults && defaults[#{segment.inspect}])"
211
+
212
+ if @symbol_conditions[segment] && @symbol_conditions[segment].is_a?(Regexp)
213
+ condition << " =~ #{@symbol_conditions[segment].inspect}"
214
+ elsif @symbol_conditions[segment]
215
+ condition << " == #{@symbol_conditions[segment].inspect}"
216
+ end
217
+
218
+ condition
219
+ end
220
+
221
+ conditions << "true" if conditions.empty?
222
+ conditions.join(" && ")
223
+ end
224
+
225
+ def remove_used_segments_in_query_path
226
+ "#{current_segments.inspect}.each { |s| query_params.delete(s) }"
227
+ end
228
+
229
+ def generate_optional_segments
230
+ optionals = []
231
+
232
+ segments.each_with_index do |segment, i|
233
+ if segment.is_a?(Array) && segment.any? { |s| !s.is_a?(String) }
234
+ with(segment) do
235
+ @opt_segment_stack.last << (optional_name = "_optional_segments_#{@opt_segment_count += 1}")
236
+ @opt_segment_stack.push []
237
+ optionals << "#{check_if_defaults_should_be_included}\n"
238
+ optionals << "#{optional_name} = #{block_for_level}"
239
+ @opt_segment_stack.pop
240
+ end
241
+ end
242
+ end
243
+
244
+ optionals.join("\n")
245
+ end
246
+
247
+ def combine_required_and_optional_segments
248
+ bits = ""
249
+
250
+ segments.each_with_index do |segment, i|
251
+ bits << case
252
+ when segment.is_a?(String) then segment
253
+ when segment.is_a?(Symbol) then '#{param_for_route(cached_' + segment.to_s + ')}'
254
+ when segment.is_a?(Array) && segment.any? { |s| !s.is_a?(String) } then "\#{#{@opt_segment_stack.last.shift}}"
255
+ else ""
256
+ end
257
+ end
258
+
259
+ bits
260
+ end
261
+
262
+ def param_for_route(param)
263
+ case param
264
+ when String, Symbol, Numeric, TrueClass, FalseClass, NilClass
265
+ param
266
+ else
267
+ _, identifier = @identifiers.find { |klass, _| param.is_a?(klass) }
268
+ identifier ? param.send(identifier) : param
269
+ end
270
+ end
271
+
272
+ end
273
+
274
+ # === Conditions ===
275
+
276
+ def compile_conditions
277
+ @original_conditions = conditions.dup
278
+
279
+ if path = conditions[:path]
280
+ path = [path].flatten.compact
281
+ if path = compile_path(path)
282
+ conditions[:path] = Regexp.new("^#{path}$")
283
+ else
284
+ conditions.delete(:path)
285
+ end
286
+ end
287
+ end
288
+
289
+ # The path is passed in as an array of different parts. We basically have
290
+ # to concat all the parts together, then parse the path and extract the
291
+ # variables. However, if any of the parts are a regular expression, then
292
+ # we abort the parsing and just convert it to a regexp.
293
+ def compile_path(path)
294
+ @segments = []
295
+ compiled = ""
296
+
297
+ return nil if path.nil? || path.empty?
298
+
299
+ path.each do |part|
300
+ case part
301
+ when Regexp
302
+ @regexp = true
303
+ @segments = []
304
+ compiled << part.source.sub(/^\^/, '').sub(/\$$/, '')
305
+ when String
306
+ segments = segments_with_optionals_from_string(part.dup)
307
+ compile_path_segments(compiled, segments)
308
+ # Concat the segments
309
+ unless regexp?
310
+ if @segments[-1].is_a?(String) && segments[0].is_a?(String)
311
+ @segments[-1] << segments.shift
312
+ end
313
+ @segments.concat segments
314
+ end
315
+ else
316
+ raise ArgumentError.new("A route path can only be specified as a String or Regexp")
317
+ end
318
+ end
319
+
320
+ unless regexp?
321
+ @variables = @segments.flatten.select { |s| s.is_a?(Symbol) }
322
+ compiled.gsub!(%r[/+], '/')
323
+ compiled.gsub!(%r[(.+)/$], '\1')
324
+ end
325
+
326
+ compiled
327
+ end
328
+
329
+ # Simple nested parenthesis parser
330
+ def segments_with_optionals_from_string(path, nest_level = 0)
331
+ segments = []
332
+
333
+ # Extract all the segments at this parenthesis level
334
+ while segment = path.slice!(OPTIONAL_SEGMENT_REGEX)
335
+ # Append the segments that we came across so far
336
+ # at this level
337
+ segments.concat segments_from_string(segment[0..-2]) if segment.length > 1
338
+ # If the parenthesis that we came across is an opening
339
+ # then we need to jump to the higher level
340
+ if segment[-1,1] == '('
341
+ segments << segments_with_optionals_from_string(path, nest_level + 1)
342
+ else
343
+ # Throw an error if we can't actually go back down (aka syntax error)
344
+ raise "There are too many closing parentheses" if nest_level == 0
345
+ return segments
346
+ end
347
+ end
348
+
349
+ # Save any last bit of the string that didn't match the original regex
350
+ segments.concat segments_from_string(path) unless path.empty?
351
+
352
+ # Throw an error if the string should not actually be done (aka syntax error)
353
+ raise "You have too many opening parentheses" unless nest_level == 0
354
+
355
+ segments
356
+ end
357
+
358
+ def segments_from_string(path)
359
+ segments = []
360
+
361
+ while match = (path.match(SEGMENT_REGEXP))
362
+ segments << match.pre_match unless match.pre_match.empty?
363
+ segments << match[2].intern
364
+ path = match.post_match
365
+ end
366
+
367
+ segments << path unless path.empty?
368
+ segments
369
+ end
370
+
371
+ # --- Yeah, this could probably be refactored
372
+ def compile_path_segments(compiled, segments)
373
+ segments.each do |segment|
374
+ case segment
375
+ when String
376
+ compiled << Regexp.escape(segment)
377
+ when Symbol
378
+ condition = (@symbol_conditions[segment] ||= @conditions.delete(segment))
379
+ compiled << compile_segment_condition(condition)
380
+ # Create a param for the Symbol segment if none already exists
381
+ @params[segment] = "#{segment.inspect}" unless @params.has_key?(segment)
382
+ @placeholders[segment] ||= capturing_parentheses_count(compiled)
383
+ when Array
384
+ compiled << "(?:"
385
+ compile_path_segments(compiled, segment)
386
+ compiled << ")?"
387
+ else
388
+ raise ArgumentError, "conditions[:path] segments can only be a Strings, Symbols, or Arrays"
389
+ end
390
+ end
391
+ end
392
+
393
+ # Handles anchors in Regexp conditions
394
+ def compile_segment_condition(condition)
395
+ return "(#{SEGMENT_CHARACTERS}+)" unless condition
396
+ return "(#{condition})" unless condition.is_a?(Regexp)
397
+
398
+ condition = condition.source
399
+ # Handle the start anchor
400
+ condition = if condition =~ /^\^/
401
+ condition[1..-1]
402
+ else
403
+ "#{SEGMENT_CHARACTERS}*#{condition}"
404
+ end
405
+ # Handle the end anchor
406
+ condition = if condition =~ /\$$/
407
+ condition[0..-2]
408
+ else
409
+ "#{condition}#{SEGMENT_CHARACTERS}*"
410
+ end
411
+
412
+ "(#{condition})"
413
+ end
414
+
415
+ def compile_params
416
+ # Loop through each param and compile it
417
+ @defaults.merge(@params).each do |key, value|
418
+ if value.nil?
419
+ @params.delete(key)
420
+ elsif value.is_a?(String)
421
+ @params[key] = compile_param(value)
422
+ else
423
+ @params[key] = value.inspect
424
+ end
425
+ end
426
+ end
427
+
428
+ # This was pretty much a copy / paste from the old router
429
+ def compile_param(value)
430
+ result = []
431
+ match = true
432
+ while match
433
+ if match = SEGMENT_REGEXP_WITH_BRACKETS.match(value)
434
+ result << match.pre_match.inspect unless match.pre_match.empty?
435
+ placeholder_key = match[1][1..-1].intern
436
+ if match[2] # has brackets, e.g. :path[2]
437
+ result << "#{placeholder_key}#{match[3]}"
438
+ else # no brackets, e.g. a named placeholder such as :controller
439
+ if place = @placeholders[placeholder_key]
440
+ # result << "(path#{place} || )" # <- Defaults
441
+ with_defaults = ["(path#{place}"]
442
+ with_defaults << " || #{@defaults[placeholder_key].inspect}" if @defaults[placeholder_key]
443
+ with_defaults << ")"
444
+ result << with_defaults.join
445
+ else
446
+ raise GenerationError, "Placeholder not found while compiling routes: #{placeholder_key.inspect}. Add it to the conditions part of the route."
447
+ end
448
+ end
449
+ value = match.post_match
450
+ elsif match = JUST_BRACKETS.match(value)
451
+ result << match.pre_match.inspect unless match.pre_match.empty?
452
+ result << "path#{match[1]}"
453
+ value = match.post_match
454
+ else
455
+ result << value.inspect unless value.empty?
456
+ end
457
+ end
458
+
459
+ result.join(' + ').gsub("\\_", "_")
460
+ end
461
+
462
+ def condition_statements
463
+ statements = []
464
+
465
+ # First, let's build the conditions for the regular
466
+ conditions.each_pair do |key, value|
467
+ statements << case value
468
+ when Regexp
469
+ captures = ""
470
+
471
+ if (max = capturing_parentheses_count(value)) > 0
472
+ captures << (1..max).to_a.map { |n| "#{key}#{n}" }.join(", ")
473
+ captures << " = "
474
+ captures << (1..max).to_a.map { |n| "$#{n}" }.join(", ")
475
+ end
476
+
477
+ # Note: =~ is slightly faster than .match
478
+ %{(#{value.inspect} =~ cached_#{key} #{' && ((' + captures + ') || true)' unless captures.empty?})}
479
+ when Array
480
+ %{(#{arrays_to_regexps(value).inspect} =~ cached_#{key})}
481
+ else
482
+ %{(cached_#{key} == #{value.inspect})}
483
+ end
484
+ end
485
+
486
+ # The first one is special, so let's extract it
487
+ if first = @deferred_procs.first
488
+ deferred = ""
489
+ deferred << "(block_result = "
490
+ deferred << "request._process_block_return("
491
+ deferred << "#{first}.call(request, #{params_as_string})"
492
+ deferred << ")"
493
+ deferred << ")"
494
+
495
+ # Let's build the rest of them now
496
+ if @deferred_procs.length > 1
497
+ deferred << deferred_condition_statement(@deferred_procs[1..-1])
498
+ end
499
+
500
+ statements << deferred
501
+ end
502
+
503
+ statements
504
+ end
505
+
506
+ # (request.matched? || ((block_result = process(proc.call))))
507
+ def deferred_condition_statement(deferred)
508
+ if current = deferred.first
509
+ html = " && (request.matched? || ("
510
+ html << "(block_result = "
511
+ html << "request._process_block_return("
512
+ html << "#{current}.call(request, block_result)"
513
+ html << ")"
514
+ html << ")"
515
+ html << "#{deferred_condition_statement(deferred[1..-1])}"
516
+ html << "))"
517
+ end
518
+ end
519
+
520
+ def params_as_string
521
+ elements = params.keys.map do |k|
522
+ "#{k.inspect} => #{params[k]}"
523
+ end
524
+ "{#{elements.join(', ')}}"
525
+ end
526
+
527
+ # ---------- Utilities ----------
528
+
529
+ def arrays_to_regexps(condition)
530
+ return condition unless condition.is_a?(Array)
531
+
532
+ source = condition.map do |value|
533
+ value = if value.is_a?(Regexp)
534
+ value.source
535
+ else
536
+ "^#{Regexp.escape(value.to_s)}$"
537
+ end
538
+ "(?:#{value})"
539
+ end
540
+
541
+ Regexp.compile(source.join('|'))
542
+ end
543
+
544
+ def segment_level_to_s(segments)
545
+ (segments || []).inject('') do |str, seg|
546
+ str << case seg
547
+ when String then seg
548
+ when Symbol then ":#{seg}"
549
+ when Array then "(#{segment_level_to_s(seg)})"
550
+ end
551
+ end
552
+ end
553
+
554
+ def capturing_parentheses_count(regexp)
555
+ regexp = regexp.source if regexp.is_a?(Regexp)
556
+ regexp.scan(/(?!\\)[(](?!\?[#=:!>-imx])/).length
557
+ end
558
+ end
559
+ end
560
+ end