merb-core 0.9.10 → 0.9.11

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -230,6 +230,12 @@ end
230
230
  setup_specs("mri", "spec")
231
231
  setup_specs("jruby", "jruby -S spec")
232
232
 
233
+ task "specs:core_ext" do
234
+ require "lib/merb-core/test/run_specs"
235
+ run_specs("spec/public/core_ext/*_spec.rb", "spec", "-c -f o")
236
+ end
237
+
238
+ task "spec" => ["specs:mri"]
233
239
  task "specs" => ["specs:mri"]
234
240
  task "specs:private" => ["specs:mri:private"]
235
241
  task "specs:public" => ["specs:mri:public"]
@@ -110,12 +110,14 @@ module Merb
110
110
  #
111
111
  # @api public
112
112
  def start(argv = ARGV)
113
- Merb::Config[:log_stream] = STDOUT
113
+ Merb::Config[:original_log_stream] = Merb::Config[:log_stream]
114
+ Merb::Config[:log_stream] ||= STDOUT
114
115
  if Hash === argv
115
116
  Merb::Config.setup(argv)
116
- else
117
+ elsif !argv.nil?
117
118
  Merb::Config.parse_args(argv)
118
119
  end
120
+
119
121
  Merb::Config[:log_stream] = STDOUT
120
122
 
121
123
  Merb.environment = Merb::Config[:environment]
@@ -277,7 +279,7 @@ module Merb
277
279
  #
278
280
  # @api public
279
281
  def root=(value)
280
- @root = File.expand_path(value) + File::SEPARATOR
282
+ @root = value
281
283
  end
282
284
 
283
285
  # ==== Parameters
@@ -400,7 +402,12 @@ module Merb
400
402
  Merb.logger.fatal!
401
403
 
402
404
  print_colorized_backtrace(e) if e && Merb::Config[:verbose]
403
- exit(1)
405
+
406
+ if Merb::Config[:show_ugly_backtraces]
407
+ raise e
408
+ else
409
+ exit(1)
410
+ end
404
411
  end
405
412
 
406
413
  # Print a colorized backtrace to the merb logger.
@@ -14,6 +14,7 @@ module Merb
14
14
  autoload :Rack, "merb-core/rack"
15
15
  autoload :RenderMixin, "merb-core/controller/mixins/render"
16
16
  autoload :Request, "merb-core/dispatch/request"
17
+ autoload :Parse, "merb-core/dispatch/request_parsers.rb"
17
18
  autoload :ResponderMixin, "merb-core/controller/mixins/responder"
18
19
  autoload :Router, "merb-core/dispatch/router"
19
20
  autoload :Test, "merb-core/test"
@@ -167,10 +167,28 @@ module Merb
167
167
  before_load_callbacks << block
168
168
  end
169
169
 
170
+ # Execute a block of code before master process is shut down.
171
+ # Only makes sense on platforms where Merb server can use forking.
172
+ #
173
+ # ==== Parameters
174
+ # &block::
175
+ # A block to be added to the callbacks that will be executed
176
+ # before master process is shut down.
177
+ #
178
+ # @api public
170
179
  def before_master_shutdown(&block)
171
180
  before_master_shutdown_callbacks << block
172
181
  end
173
182
 
183
+ # Execute a block of code before worker process is shut down.
184
+ # Only makes sense on platforms where Merb server can use forking.
185
+ #
186
+ # ==== Parameters
187
+ # &block::
188
+ # A block to be added to the callbacks that will be executed
189
+ # before worker process is shut down.
190
+ #
191
+ # @api public
174
192
  def before_worker_shutdown(&block)
175
193
  before_worker_shutdown_callbacks << block
176
194
  end
@@ -201,7 +219,8 @@ class Merb::BootLoader::Logger < Merb::BootLoader
201
219
  end
202
220
  end
203
221
 
204
- Merb::Config[:log_stream] = Merb.log_stream
222
+ Merb::Config[:log_stream] =
223
+ Merb::Config[:original_log_stream] || Merb.log_stream
205
224
 
206
225
  print_warnings
207
226
 
@@ -366,6 +385,7 @@ class Merb::BootLoader::Dependencies < Merb::BootLoader
366
385
  load_initfile
