merb 0.3.7 → 0.4.0

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 (173) hide show
  1. data/README +25 -26
  2. data/Rakefile +48 -36
  3. data/app_generators/merb/USAGE +5 -0
  4. data/app_generators/merb/merb_generator.rb +107 -0
  5. data/app_generators/merb/templates/Rakefile +99 -0
  6. data/{examples/skeleton/dist → app_generators/merb/templates}/app/controllers/application.rb +1 -1
  7. data/app_generators/merb/templates/app/controllers/exceptions.rb +13 -0
  8. data/{examples/skeleton/dist → app_generators/merb/templates}/app/helpers/global_helper.rb +0 -0
  9. data/{examples/skeleton/dist/app/mailers → app_generators/merb/templates/app/mailers/views}/layout/application.erb +0 -0
  10. data/app_generators/merb/templates/app/views/exceptions/internal_server_error.html.erb +207 -0
  11. data/app_generators/merb/templates/app/views/exceptions/not_acceptable.html.erb +38 -0
  12. data/app_generators/merb/templates/app/views/exceptions/not_found.html.erb +40 -0
  13. data/app_generators/merb/templates/app/views/layout/application.html.erb +11 -0
  14. data/app_generators/merb/templates/config/boot.rb +11 -0
  15. data/app_generators/merb/templates/config/dependencies.rb +41 -0
  16. data/{examples/skeleton/dist/conf → app_generators/merb/templates/config}/environments/development.rb +0 -0
  17. data/{examples/skeleton/dist/conf → app_generators/merb/templates/config}/environments/production.rb +0 -0
  18. data/{examples/skeleton/dist/conf → app_generators/merb/templates/config}/environments/test.rb +0 -0
  19. data/app_generators/merb/templates/config/merb.yml +64 -0
  20. data/app_generators/merb/templates/config/merb_init.rb +16 -0
  21. data/app_generators/merb/templates/config/plugins.yml +1 -0
  22. data/app_generators/merb/templates/config/router.rb +32 -0
  23. data/{lib/merb/core_ext/merb_array.rb → app_generators/merb/templates/config/upload.conf} +0 -0
  24. data/app_generators/merb/templates/public/images/merb.jpg +0 -0
  25. data/app_generators/merb/templates/public/merb.fcgi +6 -0
  26. data/app_generators/merb/templates/public/stylesheets/master.css +119 -0
  27. data/app_generators/merb/templates/script/destroy +28 -0
  28. data/app_generators/merb/templates/script/generate +28 -0
  29. data/{examples/skeleton → app_generators/merb/templates}/script/stop_merb +0 -0
  30. data/app_generators/merb/templates/script/win_script.cmd +1 -0
  31. data/app_generators/merb/templates/spec/spec.opts +6 -0
  32. data/app_generators/merb/templates/spec/spec_helper.rb +10 -0
  33. data/app_generators/merb/templates/test/test_helper.rb +13 -0
  34. data/app_generators/merb_plugin/USAGE +5 -0
  35. data/app_generators/merb_plugin/merb_plugin_generator.rb +64 -0
  36. data/app_generators/merb_plugin/templates/LICENSE +20 -0
  37. data/app_generators/merb_plugin/templates/README +4 -0
  38. data/app_generators/merb_plugin/templates/Rakefile +35 -0
  39. data/app_generators/merb_plugin/templates/TODO +5 -0
  40. data/app_generators/merb_plugin/templates/merbtasks.rb +6 -0
  41. data/app_generators/merb_plugin/templates/sampleplugin.rb +10 -0
  42. data/app_generators/merb_plugin/templates/sampleplugin_spec.rb +7 -0
  43. data/app_generators/merb_plugin/templates/spec_helper.rb +2 -0
  44. data/bin/merb +1 -1
  45. data/lib/autotest/discover.rb +3 -0
  46. data/lib/autotest/merb_rspec.rb +79 -0
  47. data/lib/merb.rb +72 -93
  48. data/lib/merb/{merb_abstract_controller.rb → abstract_controller.rb} +28 -5
  49. data/lib/merb/caching/action_cache.rb +65 -29
  50. data/lib/merb/caching/fragment_cache.rb +9 -4
  51. data/lib/merb/caching/store/file_cache.rb +22 -14
  52. data/lib/merb/caching/store/memory_cache.rb +26 -8
  53. data/lib/merb/{merb_constants.rb → constants.rb} +9 -7
  54. data/lib/merb/controller.rb +178 -0
  55. data/lib/merb/core_ext.rb +13 -11
  56. data/lib/merb/core_ext/array.rb +0 -0
  57. data/lib/merb/core_ext/{merb_class.rb → class.rb} +0 -0
  58. data/lib/merb/core_ext/{merb_enumerable.rb → enumerable.rb} +0 -0
  59. data/lib/merb/core_ext/get_args.rb +52 -0
  60. data/lib/merb/core_ext/{merb_hash.rb → hash.rb} +40 -11
  61. data/lib/merb/core_ext/{merb_inflections.rb → inflections.rb} +0 -0
  62. data/lib/merb/core_ext/{merb_inflector.rb → inflector.rb} +1 -1
  63. data/lib/merb/core_ext/{merb_kernel.rb → kernel.rb} +56 -3
  64. data/lib/merb/core_ext/mash.rb +88 -0
  65. data/lib/merb/core_ext/{merb_module.rb → module.rb} +0 -0
  66. data/lib/merb/core_ext/{merb_numeric.rb → numeric.rb} +0 -0
  67. data/lib/merb/core_ext/{merb_object.rb → object.rb} +10 -47
  68. data/lib/merb/core_ext/string.rb +56 -0
  69. data/lib/merb/core_ext/{merb_symbol.rb → symbol.rb} +0 -0
  70. data/lib/merb/dispatcher.rb +109 -0
  71. data/lib/merb/{merb_drb_server.rb → drb_server.rb} +0 -0
  72. data/lib/merb/erubis_ext.rb +10 -0
  73. data/lib/merb/exceptions.rb +173 -0
  74. data/lib/merb/generators/merb_app/merb_app.rb +5 -25
  75. data/lib/merb/generators/merb_generator_helpers.rb +317 -0
  76. data/lib/merb/generators/merb_plugin.rb +19 -0
  77. data/lib/merb/logger.rb +65 -0
  78. data/lib/merb/{merb_mail_controller.rb → mail_controller.rb} +102 -49
  79. data/lib/merb/{merb_mailer.rb → mailer.rb} +31 -27
  80. data/lib/merb/mixins/{basic_authentication_mixin.rb → basic_authentication.rb} +3 -3
  81. data/lib/merb/mixins/{controller_mixin.rb → controller.rb} +131 -112
  82. data/lib/merb/mixins/{erubis_capture_mixin.rb → erubis_capture.rb} +12 -21
  83. data/lib/merb/mixins/{form_control_mixin.rb → form_control.rb} +6 -12
  84. data/lib/merb/mixins/render.rb +401 -0
  85. data/lib/merb/mixins/responder.rb +378 -0
  86. data/lib/merb/mixins/{view_context_mixin.rb → view_context.rb} +65 -10
  87. data/lib/merb/mixins/web_controller.rb +29 -0
  88. data/lib/merb/{merb_handler.rb → mongrel_handler.rb} +59 -38
  89. data/lib/merb/part_controller.rb +19 -0
  90. data/lib/merb/plugins.rb +16 -0
  91. data/lib/merb/rack_adapter.rb +37 -0
  92. data/lib/merb/request.rb +421 -0
  93. data/lib/merb/router.rb +576 -0
  94. data/lib/merb/{merb_server.rb → server.rb} +275 -71
  95. data/lib/merb/session.rb +10 -10
  96. data/lib/merb/session/cookie_store.rb +125 -0
  97. data/lib/merb/session/{merb_mem_cache_session.rb → mem_cache_session.rb} +22 -9
  98. data/lib/merb/session/{merb_memory_session.rb → memory_session.rb} +15 -11
  99. data/lib/merb/template.rb +35 -8
  100. data/lib/merb/template/erubis.rb +16 -10
  101. data/lib/merb/template/haml.rb +33 -20
  102. data/lib/merb/template/markaby.rb +16 -14
  103. data/lib/merb/template/xml_builder.rb +8 -4
  104. data/lib/merb/test/{merb_fake_request.rb → fake_request.rb} +11 -5
  105. data/lib/merb/test/helper.rb +31 -0
  106. data/lib/merb/test/hpricot.rb +136 -0
  107. data/lib/merb/test/{merb_multipart.rb → multipart.rb} +1 -1
  108. data/lib/merb/test/rspec.rb +93 -0
  109. data/lib/merb/{merb_upload_handler.rb → upload_handler.rb} +5 -6
  110. data/lib/merb/{merb_upload_progress.rb → upload_progress.rb} +1 -1
  111. data/lib/merb/{merb_view_context.rb → view_context.rb} +27 -42
  112. data/lib/{merb_tasks.rb → tasks.rb} +0 -0
  113. data/lib/tasks/merb.rake +21 -11
  114. data/merb_default_generators/model/USAGE +0 -0
  115. data/merb_default_generators/model/model_generator.rb +16 -0
  116. data/merb_default_generators/model/templates/new_model_template.erb +5 -0
  117. data/merb_default_generators/resource_controller/USAGE +0 -0
  118. data/merb_default_generators/resource_controller/resource_controller_generator.rb +26 -0
  119. data/merb_default_generators/resource_controller/templates/controller.rb +30 -0
  120. data/merb_default_generators/resource_controller/templates/edit.html.erb +1 -0
  121. data/merb_default_generators/resource_controller/templates/helper.rb +5 -0
  122. data/merb_default_generators/resource_controller/templates/index.html.erb +1 -0
  123. data/merb_default_generators/resource_controller/templates/new.html.erb +1 -0
  124. data/merb_default_generators/resource_controller/templates/show.html.erb +1 -0
  125. data/merb_generators/controller/USAGE +5 -0
  126. data/merb_generators/controller/controller_generator.rb +16 -0
  127. data/merb_generators/controller/templates/controller.rb +8 -0
  128. data/merb_generators/controller/templates/helper.rb +5 -0
  129. data/merb_generators/controller/templates/index.html.erb +3 -0
  130. data/merb_generators/resource/USAGE +0 -0
  131. data/merb_generators/resource/resource_generator.rb +60 -0
  132. data/rspec_generators/merb_controller_test/merb_controller_test_generator.rb +67 -0
  133. data/rspec_generators/merb_controller_test/templates/controller_spec.rb +8 -0
  134. data/rspec_generators/merb_controller_test/templates/edit_spec.rb +12 -0
  135. data/rspec_generators/merb_controller_test/templates/helper_spec.rb +5 -0
  136. data/rspec_generators/merb_controller_test/templates/index_spec.rb +12 -0
  137. data/rspec_generators/merb_controller_test/templates/new_spec.rb +12 -0
  138. data/rspec_generators/merb_controller_test/templates/show_spec.rb +5 -0
  139. data/rspec_generators/merb_model_test/merb_model_test_generator.rb +26 -0
  140. data/rspec_generators/merb_model_test/templates/model_spec_template.erb +7 -0
  141. data/script/destroy +14 -0
  142. data/script/generate +14 -0
  143. data/test_unit_generators/merb_controller_test/merb_controller_test_generator.rb +53 -0
  144. data/test_unit_generators/merb_controller_test/templates/functional_test.rb +17 -0
  145. data/test_unit_generators/merb_controller_test/templates/helper_test.rb +9 -0
  146. data/test_unit_generators/merb_model_test/merb_model_test_generator.rb +29 -0
  147. data/test_unit_generators/merb_model_test/templates/model_test_unit_template.erb +9 -0
  148. metadata +172 -94
  149. data/examples/README_EXAMPLES +0 -10
  150. data/examples/skeleton/Rakefile +0 -68
  151. data/examples/skeleton/dist/app/views/layout/application.herb +0 -12
  152. data/examples/skeleton/dist/conf/database.yml +0 -23
  153. data/examples/skeleton/dist/conf/merb.yml +0 -57
  154. data/examples/skeleton/dist/conf/merb_init.rb +0 -24
  155. data/examples/skeleton/dist/conf/router.rb +0 -22
  156. data/examples/skeleton/dist/conf/upload.conf +0 -5
  157. data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +0 -14
  158. data/examples/skeleton/script/new_migration +0 -21
  159. data/lib/merb/core_ext/merb_string.rb +0 -18
  160. data/lib/merb/merb_controller.rb +0 -206
  161. data/lib/merb/merb_dispatcher.rb +0 -87
  162. data/lib/merb/merb_exceptions.rb +0 -319
  163. data/lib/merb/merb_part_controller.rb +0 -42
  164. data/lib/merb/merb_plugins.rb +0 -293
  165. data/lib/merb/merb_request.rb +0 -165
  166. data/lib/merb/merb_router.rb +0 -309
  167. data/lib/merb/merb_yaml_store.rb +0 -31
  168. data/lib/merb/mixins/render_mixin.rb +0 -283
  169. data/lib/merb/mixins/responder_mixin.rb +0 -159
  170. data/lib/merb/session/merb_ar_session.rb +0 -131
  171. data/lib/merb/vendor/paginator/README.txt +0 -84
  172. data/lib/merb/vendor/paginator/paginator.rb +0 -124
  173. data/lib/tasks/db.rake +0 -55
