merb-core 0.9.10 → 0.9.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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