367
386
  load_env_config
368
387
  end
388
+ expand_ruby_path
369
389
  enable_json_gem unless Merb::disabled?(:json)
370
390
  load_dependencies
371
391
  update_logger
@@ -497,6 +517,27 @@ class Merb::BootLoader::Dependencies < Merb::BootLoader
497
517
  end
498
518
  nil
499
519
  end
520
+
521
+ # Expands Ruby path with framework directories (for models, lib, etc). Only run once.
522
+ #
523
+ # ==== Returns
524
+ # nil
525
+ #
526
+ # @api private
527
+ def self.expand_ruby_path
528
+ # Add models, controllers, helpers and lib to the load path
529
+ unless @ran
530
+ Merb.logger.info "Expanding RUBY_PATH..." if Merb::Config[:verbose]
531
+
532
+ $LOAD_PATH.unshift Merb.dir_for(:model)
533
+ $LOAD_PATH.unshift Merb.dir_for(:controller)
534
+ $LOAD_PATH.unshift Merb.dir_for(:lib)
535
+ $LOAD_PATH.unshift Merb.dir_for(:helper)
536
+ end
537
+
538
+ @ran = true
539
+ nil
540
+ end
500
541
  end
501
542
 
502
543
  class Merb::BootLoader::MixinSession < Merb::BootLoader
@@ -564,20 +605,12 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
564
605
  #
565
606
  # @api plugin
566
607
  def run
567
- # Add models, controllers, helpers and lib to the load path
568
- unless @ran
569
- $LOAD_PATH.unshift Merb.dir_for(:model)
570
- $LOAD_PATH.unshift Merb.dir_for(:controller)
571
- $LOAD_PATH.unshift Merb.dir_for(:lib)
572
- $LOAD_PATH.unshift Merb.dir_for(:helper)
573
- end
574
-
575
- @ran = true
576
608
  # process name you see in ps output
577
609
  $0 = "merb#{" : " + Merb::Config[:name] if Merb::Config[:name]} : master"
578
610
 
579
611
  # Log the process configuration user defined signal 1 (SIGUSR1) is received.
580
612
  Merb.trap("USR1") do
613
+ require "yaml"
581
614
  Merb.logger.fatal! "Configuration:\n#{Merb::Config.to_hash.merge(:pid => $$).to_yaml}\n\n"
582
615
  end
583
616
 
@@ -797,7 +830,7 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
797
830
  # Ignore the file for syntax errors. The next time
798
831
  # the file is changed, it'll be reloaded again
799
832
  begin
800
- load file
833
+ require file
801
834
  rescue SyntaxError => e
802
835
  Merb.logger.error "Cannot load #{file} because of syntax error: #{e.message}"
803
836
  ensure
@@ -350,6 +350,13 @@ module Merb
350
350
  begin
351
351
  require "ruby-debug"
352
352
  Debugger.start
353
+
354
+ # Load up any .rdebugrc files we find
355
+ [".", ENV["HOME"], ENV["HOMEPATH"]].each do |script_dir|
356
+ script_file = "#{script_dir}/.rdebugrc"
357
+ Debugger.run_script script_file, StringIO.new if File.exists?(script_file)
358
+ end
359
+
353
360
  if Debugger.respond_to?(:settings)
354
361
  Debugger.settings[:autoeval] = true
355
362
  end
@@ -661,7 +661,7 @@ class Merb::AbstractController
661
661
  # get appended
662
662
  filters << [filter, opts]
663
663
  when Symbol, String
664
- if existing_filter = filters.find {|f| f.first.to_s[filter.to_s]}
664
+ if existing_filter = filters.find {|f| f.first.to_s == filter.to_s}
665
665
  filters[ filters.index(existing_filter) ] = [filter, opts]
666
666
  else
667
667
  filters << [filter, opts]
@@ -113,7 +113,6 @@ class Merb::Controller < Merb::AbstractController
113
113
  callables.flatten.reject{|action| action =~ /^_.*/}
114
114
  end
115
115
 
116
-
117
116
  # The location to look for a template for a particular controller, context,
118
117
  # and mime-type. This is overridden from AbstractController, which defines a
