wycats-merb-core 0.9.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.
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 +458 -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 +598 -0
  11. data/lib/merb-core/autoload.rb +31 -0
  12. data/lib/merb-core/bootloader.rb +717 -0
  13. data/lib/merb-core/config.rb +305 -0
  14. data/lib/merb-core/constants.rb +45 -0
  15. data/lib/merb-core/controller/abstract_controller.rb +568 -0
  16. data/lib/merb-core/controller/exceptions.rb +315 -0
  17. data/lib/merb-core/controller/merb_controller.rb +256 -0
  18. data/lib/merb-core/controller/mime.rb +107 -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 +319 -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 +340 -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 +198 -0
  31. data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +73 -0
  32. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +94 -0
  33. data/lib/merb-core/dispatch/dispatcher.rb +176 -0
  34. data/lib/merb-core/dispatch/request.rb +729 -0
  35. data/lib/merb-core/dispatch/router.rb +151 -0
  36. data/lib/merb-core/dispatch/router/behavior.rb +566 -0
  37. data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
  38. data/lib/merb-core/dispatch/router/resources.rb +191 -0
  39. data/lib/merb-core/dispatch/router/route.rb +511 -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 +203 -0
  49. data/lib/merb-core/plugins.rb +67 -0
  50. data/lib/merb-core/rack.rb +25 -0
  51. data/lib/merb-core/rack/adapter.rb +44 -0
  52. data/lib/merb-core/rack/adapter/ebb.rb +25 -0
  53. data/lib/merb-core/rack/adapter/evented_mongrel.rb +26 -0
  54. data/lib/merb-core/rack/adapter/fcgi.rb +17 -0
  55. data/lib/merb-core/rack/adapter/irb.rb +118 -0
  56. data/lib/merb-core/rack/adapter/mongrel.rb +26 -0
  57. data/lib/merb-core/rack/adapter/runner.rb +28 -0
  58. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +26 -0
  59. data/lib/merb-core/rack/adapter/thin.rb +39 -0
  60. data/lib/merb-core/rack/adapter/thin_turbo.rb +24 -0
  61. data/lib/merb-core/rack/adapter/webrick.rb +36 -0
  62. data/lib/merb-core/rack/application.rb +32 -0
  63. data/lib/merb-core/rack/handler/mongrel.rb +97 -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 +284 -0
  73. data/lib/merb-core/tasks/audit.rake +68 -0
  74. data/lib/merb-core/tasks/gem_management.rb +229 -0
  75. data/lib/merb-core/tasks/merb.rb +1 -0
  76. data/lib/merb-core/tasks/merb_rake_helper.rb +80 -0
  77. data/lib/merb-core/tasks/stats.rake +71 -0
  78. data/lib/merb-core/test.rb +11 -0
  79. data/lib/merb-core/test/helpers.rb +9 -0
  80. data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
  81. data/lib/merb-core/test/helpers/multipart_request_helper.rb +175 -0
  82. data/lib/merb-core/test/helpers/request_helper.rb +393 -0
  83. data/lib/merb-core/test/helpers/route_helper.rb +39 -0
  84. data/lib/merb-core/test/helpers/view_helper.rb +121 -0
  85. data/lib/merb-core/test/matchers.rb +9 -0
  86. data/lib/merb-core/test/matchers/controller_matchers.rb +351 -0
  87. data/lib/merb-core/test/matchers/route_matchers.rb +137 -0
  88. data/lib/merb-core/test/matchers/view_matchers.rb +375 -0
  89. data/lib/merb-core/test/run_specs.rb +49 -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,305 @@