@@ -0,0 +1,19 @@
1
+ require 'fileutils'
2
+ require 'find'
3
+
4
+ module Merb
5
+
6
+ class PluginGenerator
7
+ def self.run(path)
8
+ require 'rubygems'
9
+ require 'rubigen'
10
+
11
+ require 'rubigen/scripts/generate'
12
+ RubiGen::Base.use_application_sources!
13
+ RubiGen::Scripts::Generate.new.run([path], :generator => 'merb_plugin', :backtrace => true)
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+
@@ -0,0 +1,65 @@
1
+ module Merb
2
+
3
+ class Logger
4
+ module Severity
5
+ DEBUG = 0
6
+ INFO = 1
7
+ WARN = 2
8
+ ERROR = 3
9
+ FATAL = 4
10
+ UNKNOWN = 5
11
+ end
12
+ include Severity
13
+
14
+ attr_accessor :level
15
+ attr_reader :buffer
16
+
17
+ def initialize(log, level = DEBUG)
18
+ @level = level
19
+ @buffer = []
20
+ if log.respond_to?(:write)
21
+ @log = log
22
+ elsif File.exist?(log)
23
+ @log = open(log, (File::WRONLY | File::APPEND))
24
+ @log.sync = true
25
+ else
26
+ @log = open(log, (File::WRONLY | File::APPEND | File::CREAT))
27
+ @log.sync = true
28
+ @log.write("# Logfile created on %s\n" % [Time.now.to_s])
29
+ end
30
+ end
31
+
32
+ def flush
33
+ @log.write @buffer.slice!(0..-1).to_s unless @buffer.size == 0
34
+ end
35
+
36
+ def close
37
+ flush
38
+ @log.close if @log.respond_to?(:close)
39
+ @log = nil
40
+ end
41
+
42
+ def add(severity, message = nil, progname = nil, &block)
43
+ return if @level > severity
44
+ message = (message || (block && block.call) || progname).to_s
45
+ # If a newline is necessary then create a new message ending with a newline.
46
+ # Ensures that the original message is not mutated.
47
+ message = "#{message}\n" unless message[-1] == ?\n
48
+ @buffer << message
49
+ message
50
+ end
51
+
52
+ Severity.constants.each do |severity|
53
+ class_eval <<-EOT
54
+ def #{severity.downcase}(message = nil, progname = nil, &block)
55
+ add(#{severity}, message, progname, &block)
56
+ end
57
+
58
+ def #{severity.downcase}?
59
+ #{severity} >= @level
60
+ end
61
+ EOT
62
+ end
63
+
64
+ end
65
+ end
@@ -1,9 +1,72 @@
1
- require File.dirname(__FILE__)+'/merb_mailer'
2
1
 
3
2
  module Merb
3
+
4
+ # Sending mail from a controller involves three steps:
5
+ #
6
+ # * Set mail settings in merb_init.rb (Not shown here...see the Mailer docs).
7
+ # * Create a MailController subclass with actions and templates.
8
+ # * Call the MailController from another Controller via the send_mail method.
9
+ #
10
+ # First, create a file in app/mailers that subclasses Merb::MailController.
11
+ # The actions in this controller will do nothing but render mail.
12
+ #
13
+ # # app/mailers/article_mailer.rb
14
+ # class ArticleMailer < Merb::MailController
15
+ #
16
+ # def notify
17
+ # @user = params[:user]
18
+ # render_mail
19
+ # end
20
+ #
21
+ # end
22
+ #
23
+ # You also can access the params hash for values passed with the
24
+ # Controller.send_mail method. See also the documentation for
25
+ # render_mail to see all the ways it can be called.
26
+ #
27
+ # Create a template in a subdirectory of app/mailers/views that corresponds
28
+ # to the controller and action name. Put plain text and ERB tags here:
29
+ #
30
+ # # app/mailers/views/article_mailer/notify.text.erb
31
+ # Hey, <%= @user.name %>,
32
+ #
33
+ # We're running a sale on dog bones!
34
+ #
35
+ # Finally, call the Controller.send_mail method from a standard
36
+ # Merb controller.
37
+ #
38
+ # class Articles < Application
39
+ #
40
+ # def index
41
+ # @user = User.find_by_name('louie')
42
+ #
43
+ # send_mail(ArticleMailer, :notify, {
44
+ # :from => "me@example.com",
45
+ # :to => "louie@example.com",
46
+ # :subject => "Sale on Dog Bones!"
47
+ # }, { :user => @user })
48
+ # render
49
+ # end
50
+ #
51
+ # end
52
+ #
53
+ # Note: If you don't pass a fourth argument to Controller.send_mail,
54
+ # the controller's params will be sent to the MailController subclass
55
+ # as params. However, you can explicitly send a hash of objects that
56
+ # will populate the params hash instead. In either case, you must
57
+ # set instance variables in the MailController's actions if you
58
+ # want to use them in the MailController's views.
59
+ #
60
+ # The MailController class is very powerful. You can:
61
+ #
62
+ # * Send multipart email with a single call to render_mail.
63
+ # * Attach files.
64
+ # * Render layouts and other templates.
65
+ # * Use any template engine supported by Merb.
66
+
4
67
  class MailController < AbstractController
5
68
 
6
- self._template_root = File.expand_path(DIST_ROOT / "app/mailers/views")
69
+ self._template_root = File.expand_path(self._template_root / "../app/mailers/views")
7
70
  class_inheritable_accessor :_mailer_klass
8
71
  self._mailer_klass = Merb::Mailer
9
72
 
@@ -18,6 +81,7 @@ module Merb
18
81
  @params = params
19
82
  @base_controller = controller
20
83
  @session = (controller && controller.session) || {}
84
+ super
21
85
  end
22
86
 
23
87
  def filters_halted
@@ -32,13 +96,13 @@ module Merb
32
96
  # default Merb render method.
33
97
  #
34
98
  # First of all, you'll need to store email files in your
35
- # dist/app/mailers/views directory. They should be under a directory that
99
+ # app/mailers/views directory. They should be under a directory that
36
100
  # matches the name of your mailer (e.g. TestMailer's views would be stored
37
101
  # under test_mailer).
38
102
  #
39
103
  # The files themselves should be named action_name.mime_type.extension. For
40
- # example, a herb template that should be the HTML part of the email, and
41
- # rendered from the "foo" action would be named foo.html.herb.
104
+ # example, an erb template that should be the HTML part of the email, and
105
+ # rendered from the "foo" action would be named foo.html.erb.
42
106
  #
43
107
  # The only mime-types currently supported are "html" and "text", which
44
108
  # correspond to text/html and text/plain respectively. All template systems
@@ -91,37 +155,41 @@ module Merb
91
155
  #
92
156
  # render_mail :html => :foo, :text => "BAR"
93
157
  def render_mail(options = @method)
94
- # If the options are not a hash, normalize to an action hash
158
+ @_missing_templates = false # used to make sure that at least one template was found
159
+ # # If the options are not a hash, normalize to an action hash
95
160
  options = {:action => {:html => options, :text => options}} if !options.is_a?(Hash)
96
- # Normalize :html => :sym and :text => :sym to an action hash
97
- options[:action] ||= {}
98
- options[:action][:html] = options.delete(:html) if options[:html].is_a?(Symbol)
99
- options[:action][:text] = options.delete(:text) if options[:text].is_a?(Symbol)
100
- options.delete(:action) if options[:action].empty?
101
-
102
- # set type to action or template, if the hash includes :action or :template
103
- type = options.keys.find {|x| x.to_s =~ /(action|template)/}
104
- # normalize :action/:template => ... to :action/template => {:html => ..., :text => ...}
105
- options[type] = {:html => options[type], :text => options[type]} if type && !options[type].is_a?(Hash)
106
- for mime_type in [:html, :text]
107
- # if action or template, get the template to render. If not, get the string to render. If none
108
- # are available, use the default params[:action]
109
- renderer = (type && options[type][mime_type] ? options[type][mime_type] : options[mime_type]).to_s
110
- # action or template
111
- if type && options[type][mime_type]
112
- # build the template to render
113
- mime_renderer = renderer + ".#{mime_type}" if !(renderer =~ /.#{mime_type}$/)
114
- if find_template(type => mime_renderer)
115
- @mail.send((mime_type == :html ? "rawhtml=" : "text="),
116
- render(type => mime_renderer, :layout => mimed_layout(mime_type, options[:layout])))
117
- elsif find_template(type => renderer) && mime_type == :text
118
- @mail.text = render(type => renderer, :layout => options[:layout])
161
+
162
+ # Take care of the options
163
+ use_options = {}
164
+ opts = options.dup
165
+ actions = opts.delete(:action) if opts[:action].is_a?(Hash)
166
+ templates = opts.delete(:template) if opts[:template].is_a?(Hash)
167
+
168
+ # Prepare the options hash for each format
169
+ # We need to delete anything relating to the other format here
170
+ # before we try to render the template.
171
+ [:html, :text].each do |fmt|
172
+ opts_hash = use_options[fmt] = {}
173
+ opts_hash[fmt] = opts.delete(fmt)
174
+ opts_hash[fmt] ||= actions[fmt] if actions && actions[fmt]
175
+ opts_hash[:tempalte] = templates[fmt] if templates && templates[fmt]
176
+ end
177
+
178
+ # Send the result to the mailer
179
+ { :html => "rawhtml=", :text => "text="}.each do |fmt,meth|
180
+ begin
181
+ value = render use_options[fmt].merge!(opts).merge!(:clean_context => true, :format => fmt)
182
+ @mail.send(meth,value) unless value.nil? || value.empty?
183
+ rescue => e
184
+ # An error should be logged if no template is found instead of an error raised
185
+ if @_missing_templates
186
+ MERB_LOGGER.error(e)
187
+ else
188
+ @_missing_templates = true
119
189
  end
120
- # string to render
121
- else
122
- @mail.send((mime_type == :html ? "rawhtml=" : "text="), renderer)
123
190
  end
124
191
  end
192
+ @mail
125
193
  end
126
194
 
127
195
  # Attaches a file or multiple files to an email. You call this from a
@@ -163,6 +231,8 @@ module Merb
163
231
  @mailer = self.class._mailer_klass.new(mail_params)
164
232
  @mail = @mailer.mail
165
233
  @method = method
234
+
235
+ # dipatch and render use params[:action], so set it
166
236
  params[:action] = method
167
237
  body = dispatch method
168
238
  if !@mail.html.blank? || !@mail.text.blank?
@@ -179,22 +249,5 @@ module Merb
179
249
  new({}).dispatch_and_deliver method, mail_params
180
250
  end
181
251
 
182
- private
183
- def mimed_layout(mime_type, layout = nil)
184
- if layout && layout != :application
185
- layout_choice = find_template(:layout => layout + ".#{mime_type}") ||
186
- find_template(:layout => layout)
187
- elsif name = find_template(:layout => self.class.name.snake_case + ".#{mime_type}")
188
- layout_choice = name
189
- elsif name = find_template(:layout => self.class.name.snake_case)
190
- layout_choice = name
191
- elsif name = find_template(:layout => "application.#{mime_type}")
192
- layout_choice = name
193
- else
194
- layout_choice = find_template(:layout => "application")
195
- end
196
- layout_choice = layout_choice.split("/").last.split(".")[0...-1].join(".")
197
- end
198
-
199
252
  end
200
253
  end
@@ -11,6 +11,32 @@ class MailFactory
11
11
  end
12
12
 
13
13
  module Merb
14
+
15
+ # You'll need a simple config like this in merb_init.rb if you want
16
+ # to actually send mail:
17
+ #
18
+ # Merb::Mailer.config = {
19
+ # :host=>'smtp.yourserver.com',
20
+ # :port=>'25',
21
+ # :user=>'user',
22
+ # :pass=>'pass',
23
+ # :auth=>:plain # :plain, :login, or :cram_md5, default :plain
24
+ # }
25
+ #
26
+ # or
27
+ #
28
+ # Merb::Mailer.config = {:sendmail_path => '/somewhere/odd')
29
+ # Merb::Mailer.delivery_method = :sendmail
30
+ #
31
+ # You could send mail manually like this (but it's better to use
32
+ # a MailController instead).
33
+ #
34
+ # m = Merb::Mailer.new :to => 'foo@bar.com',
35
+ # :from => 'bar@foo.com',
36
+ # :subject => 'Welcome to whatever!',
37
+ # :body => partial(:sometemplate)
38
+ # m.deliver!
39
+
14
40
  class Mailer
15
41
 
16
42
  class_inheritable_accessor :config, :delivery_method, :deliveries
@@ -18,7 +44,7 @@ module Merb
18
44
  self.deliveries = []
19
45
 
20
46
  def sendmail
21
- sendmail = IO.popen("sendmail #{@mail.to}", 'w+')
47
+ sendmail = IO.popen("#{config[:sendmail_path]} #{@mail.to}", 'w+')
22
48
  sendmail.puts @mail.to_s
23
49
  sendmail.close
24
50
  end
@@ -50,34 +76,12 @@ module Merb
50
76
  end
51
77
 
52
78
  def initialize(o={})
53
- self.config = :sendmail if config.nil?
79
+ self.config = {:sendmail_path => '/usr/sbin/sendmail'} if config.nil?
54
80
  o[:rawhtml] = o.delete(:html)
55
- @mail = returning MailFactory.new() do |m|
56
- o.each { |k,v| m.send "#{k}=", v }
57
- end
81
+ m = MailFactory.new()
82
+ o.each { |k,v| m.send "#{k}=", v }
83
+ @mail = m
58
84
  end
59
85
 
60
86
  end
61
87
  end
62
-
63
- =begin
64
-
65
-
66
- Merb::Mailer.config = {
67
- :host=>'smtp.yourserver.com',
68
- :port=>'25',
69
- :user=>'user',
70
- :pass=>'pass',
71
- :auth=>:plain # :plain, :login, or :cram_md5, default :plain
72
- }
73
-
74
- Merb::Mailer.delivery_method = :sendmail
75
-
76
- m = Merb::Mailer.new :to => 'foo@bar.com',
77
- :from => 'bar@foo.com',
78
- :subject => 'Welcome to whatever!',
79
- :body => partial(:sometemplate)
80
- m.deliver!
81
-
82
-
83
- =end
@@ -1,6 +1,6 @@
1
1
  module Merb
2
2
 
3
- module Authentication
3
+ module AuthenticationMixin
4
4
  require 'base64'
5
5
 
6
6
  def credentials
@@ -13,7 +13,7 @@ module Merb
13
13
 
14
14
  def authenticated?
15
15
  username, password = *credentials
16
- username == Merb::Server.basic_auth[:username] and password == Merb::Server.basic_auth[:password]
16
+ username == Merb::Server.config[:basic_auth][:username] and password == Merb::Server.config[:basic_auth][:password]
17
17
  end
18
18
 
19
19
  def basic_authentication
@@ -26,7 +26,7 @@ module Merb
26
26
  set_status(401)
27
27
  headers['Content-type'] = 'text/plain'
28
28
  headers['Status'] = 'Unauthorized'
29
- headers['WWW-Authenticate'] = "Basic realm=\"#{Merb::Server.basic_auth[:domain]}\""
29
+ headers['WWW-Authenticate'] = "Basic realm=\"#{Merb::Server.config[:basic_auth][:domain]}\""
30
30
  return 'Unauthorized'
31
31
  end
32
32
 
@@ -4,6 +4,15 @@ module Merb
4
4
  # Returns a URL according to the defined route. Accepts the path and
5
5
  # an options hash. The path specifies the route requested. The options
6
6
  # hash fills in the dynamic parts of the route.
7
+ #
8
+ # Merb routes can often be one-way; if they use a regex to define
9
+ # the route, then knowing the controller & action won't be enough
10
+ # to reverse-generate the route. However, if you use the default
11
+ # /controller/action/id?query route, +default_route+ can generate
12
+ # it for you.
13
+ #
14
+ # For easy reverse-routes that use a Regex, be sure to also add
15
+ # a name to the route, so +url+ can find it.
7
16
  #
8
17
  # Nested resources such as:
9
18
  # r.resources :blogposts do |post|
@@ -39,123 +48,129 @@ module Merb
39
48
  # url(:edit_comment, @comment) # => /blogposts/1/comments/1/edit
40
49
  # url(:custom_new_comment, :blogpost => @post)
41
50
  #
42
- def url(path, o={})
43
- ::Merb::Router.generate(path,o)
44
- end
45
-
46
- protected
47
- NAME_REGEX = /Content-Disposition:.* name="?([^\";]*)"?/ni.freeze
48
- CONTENT_TYPE_REGEX = /Content-Type: (.*)\r\n/ni.freeze
49
- FILENAME_REGEX = /Content-Disposition:.* filename="?([^\";]*)"?/ni.freeze
50
- CRLF = "\r\n".freeze
51
- EOL = CRLF
52
- def parse_multipart(request,boundary,env)
53
- boundary = "--#{boundary}"
54
- paramhsh = {}
55
- buf = ""
56
- content_length = env['CONTENT_LENGTH'].to_i
57
- input = request
58
- input.binmode if defined? input.binmode
59
- boundary_size = boundary.size + EOL.size
60
- bufsize = 16384
61
- content_length -= boundary_size
62
- status = input.read(boundary_size)
63
- raise EOFError, "bad content body" unless status == boundary + EOL
64
- rx = /(?:#{EOL})?#{Regexp.quote(boundary,'n')}(#{EOL}|--)/
65
- loop {
66
- head = nil
67
- body = ''
68
- filename = content_type = name = nil
69
- read_size = 0
70
- until head && buf =~ rx
71
- i = buf.index("\r\n\r\n")
72
- if( i == nil && read_size == 0 && content_length == 0 )
73
- content_length = -1
74
- break
75
- end
76
- if !head && i
77
- head = buf.slice!(0, i+2) # First \r\n
78
- buf.slice!(0, 2) # Second \r\n
79
- filename = head[FILENAME_REGEX, 1]
80
- content_type = head[CONTENT_TYPE_REGEX, 1]
81
- name = head[NAME_REGEX, 1]
82
-
83
- if filename && !filename.empty?
84
- body = Tempfile.new(:Merb)
85
- body.binmode if defined? body.binmode
86
- end
87
- next
88
- end
89
-
90
-
91
- # Save the read body part.
92
- if head && (boundary_size+4 < buf.size)
93
- body << buf.slice!(0, buf.size - (boundary_size+4))
94
- end
51
+ # url(:page => 2) # => /posts/show/1?page=2
52
+ # url(:new_post, :page => 3) # => /posts/new?page=3
53
+ # url('/go/here', :page => 3) # => /go/here?page=3
54
+ #
55
+ # url(:controller => "welcome") # => /welcome
56
+ # url(:controller => "welcome", :action => "greet")
57
+ # # => /welcome/greet
58
+ #
59
+ def url(route_name = nil, new_params = {})
60
+ if route_name.is_a?(Hash)
61
+ new_params = route_name
62
+ route_name = nil
63
+ end
95
64
 
96
- read_size = bufsize < content_length ? bufsize : content_length
97
- if( read_size > 0 )
98
- c = input.read(read_size)
99
- raise EOFError, "bad content body" if c.nil? || c.empty?
100
- buf << c
101
- content_length -= c.size
102
- end
65
+ url = if new_params.respond_to?(:keys) &&
66
+ !(new_params.keys & [:controller, :action, :id]).empty?
67
+ url_from_default_route(new_params)
68
+ elsif route_name.nil? && !route.regexp?
69
+ url_from_route(route, new_params)
70
+ elsif route_name.nil?
71
+ request.path + (new_params.empty? ? "" : "?" + params_to_query_string(new_params))
72
+ elsif route_name.is_a?(Symbol)
73
+ url_from_route(route_name, new_params)
74
+ elsif route_name.is_a?(String)
75
+ route_name + (new_params.empty? ? "" : "?" + params_to_query_string(new_params))
76
+ else
77
+ raise "URL not generated: #{route_name.inspect}, #{new_params.inspect}"
103
78
  end
79
+ url = MerbHandler.path_prefix + url if MerbHandler.path_prefix
80
+ url
81
+ end
104
82
 
105
- # Save the rest.
106
- if i = buf.index(rx)
107
- body << buf.slice!(0, i)
108
- buf.slice!(0, boundary_size+2)
109
-
110
- content_length = -1 if $1 == "--"
111
- end
112
-
113
- if filename && !filename.empty?
114
- body.rewind
115
- data = {
116
- :filename => File.basename(filename),
117
- :content_type => content_type,
118
- :tempfile => body,
119
- :size => File.size(body)
120
- }
121
- else
122
- data = body
83
+ def url_from_route(symbol, new_params = {})
84
+ route = symbol.is_a?(Symbol) ? Merb::Router.named_routes[symbol] : symbol
85
+ raise "URL could not be constructed. Route symbol not found: #{symbol.inspect}" unless route
86
+ path = route.generate(new_params, params)
87
+ keys = route.symbol_segments
88
+ if new_params.is_a? Hash
89
+ if ext = format_extension(new_params)
90
+ new_params.delete(:format)
91
+ path += "." + ext
123
92
  end
124
- paramhsh = normalize_params(paramhsh,name,data)
125
- break if buf.empty? || content_length == -1
126
- }
127
- paramhsh
93
+ extras = new_params.reject{ |k, v| keys.include?(k) }
94
+ path += "?" + params_to_query_string(extras) unless extras.empty?
95
+ end
96
+ path
128
97
  end
129
98
 
130
- def normalize_params(parms, key, val)
131
- case key
132
- when /(.+)\[(.+)\]\[\]$/
133
- parms[$1] ||= {}
134
- parms[$1] = normalize_params(parms[$1], "#{$2}[]", val)
135
- when /(.+)\[(.+)\]$/
136
- parms[$1] ||= {}
137
- parms[$1] = normalize_params(parms[$1], $2, val)
138
- when /(.+)\[\]$/
139
- (parms[$1] ||= []) << val
99
+ # this is pretty ugly, but it works. TODO: make this cleaner
100
+ def url_from_default_route(new_params)
101
+ query_params = new_params.reject do |k,v|
102
+ [:controller, :action, :id, :format].include?(k)
103
+ end
104
+ controller = new_params[:controller] || params[:controller]
105
+ controller = params[:controller] if controller == :current
106
+ url = "/#{controller}"
107
+ if new_params[:action] || new_params[:id] ||
108
+ new_params[:format] || !query_params.empty?
109
+ action = new_params[:action] || params[:action]
110
+ url += "/#{action}"
111
+ end
112
+ if new_params[:id]
113
+ url += "/#{new_params[:id]}"
114
+ end
115
+ if format = new_params[:format]
116
+ format = params[:format] if format == :current
117
+ url += ".#{format}"
118
+ end
119
+ unless query_params.empty?
120
+ url += "?" + params_to_query_string(query_params)
121
+ end
122
+ url
123
+ end
124
+
125
+ protected
126
+
127
+ # Creates query string from params, supporting nested arrays and hashes.
128
+ # ==== Example
129
+ # params_to_query_string(:user => {:filter => {:name => "quux*"}, :order => ["name"]})
130
+ # # => user[filter][name]=quux%2A&user[order][]=name
131
+ def params_to_query_string(value, prefix = nil)
132
+ case value
133
+ when Array
134
+ value.map { |v|
135
+ params_to_query_string(v, "#{prefix}[]")
136
+ } * "&"
137
+ when Hash
138
+ value.map { |k, v|
139
+ params_to_query_string(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
140
+ } * "&"
140
141
  else
141
- parms[key] = val if val
142
+ "#{prefix}=#{escape(value)}"
142
143
  end
143
- parms
144
144
  end
145
145
 
146
-
147
- # parses a query string or the payload of a POST
148
- # request into the params hash. So for example:
149
- # /foo?bar=nik&post[title]=heya&post[body]=whatever
150
- # parses into:
151
- # {:bar => 'nik', :post => {:title => 'heya', :body => 'whatever'}}
152
- def query_parse(qs, d = '&;')
153
- m = proc {|_,o,n|o.update(n,&m)rescue([*o]<<n)}
154
- (qs||'').split(/[#{d}] */n).inject(Hash[]) { |h,p|
155
- k, v=unescape(p).split('=',2)
156
- h.update(k.split(/[\]\[]+/).reverse.
157
- inject(v) { |x,i| Hash[i,x] },&m)
158
- }
146
+ # +format_extension+ dictates when named route urls generated by #url
147
+ # will have a file-extension. It will return false or the format
148
+ # extension to append.
149
+ #
150
+ # url(:post, :id => post, :format => 'xml')
151
+ #
152
+ # generates:
153
+ #
154
+ # /posts/34.xml
155
+ #
156
+ # by default NON-HTML urls will be given an extension. it is posible
157
+ # to override this behaviour by setting +:use_format_in_urls+ in your
158
+ # server config (merb.yml) to either true/false
159
+ #
160
+ # +true+ would result in ALL urls (even html) being given extensions
161
+ # this is often desirable when you have many formats and dont
162
+ # wish to treat .html any differently from
163
+ # +false+ would result in NO urls being given extensions, and format
164
+ # gets treated just like any other param. leave it unset for
165
+ # the default behavior
166
+ #
167
+ def format_extension(new_params={})
168
+ use_format = Merb::Server.config[:use_format_in_urls]
169
+ if use_format.nil?
170
+ prms = params.merge(new_params)
171
+ use_format = prms[:format] != 'html' && prms[:format]
172
+ end
173
+ use_format
159
174
  end
160
175
 
161
176
  # render using chunked encoding
@@ -231,10 +246,8 @@ module Merb
231
246
  # stream_file( { :filename => file_name,
232
247
  # :type => content_type,
233
248
  # :content_length => content_length }) do
234
- # response.send_status(opts[:content_length])
235
- # response.send_header
236
249
  # AWS::S3::S3Object.stream(user.folder_name + "-" + user_file.unique_id, bucket_name) do |chunk|
237
- # @response.write chunk
250
+ # response.write chunk
238
251
  # end
239
252
  # end
240
253
  def stream_file(opts={}, &stream)
@@ -247,6 +260,8 @@ module Merb
247
260
  'Content-Transfer-Encoding' => 'binary',
248
261
  'CONTENT-LENGTH' => opts[:content_length]
249
262
  )
263
+ response.send_status(opts[:content_length])
264
+ response.send_header
250
265
  stream
251
266
  end
252
267
 
@@ -261,8 +276,12 @@ module Merb
261
276
 
262
277
  # Sets a cookie to be included in the response.
263
278
  def set_cookie(name, value, expires)
264
- (headers['Set-Cookie'] ||='') <<
265
- (Merb::Const::SET_COOKIE % [name.to_s, escape(value.to_s), expires.rfc2822])
279
+ (headers['Set-Cookie'] ||='') << (Merb::Const::SET_COOKIE % [
280
+ name.to_s,
281
+ escape(value.to_s),
282
+ # Cookie expiration time must be GMT. See RFC 2109
283
+ expires.gmtime.strftime(Merb::Const::COOKIE_EXPIRATION_FORMAT)
284
+ ])
266
285
  end
267
286
 
268
287
  # Marks a cookie as deleted. The cookie is given an expires stamp in