119
118
  # version of this that does not involve mime-types.
@@ -133,7 +133,7 @@ module Merb
133
133
  default_redirect_options = { :message => nil, :permanent => false }
134
134
  opts = default_redirect_options.merge(opts)
135
135
  if opts[:message]
136
- notice = Merb::Request.escape([Marshal.dump(opts[:message])].pack("m"))
136
+ notice = Merb::Parse.escape([Marshal.dump(opts[:message])].pack("m"))
137
137
  url = url =~ /\?/ ? "#{url}&_message=#{notice}" : "#{url}?_message=#{notice}"
138
138
  end
139
139
  self.status = opts[:permanent] ? 301 : 302
@@ -2,7 +2,7 @@ require 'rubygems/dependency'
2
2
 
3
3
  module Gem
4
4
  class Dependency
5
- attr_accessor :require_block
5
+ attr_accessor :require_block, :require_as
6
6
  end
7
7
  end
8
8
 
@@ -18,18 +18,16 @@ module Kernel
18
18
  #
19
19
  # @api private
20
20
  def track_dependency(name, *ver, &blk)
21
- dep = Gem::Dependency.new(name, ver.empty? ? nil : ver)
22
- dep.require_block = blk
21
+ options = ver.pop if ver.last.is_a?(Hash)
22
+ new_dep = Gem::Dependency.new(name, ver.empty? ? nil : ver)
23
+ new_dep.require_block = blk
24
+ new_dep.require_as = (options && options[:require_as]) || name
23
25
 
24
- existing = Merb::BootLoader::Dependencies.dependencies.find { |d| d.name == dep.name }
25
- if existing
26
- index = Merb::BootLoader::Dependencies.dependencies.index(existing)
27
- Merb::BootLoader::Dependencies.dependencies.delete(existing)
28
- Merb::BootLoader::Dependencies.dependencies.insert(index, dep)
29
- else
30
- Merb::BootLoader::Dependencies.dependencies << dep
31
- end
32
- return dep
26
+ deps = Merb::BootLoader::Dependencies.dependencies
27
+
28
+ deps.reject! {|current| current.name == new_dep.name }
29
+ deps << new_dep
30
+ new_dep
33
31
  end
34
32
 
35
33
  # Loads the given string as a gem. Execution is deferred until
@@ -51,7 +49,7 @@ module Kernel
51
49
  #
52
50
  # @api public
53
51
  def dependency(name, *ver, &blk)
54
- immediate = ver.last.is_a?(Hash) && ver.pop[:immediate]
52
+ immediate = ver.last.delete(:immediate) if ver.last.is_a?(Hash)
55
53
  if immediate || Merb::BootLoader.finished?(Merb::BootLoader::Dependencies)
56
54
  load_dependency(name, *ver, &blk)
57
55
  else
@@ -78,20 +76,21 @@ module Kernel
78
76
  #
79
77
  # @api private
80
78
  def load_dependency(name, *ver, &blk)
81
- dep = name.is_a?(Gem::Dependency) ? name : track_dependency(name, *ver)
79
+ dep = name.is_a?(Gem::Dependency) ? name : track_dependency(name, *ver, &blk)
82
80
  gem(dep)
83
81
  rescue Gem::LoadError => e
84
82
  Merb.fatal! "The gem #{name}, #{ver.inspect} was not found", e
85
83
  ensure
86
- if block = blk || dep.require_block
84
+ begin
85
+ require dep.require_as
86
+ rescue LoadError => e
87
+ Merb.fatal! "The file #{dep.require_as} was not found", e
88
+ end
89
+
90
+ if block = dep.require_block
87
91
  block.call
88
- else
89
- begin
90
- require dep.name
91
- rescue LoadError => e
92
- Merb.fatal! "The file #{dep.name} was not found", e
93
- end
94
92
  end
93
+
95
94
  Merb.logger.verbose!("loading gem '#{dep.name}' ...")
96
95
  return dep # ensure needs explicit return
97
96
  end
@@ -299,11 +298,18 @@ module Kernel
299
298
  lines = File.read(file).split("\n")
300
299
  first_line = (f = line - size - 1) < 0 ? 0 : f
301
300
 