1
+ require "optparse"
2
+
3
+ module Merb
4
+
5
+ class Config
6
+
7
+ class << self
8
+
9
+ # ==== Returns
10
+ # Hash:: The defaults for the config.
11
+ def defaults
12
+ @defaults ||= {
13
+ :host => "0.0.0.0",
14
+ :port => "4000",
15
+ :adapter => "runner",
16
+ :reload_classes => true,
17
+ :environment => "development",
18
+ :merb_root => Dir.pwd,
19
+ :use_mutex => true,
20
+ :log_delimiter => " ~ ",
21
+ :log_auto_flush => false,
22
+ :log_level => :info,
23
+ :disabled_components => [],
24
+ :deferred_actions => [],
25
+ :verbose => false
26
+ }
27
+ end
28
+
29
+ # Yields the configuration.
30
+ #
31
+ # ==== Block parameters
32
+ # c<Hash>:: The configuration parameters.
33
+ #
34
+ # ==== Examples
35
+ # Merb::Config.use do |config|
36
+ # config[:exception_details] = false
37
+ # end
38
+ def use
39
+ @configuration ||= {}
40
+ yield @configuration
41
+ end
42
+
43
+ # ==== Parameters
44
+ # key<Object>:: The key to check.
45
+ #
46
+ # ==== Returns
47
+ # Boolean:: True if the key exists in the config.
48
+ def key?(key)
49
+ @configuration.key?(key)
50
+ end
51
+
52
+ # ==== Parameters
53
+ # key<Object>:: The key to retrieve the parameter for.
54
+ #
55
+ # ==== Returns
56
+ # Object:: The value of the configuration parameter.
57
+ def [](key)
58
+ (@configuration||={})[key]
59
+ end
60
+
61
+ # ==== Parameters
62
+ # key<Object>:: The key to set the parameter for.
63
+ # val<Object>:: The value of the parameter.
64
+ def []=(key,val)
65
+ @configuration[key] = val
66
+ end
67
+
68
+ # ==== Parameters
69
+ # key<Object>:: The key of the parameter to delete.
70
+ def delete(key)
71
+ @configuration.delete(key)
72
+ end
73
+
74
+ # ==== Parameters
75
+ # key<Object>:: The key to retrieve the parameter for.
76
+ # default<Object>::
77
+ # The default value to return if the parameter is not set.
78
+ #
79
+ # ==== Returns
80
+ # Object:: The value of the configuration parameter or the default.
81
+ def fetch(key, default)
82
+ @configuration.fetch(key, default)
83
+ end
84
+
85
+ # ==== Returns
86
+ # Hash:: The config as a hash.
87
+ def to_hash
88
+ @configuration
89
+ end
90
+
91
+ # ==== Returns
92
+ # String:: The config as YAML.
93
+ def to_yaml
94
+ require "yaml"
95
+ @configuration.to_yaml
96
+ end
97
+
98
+ # Sets up the configuration by storing the given settings.
99
+ #
100
+ # ==== Parameters
101
+ # settings<Hash>::
102
+ # Configuration settings to use. These are merged with the defaults.
103
+ def setup(settings = {})
104
+ @configuration = defaults.merge(settings)
105
+ end
106
+
107
+ # Parses the command line arguments and stores them in the config.
108
+ #
109
+ # ==== Parameters
110
+ # argv<String>:: The command line arguments. Defaults to +ARGV+.
111
+ def parse_args(argv = ARGV)
112
+ @configuration ||= {}
113
+ # Our primary configuration hash for the length of this method
114
+ options = {}
115
+
116
+ # Environment variables always win
117
+ options[:environment] = ENV["MERB_ENV"] if ENV["MERB_ENV"]
118
+
119
+ # Build a parser for the command line arguments
120
+ opts = OptionParser.new do |opts|
121
+ opts.version = Merb::VERSION
122
+
123
+ opts.banner = "Usage: merb [uGdcIpPhmailLerkKX] [argument]"
124
+ opts.define_head "Merb. Pocket rocket web framework"
125
+ opts.separator '*'*80
126
+ opts.separator 'If no flags are given, Merb starts in the foreground on port 4000.'
127
+ opts.separator '*'*80
128
+
129
+ opts.on("-u", "--user USER", "This flag is for having merb run as a user other than the one currently logged in. Note: if you set this you must also provide a --group option for it to take effect.") do |user|
130
+ options[:user] = user
131
+ end
132
+
133
+ opts.on("-G", "--group GROUP", "This flag is for having merb run as a group other than the one currently logged in. Note: if you set this you must also provide a --user option for it to take effect.") do |group|
134
+ options[:group] = group
135
+ end
136
+
137
+ opts.on("-d", "--daemonize", "This will run a single merb in the background.") do |daemon|
138
+ options[:daemonize] = true
139
+ end
140
+
141
+ opts.on("-c", "--cluster-nodes NUM_MERBS", "Number of merb daemons to run.") do |nodes|
142
+ options[:cluster] = nodes
143
+ end
144
+
145
+ opts.on("-I", "--init-file FILE", "File to use for initialization on load, defaults to config/init.rb") do |init_file|
146
+ options[:init_file] = init_file
147
+ end
148
+
149
+ opts.on("-p", "--port PORTNUM", "Port to run merb on, defaults to 4000.") do |port|
150
+ options[:port] = port
151
+ end
152
+
153
+ opts.on("-o", "--socket-file FILE", "Socket file to run merb on, defaults to [Merb.root]/log/merb.sock") do |port|
154
+ options[:socket_file] = port
155
+ end
156
+
157
+ opts.on("-s", "--socket SOCKNUM", "Socket number to run merb on, defaults to 0.") do |port|
158
+ options[:socket] = port
159
+ end
160
+
161
+ opts.on("-P", "--pid PIDFILE", "PID file, defaults to [Merb.root]/log/merb.[port_number].pid") do |pid_file|
162
+ options[:pid_file] = pid_file
163
+ end
164
+
165
+ opts.on("-h", "--host HOSTNAME", "Host to bind to (default is 0.0.0.0).") do |host|
166
+ options[:host] = host
167
+ end
168
+
169
+ opts.on("-m", "--merb-root /path/to/approot", "The path to the Merb.root for the app you want to run (default is current working dir).") do |root|
170
+ options[:merb_root] = File.expand_path(root)
171
+ end
172
+
173
+ opts.on("-a", "--adapter mongrel", "The rack adapter to use to run merb[mongrel, emongrel, thin, ebb, fastcgi, webrick, runner, irb]") do |adapter|
174
+ options[:adapter] = adapter
175
+ end
176
+
177
+ opts.on("-R", "--rackup FILE", "Load an alternate Rack config file (default is config/rack.rb)") do |rackup|
178
+ options[:rackup] = rackup
179
+ end
180
+
181
+ opts.on("-i", "--irb-console", "This flag will start merb in irb console mode. All your models and other classes will be available for you in an irb session.") do |console|
182
+ options[:adapter] = 'irb'
183
+ end
184
+
185
+ opts.on("-S", "--sandbox", "This flag will enable a sandboxed irb console. If your ORM supports transactions, all edits will be rolled back on exit.") do |sandbox|
186
+ options[:sandbox] = true
187
+ end
188
+
189
+ opts.on("-l", "--log-level LEVEL", "Log levels can be set to any of these options: debug < info < warn < error < fatal") do |log_level|
190
+ options[:log_level] = log_level.to_sym
191
+ end
192
+
193
+ opts.on("-L", "--log LOGFILE", "A string representing the logfile to use.") do |log_file|
194
+ options[:log_file] = log_file
195
+ end
196
+
197
+ opts.on("-e", "--environment STRING", "Run merb in the correct mode(development, production, testing)") do |env|
198
+ options[:environment] = env
199
+ end
200
+
201
+ opts.on("-r", "--script-runner ['RUBY CODE'| FULL_SCRIPT_PATH]",
202
+ "Command-line option to run scripts and/or code in the merb app.") do |code_or_file|
203
+ options[:runner_code] = code_or_file
204
+ options[:adapter] = 'runner'
205
+ end
206
+
207
+ opts.on("-K", "--graceful PORT or all", "Gracefully kill one merb proceses by port number. Use merb -K all to gracefully kill all merbs.") do |ports|
208
+ options[:action] = :kill
209
+ options[:port] = ports
210
+ end
211
+
212
+ opts.on("-k", "--kill PORT or all", "Kill one merb proceses by port number. Use merb -k all to kill all merbs.") do |port|
213
+ options[:action] = :kill_9
214
+ options[:port] = port
215
+ end
216
+
217
+ opts.on("-X", "--mutex on/off", "This flag is for turning the mutex lock on and off.") do |mutex|
218
+ if mutex == "off"
219
+ options[:use_mutex] = false
220
+ else
221
+ options[:use_mutex] = true
222
+ end
223
+ end
224
+
225
+ opts.on("-D", "--debugger", "Run merb using rDebug.") do
226
+ begin
227
+ require "ruby-debug"
228
+ Debugger.start
229
+ Debugger.settings[:autoeval] = true if Debugger.respond_to?(:settings)
230
+ puts "Debugger enabled"
231
+ rescue LoadError
232
+ puts "You need to install ruby-debug to run the server in debugging mode. With gems, use 'gem install ruby-debug'"
233
+ exit
234
+ end
235
+ end
236
+
237
+ opts.on("-V", "--verbose", "Print extra information") do
238
+ options[:verbose] = true
239
+ end
240
+
241
+ opts.on("-C", "--console-trap", "Enter an irb console on ^C") do
242
+ options[:console_trap] = true
243
+ end
244
+
245
+ opts.on("-?", "-H", "--help", "Show this help message") do
246
+ puts opts
247
+ exit
248
+ end
249
+ end
250
+
251
+ # Parse what we have on the command line
252
+ opts.parse!(argv)
253
+ Merb::Config.setup(options)
254
+ end
255
+
256
+ attr_accessor :configuration
257
+
258
+ # Set configuration parameters from a code block, where each method
259
+ # evaluates to a config parameter.
260
+ #
261
+ # ==== Parameters
262
+ # &block:: Configuration parameter block.
263
+ #
264
+ # ==== Examples
265
+ # # Set environment and log level.
266
+ # Merb::Config.configure do
267
+ # environment "development"
268
+ # log_level "debug"
269
+ # end
270
+ def configure(&block)
271
+ ConfigBlock.new(self, &block) if block_given?
272
+ end
273
+
274
+ # Allows retrieval of single key config values via Merb.config.<key>
275
+ # Allows single key assignment via Merb.config.<key> = ...
276
+ #
277
+ # ==== Parameters
278
+ # method<~to_s>:: Method name as hash key value.
279
+ # *args:: Value to set the configuration parameter to.
280
+ def method_missing(method, *args)
281
+ if method.to_s[-1,1] == '='
282
+ @configuration[method.to_s.tr('=','').to_sym] = *args
283
+ else
284
+ @configuration[method]
285
+ end
286
+ end
287
+
288
+ end # class << self
289
+
290
+ class ConfigBlock
291
+
292
+ def initialize(klass, &block)
293
+ @klass = klass
294
+ instance_eval(&block)
295
+ end
296
+
297
+ def method_missing(method, *args)
298
+ @klass[method] = *args
299
+ end
300
+
301
+ end # class Configurator
302
+
303
+ end # Config
304
+
305
+ end # Merb
@@ -0,0 +1,45 @@
1
+ # Most of this list is simply constants frozen for efficiency
2
+ module Merb
3
+ module Const
4
+
5
+ DEFAULT_SEND_FILE_OPTIONS = {
6
+ :type => 'application/octet-stream'.freeze,
7
+ :disposition => 'attachment'.freeze
8
+ }.freeze
9
+
10
+ SET_COOKIE = " %s=%s; path=/; expires=%s".freeze
11
+ COOKIE_EXPIRATION_FORMAT = "%a, %d-%b-%Y %H:%M:%S GMT".freeze
12
+ COOKIE_SPLIT = /[;,] */n.freeze
13
+ COOKIE_REGEXP = /\s*(.+)=(.*)\s*/.freeze
14
+ COOKIE_EXPIRED_TIME = Time.at(0).freeze
15
+ HOUR = 60 * 60
16
+ DAY = HOUR * 24
17
+ WEEK = DAY * 7
18
+ MULTIPART_REGEXP = /\Amultipart\/form-data.*boundary=\"?([^\";,]+)/n.freeze
19
+ HTTP_COOKIE = 'HTTP_COOKIE'.freeze
20
+ QUERY_STRING = 'QUERY_STRING'.freeze
21
+ JSON_MIME_TYPE_REGEXP = %r{^application/json|^text/x-json}.freeze
22
+ XML_MIME_TYPE_REGEXP = %r{^application/xml|^text/xml}.freeze
23
+ FORM_URL_ENCODED_REGEXP = %r{^application/x-www-form-urlencoded}.freeze
24
+ UPCASE_CONTENT_TYPE = 'CONTENT_TYPE'.freeze
25
+ CONTENT_TYPE = "Content-Type".freeze
26
+ DATE = 'Date'.freeze
27
+ ETAG = 'ETag'.freeze
28
+ LAST_MODIFIED = "Last-Modified".freeze
29
+ SLASH = "/".freeze
30
+ REQUEST_METHOD = "REQUEST_METHOD".freeze
31
+ GET = "GET".freeze
32
+ POST = "POST".freeze
33
+ HEAD = "HEAD".freeze
34
+ CONTENT_LENGTH = "CONTENT_LENGTH".freeze
35
+ HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR".freeze
36
+ HTTP_IF_MODIFIED_SINCE = "HTTP_IF_MODIFIED_SINCE".freeze
37
+ HTTP_IF_NONE_MATCH = "HTTP_IF_NONE_MATCH".freeze
38
+ UPLOAD_ID = "upload_id".freeze
39
+ PATH_INFO = "PATH_INFO".freeze
40
+ SCRIPT_NAME = "SCRIPT_NAME".freeze
41
+ REQUEST_URI = "REQUEST_URI".freeze
42
+ REQUEST_PATH = "REQUEST_PATH".freeze
43
+ REMOTE_ADDR = "REMOTE_ADDR".freeze
44
+ end
45
+ end
@@ -0,0 +1,568 @@
1
+ # ==== Why do we use Underscores?
2
+ # In Merb, views are actually methods on controllers. This provides
3
+ # not-insignificant speed benefits, as well as preventing us from
4
+ # needing to copy over instance variables, which we think is proof
5
+ # that everything belongs in one class to begin with.
6
+ #
7
+ # Unfortunately, this means that view helpers need to be included
8
+ # into the <strong>Controller</strong> class. To avoid causing confusion
9
+ # when your helpers potentially conflict with our instance methods,
10
+ # we use an _ to disambiguate. As long as you don't begin your helper
11
+ # methods with _, you only need to worry about conflicts with Merb
12
+ # methods that are part of the public API.
13
+ #
14
+ #
15
+ #
16
+ # ==== Filters
17
+ # #before is a class method that allows you to specify before filters in
18
+ # your controllers. Filters can either be a symbol or string that
19
+ # corresponds to a method name to call, or a proc object. if it is a method
20
+ # name that method will be called and if it is a proc it will be called
21
+ # with an argument of self where self is the current controller object.
22
+ # When you use a proc as a filter it needs to take one parameter.
23
+ #
24
+ # #after is identical, but the filters are run after the action is invoked.
25
+ #
26
+ # ===== Examples
27
+ # before :some_filter
28
+ # before :authenticate, :exclude => [:login, :signup]
29
+ # before :has_role, :with => ["Admin"], :exclude => [:index, :show]
30
+ # before Proc.new { some_method }, :only => :foo
31
+ # before :authorize, :unless => :logged_in?
32
+ #
33
+ # You can use either <code>:only => :actionname</code> or
34
+ # <code>:exclude => [:this, :that]</code> but not both at once.
35
+ # <code>:only</code> will only run before the listed actions and
36
+ # <code>:exclude</code> will run for every action that is not listed.
37
+ #
38
+ # Merb's before filter chain is very flexible. To halt the filter chain you
39
+ # use <code>throw :halt</code>. If <code>throw</code> is called with only one
40
+ # argument of <code>:halt</code> the return value of the method
41
+ # <code>filters_halted</code> will be what is rendered to the view. You can
42
+ # override <code>filters_halted</code> in your own controllers to control what
43
+ # it outputs. But the <code>throw</code> construct is much more powerful than
44
+ # just that.
45
+ #
46
+ # <code>throw :halt</code> can also take a second argument. Here is what that
47
+ # second argument can be and the behavior each type can have:
48
+ #
49
+ # * +String+:
50
+ # when the second argument is a string then that string will be what
51
+ # is rendered to the browser. Since merb's <code>#render</code> method returns
52
+ # a string you can render a template or just use a plain string:
53
+ #
54
+ # throw :halt, "You don't have permissions to do that!"
55
+ # throw :halt, render(:action => :access_denied)
56
+ #
57
+ # * +Symbol+:
58
+ # If the second arg is a symbol, then the method named after that
59
+ # symbol will be called
60
+ #
61
+ # throw :halt, :must_click_disclaimer
62
+ #
63
+ # * +Proc+:
64
+ # If the second arg is a Proc, it will be called and its return
65
+ # value will be what is rendered to the browser:
66
+ #
67
+ # throw :halt, proc { access_denied }
68
+ # throw :halt, proc { Tidy.new(c.index) }
69
+ #
70
+ # ===== Filter Options (.before, .after, .add_filter, .if, .unless)
71
+ # :only<Symbol, Array[Symbol]>::
72
+ # A list of actions that this filter should apply to
73
+ #
74
+ # :exclude<Symbol, Array[Symbol]::
75
+ # A list of actions that this filter should *not* apply to
76
+ #
77
+ # :if<Symbol, Proc>::
78
+ # Only apply the filter if the method named after the symbol or calling the proc evaluates to true
79
+ #
80
+ # :unless<Symbol, Proc>::
81
+ # Only apply the filter if the method named after the symbol or calling the proc evaluates to false
82
+ #
83
+ # :with<Array[Object]>::
84
+ # Arguments to be passed to the filter. Since we are talking method/proc calls,
85
+ # filter method or Proc should to have the same arity
86
+ # as number of elements in Array you pass to this option.
87
+ #
88
+ # ===== Types (shortcuts for use in this file)
89
+ # Filter:: <Array[Symbol, (Symbol, String, Proc)]>
90
+ #
91
+ # ==== params[:action] and params[:controller] deprecated
92
+ # <code>params[:action]</code> and <code>params[:controller]</code> have been deprecated as of
93
+ # the 0.9.0 release. They are no longer set during dispatch, and
94
+ # have been replaced by <code>action_name</code> and <code>controller_name</code> respectively.
95
+ class Merb::AbstractController
96
+ include Merb::RenderMixin
97
+ include Merb::InlineTemplates
98
+
99
+ class_inheritable_accessor :_layout, :_template_root, :template_roots
100
+ class_inheritable_accessor :_before_filters, :_after_filters
101
+ class_inheritable_accessor :_before_dispatch_callbacks, :_after_dispatch_callbacks
102
+
103
+ cattr_accessor :_abstract_subclasses
104
+
105
+ #---
106
+ # @semipublic
107
+ attr_accessor :body
108
+ attr_accessor :action_name
109
+ attr_accessor :_benchmarks, :_thrown_content
110
+
111
+ # Stub so content-type support in RenderMixin doesn't throw errors
112
+ attr_accessor :content_type
113
+
114
+ FILTER_OPTIONS = [:only, :exclude, :if, :unless, :with]
115
+
116
+ self._before_filters, self._after_filters = [], []
117
+ self._before_dispatch_callbacks, self._after_dispatch_callbacks = [], []
118
+
119
+ #---
120
+ # We're using abstract_subclasses so that Merb::Controller can have its
121
+ # own subclasses. We're using a Set so we don't have to worry about
122
+ # uniqueness.
123
+ self._abstract_subclasses = Set.new
124
+
125
+ # ==== Returns
126
+ # String:: The controller name in path form, e.g. "admin/items".
127
+ #---
128
+ # @public
129
+ def self.controller_name() @controller_name ||= self.name.to_const_path end
130
+
131
+ # ==== Returns
132
+ # String:: The controller name in path form, e.g. "admin/items".
133
+ def controller_name() self.class.controller_name end
134
+
135
+ # This is called after the controller is instantiated to figure out where to
136
+ # look for templates under the _template_root. Override this to define a new
137
+ # structure for your app.
138
+ #
139
+ # ==== Parameters
140
+ # context<~to_s>:: The controller context (the action or template name).
141
+ # type<~to_s>:: The content type. Defaults to nil.
142
+ # controller<~to_s>::
143
+ # The name of the controller. Defaults to controller_name.
144
+ #
145
+ #
146
+ # ==== Returns
147
+ # String::
148
+ # Indicating where to look for the template for the current controller,
149
+ # context, and content-type.
150
+ #
151
+ # ==== Notes
152
+ # The type is irrelevant for controller-types that don't support
153
+ # content-type negotiation, so we default to not include it in the
154
+ # superclass.
155
+ #
156
+ # ==== Examples
157
+ # def _template_location
158
+ # "#{params[:controller]}.#{params[:action]}.#{content_type}"
159
+ # end
160
+ #
161
+ # This would look for templates at controller.action.mime.type instead
162
+ # of controller/action.mime.type
163
+ #---
164
+ # @public
165
+ def _template_location(context, type, controller)
166
+ controller ? "#{controller}/#{context}" : context
167
+ end
168
+
169
+ # The location to look for a template - stub method for particular behaviour.
170
+ #
171
+ # ==== Parameters
172
+ # template<String>:: The absolute path to a template - without template extension.
173
+ # type<~to_s>::
174
+ # The mime-type of the template that will be rendered. Defaults to nil.
175
+ #
176
+ # @public
177
+ def _absolute_template_location(template, type)
178
+ template
179
+ end
180
+
181
+ def self._template_root=(root)
182
+ @_template_root = root
183
+ _reset_template_roots
184
+ end
185
+
186
+ def self._reset_template_roots
187
+ self.template_roots = [[self._template_root, :_template_location]]
188
+ end
189
+
190
+ # ==== Returns
191
+ # roots<Array[Array]>::
192
+ # Template roots as pairs of template root path and template location
193
+ # method.
194
+ def self._template_roots
195
+ self.template_roots || _reset_template_roots
196
+ end
197
+
198
+ # ==== Parameters
199
+ # roots<Array[Array]>::
200
+ # Template roots as pairs of template root path and template location
201
+ # method.
202
+ def self._template_roots=(roots)
203
+ self.template_roots = roots
204
+ end
205
+
206
+ # ==== Returns
207
+ # Set:: The subclasses.
208
+ def self.subclasses_list() _abstract_subclasses end
209
+
210
+ class << self
211
+ # ==== Parameters
212
+ # klass<Merb::AbstractController>::
213
+ # The controller that is being inherited from Merb::AbstractController
214
+ def inherited(klass)
215
+ _abstract_subclasses << klass.to_s
216
+ helper_module_name = klass.to_s =~ /^(#|Merb::)/ ? "#{klass}Helper" : "Merb::#{klass}Helper"
217
+ Object.make_module helper_module_name
218
+ klass.class_eval <<-HERE
219
+ include Object.full_const_get("#{helper_module_name}") rescue nil
220
+ HERE
221
+ super
222
+ end
223
+ end
224
+
225
+ # ==== Parameters
226
+ # *args:: The args are ignored.
227
+ def initialize(*args)
228
+ @_benchmarks = {}
229
+ @_caught_content = {}
230
+ @_template_stack = []
231
+ end
232
+
233
+ # This will dispatch the request, calling internal before/after dispatch_callbacks
234
+ #
235
+ # ==== Parameters
236
+ # action<~to_s>::
237
+ # The action to dispatch to. This will be #send'ed in _call_action.
238
+ # Defaults to :to_s.
239
+ #
240
+ # ==== Raises
241
+ # MerbControllerError:: Invalid body content caught.
242
+ def _dispatch(action)
243
+ self._before_dispatch_callbacks.each { |cb| cb.call(self) }
244
+ self.action_name = action
245
+
246
+ caught = catch(:halt) do
247
+ start = Time.now
248
+ result = _call_filters(_before_filters)
249
+ @_benchmarks[:before_filters_time] = Time.now - start if _before_filters
250
+ result
251
+ end
252
+
253
+ @body = case caught
254
+ when :filter_chain_completed then _call_action(action_name)
255
+ when String then caught
256
+ when nil then _filters_halted
257
+ when Symbol then __send__(caught)
258
+ when Proc then self.instance_eval(&caught)
259
+ else
260
+ raise ArgumentError, "Threw :halt, #{caught}. Expected String, nil, Symbol, Proc."
261
+ end
262
+ start = Time.now
263
+ _call_filters(_after_filters)
264
+ @_benchmarks[:after_filters_time] = Time.now - start if _after_filters
265
+
266
+ self._after_dispatch_callbacks.each { |cb| cb.call(self) }
267
+
268
+ @body
269
+ end
270
+
271
+ # This method exists to provide an overridable hook for ActionArgs
272
+ #
273
+ # ==== Parameters
274
+ # action<~to_s>:: the action method to dispatch to
275
+ def _call_action(action)
276
+ send(action)
277
+ end
278
+
279
+ # ==== Parameters
280
+ # filter_set<Array[Filter]>::
281
+ # A set of filters in the form [[:filter, rule], [:filter, rule]]
282
+ #
283
+ # ==== Returns
284
+ # Symbol:: :filter_chain_completed.
285
+ #
286
+ # ==== Notes
287
+ # Filter rules can be Symbols, Strings, or Procs.
288
+ #
289
+ # Symbols or Strings::
290
+ # Call the method represented by the +Symbol+ or +String+.
291
+ # Procs::
292
+ # Execute the +Proc+, in the context of the controller (self will be the
293
+ # controller)
294
+ def _call_filters(filter_set)
295
+ (filter_set || []).each do |filter, rule|
296
+ if _call_filter_for_action?(rule, action_name) && _filter_condition_met?(rule)
297
+ case filter
298
+ when Symbol, String
299
+ if rule.key?(:with)
300
+ args = rule[:with]
301
+ send(filter, *args)
302
+ else
303
+ send(filter)
304
+ end
305
+ when Proc then self.instance_eval(&filter)
306
+ end
307
+ end
308
+ end
309
+ return :filter_chain_completed
310
+ end
311
+
312
+ # ==== Parameters
313
+ # rule<Hash>:: Rules for the filter (see below).
314
+ # action_name<~to_s>:: The name of the action to be called.
315
+ #
316
+ # ==== Options (rule)
317
+ # :only<Array>::
318
+ # Optional list of actions to fire. If given, action_name must be a part of
319
+ # it for this function to return true.
320
+ # :exclude<Array>::
321
+ # Optional list of actions not to fire. If given, action_name must not be a
322
+ # part of it for this function to return true.
323
+ #
324
+ # ==== Returns
325
+ # Boolean:: True if the action should be called.
326
+ def _call_filter_for_action?(rule, action_name)
327
+ # Both:
328
+ # * no :only or the current action is in the :only list
329
+ # * no :exclude or the current action is not in the :exclude list
330
+ (!rule.key?(:only) || rule[:only].include?(action_name)) &&
331
+ (!rule.key?(:exclude) || !rule[:exclude].include?(action_name))
332
+ end
333
+
334
+ # ==== Parameters
335
+ # rule<Hash>:: Rules for the filter (see below).
336
+ #
337
+ # ==== Options (rule)
338
+ # :if<Array>:: Optional conditions that must be met for the filter to fire.
339
+ # :unless<Array>::
340
+ # Optional conditions that must not be met for the filter to fire.
341
+ #
342
+ # ==== Returns
343
+ # Boolean:: True if the conditions are met.
344
+ def _filter_condition_met?(rule)
345
+ # Both:
346
+ # * no :if or the if condition evaluates to true
347
+ # * no :unless or the unless condition evaluates to false
348
+ (!rule.key?(:if) || _evaluate_condition(rule[:if])) &&
349
+ (!rule.key?(:unless) || ! _evaluate_condition(rule[:unless]))
350
+ end
351
+
352
+ # ==== Parameters
353
+ # condition<Symbol, Proc>:: The condition to evaluate.
354
+ #
355
+ # ==== Raises
356
+ # ArgumentError:: condition not a Symbol or Proc.
357
+ #
358
+ # ==== Returns
359
+ # Boolean:: True if the condition is met.
360
+ #
361
+ # ==== Alternatives
362
+ # If condition is a symbol, it will be send'ed. If it is a Proc it will be
363
+ # called directly with self as an argument.
364
+ def _evaluate_condition(condition)
365
+ case condition
366
+ when Symbol : self.send(condition)
367
+ when Proc : self.instance_eval(&condition)
368
+ else
369
+ raise ArgumentError,
370
+ 'Filter condtions need to be either a Symbol or a Proc'
371
+ end
372
+ end
373
+
374
+ # ==== Parameters
375
+ # filter<Symbol, Proc>:: The filter to add. Defaults to nil.
376
+ # opts<Hash>::
377
+ # Filter options (see class documentation under <tt>Filter Options</tt>).
378
+ # &block:: A block to use as a filter if filter is nil.
379
+ #
380
+ # ==== Notes
381
+ # If the filter already exists, its options will be replaced with opts.
382
+ def self.after(filter = nil, opts = {}, &block)
383
+ add_filter(self._after_filters, filter || block, opts)
384
+ end
385
+
386
+ # ==== Parameters
387
+ # filter<Symbol, Proc>:: The filter to add. Defaults to nil.
388
+ # opts<Hash>::
389
+ # Filter options (see class documentation under <tt>Filter Options</tt>).
390
+ # &block:: A block to use as a filter if filter is nil.
391
+ #
392
+ # ==== Notes
393
+ # If the filter already exists, its options will be replaced with opts.
394
+ def self.before(filter = nil, opts = {}, &block)
395
+ add_filter(self._before_filters, filter || block, opts)
396
+ end
397
+
398
+ # Skip an after filter that has been previously defined (perhaps in a
399
+ # superclass)
400
+ #
401
+ # ==== Parameters
402
+ # filter<Symbol>:: A filter name to skip.
403
+ def self.skip_after(filter)
404
+ skip_filter(self._after_filters, filter)
405
+ end
406
+
407
+ # Skip a before filter that has been previously defined (perhaps in a
408
+ # superclass).
409
+ #
410
+ # ==== Parameters
411
+ # filter<Symbol>:: A filter name to skip.
412
+ def self.skip_before(filter)
413
+ skip_filter(self._before_filters , filter)
414
+ end
415
+
416
+ #---
417
+ # Defaults that can be overridden by plugins, other mixins, or subclasses
418
+ def _filters_halted() "<html><body><h1>Filter Chain Halted!</h1></body></html>" end
419
+
420
+ # ==== Parameters
421
+ # name<~to_sym, Hash>:: The name of the URL to generate.
422
+ # rparams<Hash>:: Parameters for the route generation.
423
+ #
424
+ # ==== Returns
425
+ # String:: The generated URL.
426
+ #
427
+ # ==== Alternatives
428
+ # If a hash is used as the first argument, a default route will be
429
+ # generated based on it and rparams.
430
+ # ====
431
+ # TODO: Update this documentation
432
+ def url(name, *args)
433
+ request.generate_url(name, *args)
434
+ end
435
+
436
+ alias_method :relative_url, :url
437
+
438
+ # ==== Parameters
439
+ # name<~to_sym, Hash>:: The name of the URL to generate.
440
+ # rparams<Hash>:: Parameters for the route generation.
441
+ #
442
+ # ==== Returns
443
+ # String:: The generated url with protocol + hostname + URL.
444
+ #
445
+ # ==== Options
446
+ #
447
+ # :protocol and :host options are special: use them to explicitly
448
+ # specify protocol and host of resulting url. If you omit them,
449
+ # protocol and host of request are used.
450
+ #
451
+ # ==== Alternatives
452
+ # If a hash is used as the first argument, a default route will be
453
+ # generated based on it and rparams.
454
+ def absolute_url(name, rparams={})
455
+ request.generate_absolute_url(name,rparams)
456
+ end
457
+
458
+ # Calls the capture method for the selected template engine.
459
+ #
460
+ # ==== Parameters
461
+ # *args:: Arguments to pass to the block.
462
+ # &block:: The template block to call.
463
+ #
464
+ # ==== Returns
465
+ # String:: The output of the block.
466
+ def capture(*args, &block)
467
+ send("capture_#{@_engine}", *args, &block)
468
+ end
469
+
470
+ # Calls the concatenate method for the selected template engine.
471
+ #
472
+ # ==== Parameters
473
+ # str<String>:: The string to concatenate to the buffer.
474
+ # binding<Binding>:: The binding to use for the buffer.
475
+ def concat(str, binding)
476
+ send("concat_#{@_engine}", str, binding)
477
+ end
478
+
479
+ private
480
+ # ==== Parameters
481
+ # filters<Array[Filter]>:: The filter list that this should be added to.
482
+ # filter<Filter>:: A filter that should be added.
483
+ # opts<Hash>::
484
+ # Filter options (see class documentation under <tt>Filter Options</tt>).
485
+ #
486
+ # ==== Raises
487
+ # ArgumentError::
488
+ # Both :only and :exclude, or :if and :unless given, if filter is not a
489
+ # Symbol, String or Proc, or if an unknown option is passed.
490
+ def self.add_filter(filters, filter, opts={})
491
+ raise(ArgumentError,
492
+ "You can specify either :only or :exclude but
493
+ not both at the same time for the same filter.") if opts.key?(:only) && opts.key?(:exclude)
494
+
495
+ raise(ArgumentError,
496
+ "You can specify either :if or :unless but
497
+ not both at the same time for the same filter.") if opts.key?(:if) && opts.key?(:unless)
498
+
499
+ opts.each_key do |key| raise(ArgumentError,
500
+ "You can only specify known filter options, #{key} is invalid.") unless FILTER_OPTIONS.include?(key)
501
+ end
502
+
503
+ opts = normalize_filters!(opts)
504
+
505
+ case filter
506
+ when Proc
507
+ # filters with procs created via class methods have identical signature
508
+ # regardless if they handle content differently or not. So procs just
509
+ # get appended
510
+ filters << [filter, opts]
511
+ when Symbol, String
512
+ if existing_filter = filters.find {|f| f.first.to_s[filter.to_s]}
513
+ filters[ filters.index(existing_filter) ] = [filter, opts]
514
+ else
515
+ filters << [filter, opts]
516
+ end
517
+ else
518
+ raise(ArgumentError,
519
+ 'Filters need to be either a Symbol, String or a Proc'
520
+ )
521
+ end
522
+ end
523
+
524
+ # Skip a filter that was previously added to the filter chain. Useful in
525
+ # inheritence hierarchies.
526
+ #
527
+ # ==== Parameters
528
+ # filters<Array[Filter]>:: The filter list that this should be removed from.
529
+ # filter<Filter>:: A filter that should be removed.
530
+ #
531
+ # ==== Raises
532
+ # ArgumentError:: filter not Symbol or String.
533
+ def self.skip_filter(filters, filter)
534
+ raise(ArgumentError, 'You can only skip filters that have a String or Symbol name.') unless
535
+ [Symbol, String].include? filter.class
536
+
537
+ Merb.logger.warn("Filter #{filter} was not found in your filter chain.") unless
538
+ filters.reject! {|f| f.first.to_s[filter.to_s] }
539
+ end
540
+
541
+ # Ensures that the passed in hash values are always arrays.
542
+ #
543
+ # ==== Parameters
544
+ # opts<Hash>:: Options for the filters (see below).
545
+ #
546
+ # ==== Options (opts)
547
+ # :only<Symbol, Array[Symbol]>:: A list of actions.
548
+ # :exclude<Symbol, Array[Symbol]>:: A list of actions.
549
+ #
550
+ # ==== Examples
551
+ # normalize_filters!(:only => :new) #=> {:only => [:new]}
552
+ def self.normalize_filters!(opts={})
553
+ opts[:only] = Array(opts[:only]).map {|x| x.to_s} if opts[:only]
554
+ opts[:exclude] = Array(opts[:exclude]).map {|x| x.to_s} if opts[:exclude]
555
+ return opts
556
+ end
557
+
558
+ # Attempts to return the partial local variable corresponding to sym.
559
+ #
560
+ # ==== Paramteres
561
+ # sym<Symbol>:: Method name.
562
+ # *arg:: Arguments to pass to the method.
563
+ # &blk:: A block to pass to the method.
564
+ def method_missing(sym, *args, &blk)
565
+ return @_merb_partial_locals[sym] if @_merb_partial_locals && @_merb_partial_locals.key?(sym)
566
+ super
567
+ end
568
+ end