302
- old_lines = lines
303
- lines = lines[first_line, size * 2 + 1]
301
+ if first_line.zero?
302
+ new_size = line - 1
303
+ lines = lines[first_line, size + new_size + 1]
304
+ else
305
+ new_size = nil
306
+ lines = lines[first_line, size * 2 + 1]
307
+ end
304
308
 
305
309
  lines && lines.each_with_index do |str, index|
306
- yield index + line - size, str.chomp
310
+ line_n = index + line
311
+ line_n = (new_size.nil?) ? line_n - size : line_n - new_size
312
+ yield line_n, str.chomp
307
313
  end
308
314
  end
309
315
  end
@@ -68,7 +68,7 @@ module Merb
68
68
  options["expires"] = expiry.gmtime.strftime(Merb::Const::COOKIE_EXPIRATION_FORMAT)
69
69
  end
70
70
  secure = options.delete("secure")
71
- kookie = "#{name}=#{Merb::Request.escape(value)}; "
71
+ kookie = "#{name}=#{Merb::Parse.escape(value)}; "
72
72
  # WebKit in particular doens't like empty cookie options - skip them.
73
73
  options.each { |k, v| kookie << "#{k}=#{v}; " unless v.blank? }
74
74
  kookie << 'secure' if secure
@@ -116,7 +116,7 @@ module Merb
116
116
  # a Hash of key => value pairs.
117
117
  def cookies
118
118
  @cookies ||= begin
119
- values = self.class.query_parse(@env[Merb::Const::HTTP_COOKIE], ';,')
119
+ values = Merb::Parse.query(@env[Merb::Const::HTTP_COOKIE], ';,')
120
120
  cookies = Merb::Cookies.new(values)
121
121
  cookies.update(default_cookies) if respond_to?(:default_cookies)
122
122
  cookies
@@ -179,7 +179,7 @@ module Merb
179
179
  #
180
180
  # @api private
181
181
  def query_params
182
- @query_params ||= self.class.query_parse(query_string || '')
182
+ @query_params ||= Merb::Parse.query(query_string || '')
183
183
  end
184
184
 
185
185
  # Parameters passed in the body of the request. Ajax calls from
@@ -192,7 +192,7 @@ module Merb
192
192
  def body_params
193
193
  @body_params ||= begin
194
194
  if content_type && content_type.match(Merb::Const::FORM_URL_ENCODED_REGEXP) # or content_type.nil?
195
- self.class.query_parse(raw_post)
195
+ Merb::Parse.query(raw_post)
196
196
  end
197
197
  end
198
198
  end
@@ -226,7 +226,7 @@ module Merb
226
226
  # if the content-type is multipart
227
227
  # parse the multipart. Otherwise return {}
228
228
  if (Merb::Const::MULTIPART_REGEXP =~ content_type)
229
- self.class.parse_multipart(@body, $1, content_length)
229
+ Merb::Parse.multipart(@body, $1, content_length)
230
230
  else
231
231
  {}
232
232
  end
@@ -299,8 +299,8 @@ module Merb
299
299
  def message
300
300
  return {} unless params[:_message]
301
301
  begin
302
- Marshal.load(Merb::Request.unescape(params[:_message]).unpack("m").first)
303
- rescue ArgumentError
302
+ Marshal.load(Merb::Parse.unescape(params[:_message]).unpack("m").first)
303
+ rescue ArgumentError, TypeError
304
304
  {}
305
305
  end
306
306
  end
@@ -539,7 +539,7 @@ module Merb
539
539
  #
540
540
  # @api public
541
541
  def path_info
542
- @path_info ||= self.class.unescape(@env[Merb::Const::PATH_INFO])
542
+ @path_info ||= Merb::Parse.unescape(@env[Merb::Const::PATH_INFO])
543
543
  end
544
544
 
545
545
  # ==== Returns
@@ -555,7 +555,8 @@ module Merb
555
555
  #
556
556
  # @api public
557
557
  def host
558
- @env[Merb::Const::HTTP_X_FORWARDED_HOST] || @env[Merb::Const::HTTP_HOST]
558
+ @env[Merb::Const::HTTP_X_FORWARDED_HOST] || @env[Merb::Const::HTTP_HOST] ||
559
+ @env[Merb::Const::SERVER_NAME]
559
560
  end
560
561
 
561
562
  # ==== Parameters
@@ -603,227 +604,5 @@ module Merb
603
604
  end
604
605
  end
605
606
 
606
- class << self
607
-
608
- # ==== Parameters
609
- # value<Array, Hash, Dictionary ~to_s>:: The value for the query string.
610
- # prefix<~to_s>:: The prefix to add to the query string keys.
611
- #
612
- # ==== Returns
613
- # String:: The query string.
614
- #
615
- # ==== Alternatives
616
- # If the value is a string, the prefix will be used as the key.
617
- #
618
- # ==== Examples
619
- # params_to_query_string(10, "page")
620
- # # => "page=10"
621
- # params_to_query_string({ :page => 10, :word => "ruby" })
622
- # # => "page=10&word=ruby"
623
- # params_to_query_string({ :page => 10, :word => "ruby" }, "search")
624
- # # => "search[page]=10&search[word]=ruby"
625
- # params_to_query_string([ "ice-cream", "cake" ], "shopping_list")
626
- # # => "shopping_list[]=ice-cream&shopping_list[]=cake"
627
- #
628
- # @api private
629
- def params_to_query_string(value, prefix = nil)
630
- case value
631
- when Array
632
- value.map { |v|
633
- params_to_query_string(v, "#{prefix}[]")
634
- } * "&"
635
- when Hash, Dictionary
636
- value.map { |k, v|
637
- params_to_query_string(v, prefix ? "#{prefix}[#{Merb::Request.escape(k)}]" : Merb::Request.escape(k))
638
- } * "&"
639
- else
640
- "#{prefix}=#{Merb::Request.escape(value)}"
641
- end
642
- end
643
-
644
- # ==== Parameters
645
- # s<String>:: String to URL escape.
646
- #
647
- # ==== returns
648
- # String:: The escaped string.
649
- #
650
- # @api private
651
- def escape(s)
652
- s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
653
- '%'+$1.unpack('H2'*$1.size).join('%').upcase
654
- }.tr(' ', '+')
655
- end
656
-
657
- # ==== Parameter
658
- # s<String>:: String to URL unescape.
659
- #
660
- # ==== returns
661
- # String:: The unescaped string.
662
- #
663
- # @api private
664
- def unescape(s)
665
- s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
666
- [$1.delete('%')].pack('H*')
667
- }
668
- end
669
-
670
- # ==== Parameters
671
- # query_string<String>:: The query string.
672
- # delimiter<String>:: The query string divider. Defaults to "&".
673
- # preserve_order<Boolean>:: Preserve order of args. Defaults to false.
674
- #
675
- # ==== Returns
676
- # Mash:: The parsed query string (Dictionary if preserve_order is set).
677
- #
678
- # ==== Examples
679
- # query_parse("bar=nik&post[body]=heya")
680
- # # => { :bar => "nik", :post => { :body => "heya" } }
681
- #
682
- # @api private
683
- def query_parse(query_string, delimiter = '&;', preserve_order = false)
684
- query = preserve_order ? Dictionary.new : {}
685
- for pair in (query_string || '').split(/[#{delimiter}] */n)
686
- key, value = unescape(pair).split('=',2)
687
- next if key.nil?
688
- if key.include?('[')
689
- normalize_params(query, key, value)
690
- else
691
- query[key] = value
692
- end
693
- end
694
- preserve_order ? query : query.to_mash
695
- end
696
-
697
- NAME_REGEX = /Content-Disposition:.* name="?([^\";]*)"?/ni.freeze
698
- CONTENT_TYPE_REGEX = /Content-Type: (.*)\r\n/ni.freeze
699
- FILENAME_REGEX = /Content-Disposition:.* filename="?([^\";]*)"?/ni.freeze
700
- CRLF = "\r\n".freeze
701
- EOL = CRLF
702
-
703
- # ==== Parameters
704
- # request<IO>:: The raw request.
705
- # boundary<String>:: The boundary string.
706
- # content_length<Fixnum>:: The length of the content.
707
- #
708
- # ==== Raises
709
- # ControllerExceptions::MultiPartParseError:: Failed to parse request.
710
- #
711
- # ==== Returns
712
- # Hash:: The parsed request.
713
- #
714
- # @api private
715
- def parse_multipart(request, boundary, content_length)
716
- boundary = "--#{boundary}"
717
- paramhsh = {}
718
- buf = ""
719
- input = request
720
- input.binmode if defined? input.binmode
721
- boundary_size = boundary.size + EOL.size
722
- bufsize = 16384
723
- content_length -= boundary_size
724
- status = input.read(boundary_size)
725
- return {} if status == nil || status.empty?
726
- raise ControllerExceptions::MultiPartParseError, "bad content body:\n'#{status}' should == '#{boundary + EOL}'" unless status == boundary + EOL
727
- rx = /(?:#{EOL})?#{Regexp.quote(boundary,'n')}(#{EOL}|--)/
728
- loop {
729
- head = nil
730
- body = ''
731
- filename = content_type = name = nil
732
- read_size = 0
733
- until head && buf =~ rx
734
- i = buf.index("\r\n\r\n")
735
- if( i == nil && read_size == 0 && content_length == 0 )
736
- content_length = -1
737
- break
738
- end
739
- if !head && i
740
- head = buf.slice!(0, i+2) # First \r\n
741
- buf.slice!(0, 2) # Second \r\n
742
- filename = head[FILENAME_REGEX, 1]
743
- content_type = head[CONTENT_TYPE_REGEX, 1]
744
- name = head[NAME_REGEX, 1]
745
-
746
- if filename && !filename.empty?
747
- body = Tempfile.new(:Merb)
748
- body.binmode if defined? body.binmode
749
- end
750
- next
751
- end
752
-
753
- # Save the read body part.
754
- if head && (boundary_size+4 < buf.size)
755
- body << buf.slice!(0, buf.size - (boundary_size+4))
756
- end
757
-
758
- read_size = bufsize < content_length ? bufsize : content_length
759
- if( read_size > 0 )
760
- c = input.read(read_size)
761
- raise ControllerExceptions::MultiPartParseError, "bad content body" if c.nil? || c.empty?
762
- buf << c
763
- content_length -= c.size
764
- end
765
- end
766
-
767
- # Save the rest.
768
- if i = buf.index(rx)
769
- body << buf.slice!(0, i)
770
- buf.slice!(0, boundary_size+2)
771
-
772
- content_length = -1 if $1 == "--"
773
- end
774
-
775
- if filename && !filename.empty?
776
- body.rewind
777
- data = {
778
- :filename => File.basename(filename),
779
- :content_type => content_type,
780
- :tempfile => body,
781
- :size => File.size(body.path)
782
- }
783
- else
784
- data = body
785
- end
786
- paramhsh = normalize_params(paramhsh,name,data)
787
- break if buf.empty? || content_length == -1
788
- }
789
- paramhsh
790
- end
791
-
792
- # Converts a query string snippet to a hash and adds it to existing
793
- # parameters.
794
- #
795
- # ==== Parameters
796
- # parms<Hash>:: Parameters to add the normalized parameters to.
797
- # name<String>:: The key of the parameter to normalize.
798
- # val<String>:: The value of the parameter.
799
- #
800
- # ==== Returns
801
- # Hash:: Normalized parameters
802
- #
803
- # @api private
804
- def normalize_params(parms, name, val=nil)
805
- name =~ %r([\[\]]*([^\[\]]+)\]*)
806
- key = $1 || ''
807
- after = $' || ''
808
-
809
- if after == ""
810
- parms[key] = val
811
- elsif after == "[]"
812
- (parms[key] ||= []) << val
813
- elsif after =~ %r(^\[\]\[([^\[\]]+)\]$)
814
- child_key = $1
815
- parms[key] ||= []
816
- if parms[key].last.is_a?(Hash) && !parms[key].last.key?(child_key)
817
- parms[key].last.update(child_key => val)
818
- else
819
- parms[key] << { child_key => val }
820
- end
821
- else
822
- parms[key] ||= {}
823
- parms[key] = normalize_params(parms[key], after, val)
824
- end
825
- parms
826
- end
827
- end
828
607
  end
829
608
  end