merb 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/README +68 -58
  2. data/Rakefile +7 -6
  3. data/TODO +18 -0
  4. data/bin/merb +2 -2
  5. data/doc/rdoc/classes/Hash.html +148 -0
  6. data/doc/rdoc/classes/Merb/Config.html +161 -0
  7. data/doc/rdoc/classes/Merb/Controller.html +488 -0
  8. data/doc/rdoc/classes/Merb/RouteMatcher.html +354 -0
  9. data/doc/rdoc/classes/Merb/Server.html +288 -0
  10. data/doc/rdoc/classes/Merb.html +143 -0
  11. data/doc/rdoc/classes/MerbHandler.html +404 -0
  12. data/doc/rdoc/classes/MerbHash.html +457 -0
  13. data/doc/rdoc/classes/Noroutefound.html +172 -0
  14. data/doc/rdoc/classes/Object.html +149 -0
  15. data/doc/rdoc/classes/String.html +200 -0
  16. data/doc/rdoc/classes/Symbol.html +172 -0
  17. data/doc/rdoc/created.rid +1 -0
  18. data/doc/rdoc/files/LICENSE.html +129 -0
  19. data/doc/rdoc/files/README.html +330 -0
  20. data/doc/rdoc/files/TODO.html +154 -0
  21. data/doc/rdoc/files/lib/merb/merb_controller_rb.html +101 -0
  22. data/doc/rdoc/files/lib/merb/merb_daemon_rb.html +113 -0
  23. data/doc/rdoc/files/lib/merb/merb_handler_rb.html +108 -0
  24. data/doc/rdoc/files/lib/merb/merb_router_rb.html +101 -0
  25. data/doc/rdoc/files/lib/merb/merb_utils_rb.html +101 -0
  26. data/doc/rdoc/files/lib/merb/noroutefound_rb.html +101 -0
  27. data/doc/rdoc/files/lib/merb_config_rb.html +101 -0
  28. data/doc/rdoc/files/lib/merb_rb.html +112 -0
  29. data/doc/rdoc/fr_class_index.html +38 -0
  30. data/doc/rdoc/fr_file_index.html +38 -0
  31. data/doc/rdoc/fr_method_index.html +78 -0
  32. data/doc/rdoc/index.html +24 -0
  33. data/doc/rdoc/rdoc-style.css +208 -0
  34. data/examples/sample_app/{app → dist/app}/controllers/posts.rb +8 -8
  35. data/examples/sample_app/{app → dist/app}/controllers/test.rb +8 -5
  36. data/examples/sample_app/{app → dist/app}/controllers/upload.rb +13 -5
  37. data/examples/sample_app/dist/app/models/post.rb +13 -0
  38. data/examples/sample_app/dist/app/views/layout/application.rhtml +6 -0
  39. data/examples/sample_app/dist/app/views/layout/foo.rhtml +6 -0
  40. data/examples/sample_app/dist/app/views/layout/posts.rhtml +6 -0
  41. data/examples/sample_app/{app → dist/app}/views/posts/list.rhtml +1 -7
  42. data/examples/sample_app/{app → dist/app}/views/posts/new.rhtml +1 -7
  43. data/examples/sample_app/{app → dist/app}/views/posts/show.rhtml +1 -8
  44. data/examples/sample_app/dist/app/views/test/foo.rhtml +2 -0
  45. data/examples/sample_app/{app → dist/app}/views/test/hello.rhtml +1 -8
  46. data/examples/sample_app/{app → dist/app}/views/upload/start.rhtml +0 -8
  47. data/examples/sample_app/{app → dist/app}/views/upload/upload.rhtml +0 -0
  48. data/examples/sample_app/{conf → dist/conf}/merb_init.rb +5 -2
  49. data/examples/sample_app/{conf → dist/conf}/router.rb +6 -8
  50. data/examples/sample_app/test/test_helper.rb +1 -0
  51. data/examples/skeleton/dist/conf/merb_init.rb +21 -0
  52. data/examples/skeleton/dist/conf/router.rb +23 -0
  53. data/examples/skeleton/test/test_helper.rb +1 -0
  54. data/lib/merb/merb_controller.rb +54 -15
  55. data/lib/merb/merb_daemon.rb +35 -20
  56. data/lib/merb/merb_handler.rb +49 -38
  57. data/lib/merb/merb_router.rb +25 -28
  58. data/lib/merb/merb_utils.rb +98 -9
  59. data/{examples/sample_app/app/controllers → lib/merb}/noroutefound.rb +1 -1
  60. data/lib/merb.rb +4 -4
  61. data/lib/merb_config.rb +1 -1
  62. data/test/unit/route_matcher_test.rb +12 -31
  63. metadata +109 -28
  64. data/examples/sample_app/app/models/post.rb +0 -2
  65. data/examples/sample_app/app/views/test/foo.rhtml +0 -6
@@ -1,19 +1,21 @@
1
1
  module Merb
2
2
 
3
- # controller class for the merb pocket-framework. All of your
4
- # controllers will inherit from Merb::Controller. This superclass
5
- # takes care of parsing the incoming headers and body into params
6
- # and cookies and headers. If the request is a file upload it will
3
+ # All of your controllers will inherit from Merb::Controller. This
4
+ # superclass takes care of parsing the incoming headers and body into
5
+ # params and cookies and headers. If the request is a file upload it will
7
6
  # stream it into a tempfile and pass in the filename and tempfile object
8
7
  # to your controller via params. It also parses the ?query=string and
9
8
  # puts that into params as well.
10
9
  class Controller
10
+
11
11
  attr_accessor :status
12
- # Stolen from Camping without a twinge of remorse ;)
13
- def initialize(req, env, method=(env['REQUEST_METHOD']||"GET")) #:nodoc:
12
+
13
+
14
+ def initialize(req, env, args, method=(env['REQUEST_METHOD']||"GET")) #:nodoc:
14
15
  env = MerbHash[env.to_hash]
15
16
  puts env.inspect if $DEBUG
16
17
  puts req.inspect if $DEBUG
18
+ @layout = 'application'
17
19
  @status, @method, @env, @headers, @root = 200, method.downcase, env,
18
20
  {'Content-Type'=>'text/html'}, env['SCRIPT_NAME'].sub(/\/$/,'')
19
21
  @k = query_parse(env['HTTP_COOKIE'], ';,')
@@ -54,7 +56,18 @@ module Merb
54
56
  elsif @method == "post"
55
57
  qs.merge!(query_parse(@in.read))
56
58
  end
57
- @cookies, @params = @k.dup, qs.dup
59
+ @cookies, @params = @k.dup, qs.dup.merge(args)
60
+ end
61
+
62
+ def redirect(url)
63
+ @status = 302
64
+ @headers.merge!({'Location'=> url})
65
+ return ''
66
+ end
67
+
68
+ def send_file(file)
69
+ headers['X-SENDFILE'] = file
70
+ return
58
71
  end
59
72
 
60
73
  def params
@@ -85,20 +98,46 @@ module Merb
85
98
  def unescape(s)
86
99
  Mongrel::HttpRequest.unescape(s)
87
100
  end
88
-
89
- def self.template_dir(loc)
90
- @@template_dir = File.expand_path(Merb::Server.config[:merb_root] + "/app/views/#{loc}")
101
+
102
+
103
+ def template_dir(loc)
104
+ File.expand_path(Merb::Server.config[:merb_root] + "/dist/app/views/#{loc}")
91
105
  end
92
106
 
93
107
  def current_method_name(depth=0)
94
- caller[depth] =~ /`(.*)'$/
95
- $1
108
+ caller[depth] =~ /`(.*)'$/; $1
109
+ end
110
+
111
+
112
+ def layout(l)
113
+ @layout = l
96
114
  end
97
115
 
98
- def render(template=current_method_name(1),b=binding)
99
- template = ERB.new( IO.read( @@template_dir + "/#{template}.rhtml" ) )
100
- template.result( b )
116
+ def render_no_layout(template=current_method_name(1), b=binding)
117
+ template = Erubis::Eruby.new( IO.read( template_dir(self.class.name.snake_case) + "/#{template}.rhtml" ) )
118
+ template.result(b)
101
119
  end
120
+
121
+ def render(template=current_method_name(1), b=binding)
122
+ name = self.class.name.snake_case
123
+ template = Erubis::Eruby.new( IO.read( template_dir(name) + "/#{template}.rhtml" ) )
124
+ layout_content = template.result(b)
125
+ return layout_content if (@layout.to_s == 'none')
126
+ if ['application', name].include?(@layout.to_s)
127
+ if File.exist?(template_dir("layout/#{name}.rhtml"))
128
+ layout = name
129
+ else
130
+ layout = 'application'
131
+ end
132
+ else
133
+ layout = @layout.to_s
134
+ end
135
+ @layout_content = layout_content
136
+ layout_tmpl = Erubis::Eruby.new( IO.read( template_dir('layout') + "/#{layout}.rhtml" ) )
137
+ layout_tmpl.result(b)
138
+ end
139
+
140
+
102
141
  end
103
142
 
104
143
  end
@@ -1,5 +1,4 @@
1
1
  require 'rubygems'
2
- require 'daemons'
3
2
  require 'merb'
4
3
  require 'optparse'
5
4
  require 'ostruct'
@@ -7,11 +6,6 @@ require 'ostruct'
7
6
  module Merb
8
7
  end
9
8
 
10
- module Daemons
11
- class Controller
12
- attr_accessor :app_part
13
- end
14
- end
15
9
 
16
10
  class Merb::Server
17
11
 
@@ -36,7 +30,11 @@ class Merb::Server
36
30
  end
37
31
 
38
32
  opts.on("-m", "--merb-root [String]") do |merb_root|
39
- options[:merb_root] = merb_root
33
+ options[:merb_root] = File.expand_path(merb_root)
34
+ end
35
+
36
+ opts.on("-i", "--irb-console [String]") do |console|
37
+ options[:console] = true
40
38
  end
41
39
  end
42
40
 
@@ -44,6 +42,8 @@ class Merb::Server
44
42
 
45
43
 
46
44
  @@merb_opts = options
45
+ puts %{Merb started with these options:}
46
+ puts @@merb_opts.to_yaml; puts
47
47
  end
48
48
 
49
49
  def self.run
@@ -53,19 +53,33 @@ class Merb::Server
53
53
  :dir_mode => :normal
54
54
  }
55
55
 
56
- @@merb_raw_opts = Daemons::Controller.new(options, ARGV).app_part
56
+ @@merb_raw_opts = ARGV
57
57
  merb_config
58
-
59
- require @@merb_opts[:merb_root]+'/conf/router.rb'
60
- require @@merb_opts[:merb_root]+'/conf/merb_init.rb'
61
- $LOAD_PATH.unshift( File.join(@@merb_opts[:merb_root] , 'app/controllers') )
62
- $LOAD_PATH.unshift( File.join(@@merb_opts[:merb_root] , 'lib') )
63
-
64
-
65
- h = Mongrel::HttpServer.new((@@merb_opts[:host]||"0.0.0.0"), (@@merb_opts[:port]||4000), (@@merb_opts[:numprocs]||40))
66
- h.register("/", MerbHandler.new(@@merb_opts[:merb_root]+'/public'))
67
- h.register("/favicon.ico", Mongrel::Error404Handler.new(""))
68
- h.run.join
58
+
59
+ @@merb_opts[:dist_root] = @@merb_opts[:merb_root]+'/dist'
60
+
61
+ $LOAD_PATH.unshift( File.join(@@merb_opts[:merb_root] , '/dist/app/controllers') )
62
+ $LOAD_PATH.unshift( File.join(@@merb_opts[:merb_root] , '/dist/lib') )
63
+ require @@merb_opts[:merb_root]+'/dist/conf/router.rb'
64
+ require @@merb_opts[:merb_root]+'/dist/conf/merb_init.rb'
65
+
66
+ if @@merb_opts[:console]
67
+ ARGV.clear # Avoid passing args to IRB
68
+ require 'irb'
69
+ require 'irb/completion'
70
+ def exit
71
+ exit!
72
+ end
73
+ if File.exists? ".irbrc"
74
+ ENV['IRBRC'] = ".irbrc"
75
+ end
76
+ IRB.start
77
+ end
78
+
79
+ h = Mongrel::HttpServer.new((@@merb_opts[:host]||"0.0.0.0"), (@@merb_opts[:port]||4000))#, (@@merb_opts[:numprocs]||40))
80
+ h.register("/", MerbHandler.new(@@merb_opts[:dist_root]+'/public'))
81
+ h.register("/favicon.ico", Mongrel::Error404Handler.new(""))
82
+ h.run.join
69
83
 
70
84
  end
71
85
 
@@ -73,4 +87,5 @@ class Merb::Server
73
87
  @@merb_opts
74
88
  end
75
89
 
76
- end
90
+ end
91
+
@@ -1,3 +1,4 @@
1
+ require 'sync'
1
2
  class MerbHandler < Mongrel::HttpHandler
2
3
  @@file_only_methods = ["GET","HEAD"]
3
4
 
@@ -6,6 +7,7 @@ class MerbHandler < Mongrel::HttpHandler
6
7
  # by default. See merb_daemon.rb if you want to change this.
7
8
  def initialize(dir, opts = {})
8
9
  @files = Mongrel::DirHandler.new(dir,false)
10
+ @guard = Sync.new
9
11
  end
10
12
 
11
13
  # process incoming http requests and do a number of things
@@ -43,37 +45,37 @@ class MerbHandler < Mongrel::HttpHandler
43
45
  @files.process(request,response)
44
46
  else
45
47
  begin
46
- controller, method, args = handle(request)
47
- output = if (controller && controller.kind_of?(Merb::Controller))
48
- if method
49
- ( args ? controller.send( method, args ) : controller.send(method) )
48
+ # This handles parsing the query string and post/file upload
49
+ # params and is outside of the synchronize call so that
50
+ # multiple file uploads can be done at once.
51
+ controller, action = handle(request)
52
+ p controller, action
53
+ output = nil
54
+ # synchronize here because this is where ActiveRecord or your db
55
+ # calls will be run in your controller methods.
56
+ @guard.synchronize(:EX) {
57
+ output = if (controller && controller.kind_of?(Merb::Controller))
58
+ if action
59
+ controller.send(action)
60
+ else
61
+ controller.to_s
62
+ end
50
63
  else
51
- controller.to_s
52
- end
53
- else
54
- nil
55
- end
64
+ nil
65
+ end
66
+ }
56
67
  rescue Exception => e
57
68
  response.start(500) do |head,out|
58
69
  head["Content-Type"] = "text/html"
59
- out <<"<html><h2>Merb Error!</h2><pre>#{ e.message } - (#{ e.class })" <<
60
- "\n" << "#{(e.backtrace or []).join('\n')}</pre></html>"
70
+ out << exception(e)
61
71
  end
62
72
  return
63
- end
64
-
65
- unless output
66
- response.start(404) do |head,out|
67
- head["Content-Type"] = "text/html"
68
- out << "<html><body>Error: no merb controller found for this url.</body></html>"
69
- end
70
- return
71
- end
73
+ end
72
74
 
73
75
  sendfile, clength = nil
74
76
  response.status = controller.status
75
77
 
76
- # check for the X-SENDFILE heaqder from your Merb::Controller
78
+ # check for the X-SENDFILE header from your Merb::Controller
77
79
  # and serve the file directly instead of buffering.
78
80
  controller.headers.each do |k, v|
79
81
  if k =~ /^X-SENDFILE$/i
@@ -99,39 +101,48 @@ class MerbHandler < Mongrel::HttpHandler
99
101
  response.write(output)
100
102
  end
101
103
  end
102
- end
103
-
104
+ end
105
+
104
106
  # This is where we grab the incoming request PATH_INFO
105
107
  # and use that in the merb routematcher to determine
106
- # which controller and method to run.
108
+ # which controller and method to run.
109
+ # returns a 2 element tuple of:
110
+ # [controller_object, method]
107
111
  def handle(request)
108
- path = request.params["PATH_INFO"].sub(/\/+/, '/')
112
+ path = request.params[Mongrel::Const::PATH_INFO].sub(/\/+/, '/')
109
113
  path = path[0..-2] if (path[-1] == ?/)
110
- routes = Merb::RouteMatcher.new
111
- route = routes.route_request(path)
114
+ route = Merb::RouteMatcher.new.route_request(path)
115
+ puts route.inspect if $DEBUG
112
116
  if route
113
- [ instantiate_controller(route.delete(:class), request.body, request.params),
114
- route.delete(:method), route ]
117
+ [ instantiate_controller(route[:controller], request.body, request.params,
118
+ route.dup.delete_if{|k,v| [:controller, :action].include? k}),
119
+ route[:action]]
115
120
  else
116
- ["<html><body>Error: no route matches!</body></html>", nil, nil]
121
+ ["<html><body>Error: no route matches!</body></html>", nil]
117
122
  end
118
123
  end
119
124
 
120
125
  # take a controller class name string and reload or require
121
126
  # the right controller file then upcase the first letter and
122
- # trun it into a new object p[assing in the request and response.
127
+ # trun it into a new object passing in the request and response.
123
128
  # this is where your Merb::Controller is instantiated.
124
- def instantiate_controller(controller_name, req, env)
125
- if !File.exist?(Merb::Server.config[:merb_root]+"/app/controllers/#{controller_name}.rb")
126
- return Object.const_get(:Noroutefound).new(req, env)
127
- end
128
- controller_name.import
129
+ def instantiate_controller(controller_name, req, env, params)
130
+ if !File.exist?(Merb::Server.config[:dist_root]+"/app/controllers/#{controller_name.snake_case}.rb")
131
+ return Object.const_get(:Noroutefound).new(req, env, params)
132
+ end
129
133
  begin
130
- return Object.const_get( controller_name.controller_class_name ).new(req, env)
134
+ controller_name.import
135
+ return Object.const_get( controller_name.camel_case ).new(req, env, params)
131
136
  rescue Exception
132
- warn "Error getting instance of '#{controller_name.controller_class_name}': #{$!}"
137
+ warn "Error getting instance of '#{controller_name.camel_case}': #{$!}"
133
138
  raise $!
134
139
  end
135
140
  end
136
141
 
142
+ # format exception message for browser display
143
+ def exception(e)
144
+ "<html><h2>Merb Error!</h2><p>#{ e.message } - (#{ e.class })\n" <<
145
+ "#{(e.backtrace or []).join('<br />')}</p></html>"
146
+ end
147
+
137
148
  end
@@ -1,34 +1,19 @@
1
1
  module Merb
2
2
 
3
3
  # Merb::RouteMatcher is the request routing mapper for the merb framework.
4
- # You can define placeholder parts of the url with the :smbol notation.
4
+ # You can define placeholder parts of the url with the :symbol notation.
5
5
  # so r.add '/foo/:bar/baz/:id', :class => 'Bar', :method => 'foo'
6
6
  # will match against a request to /foo/123/baz/456. It will then
7
7
  # use the class Bar as your merb controller and call the foo method on it.
8
8
  # the foo method will recieve a hash with {:bar => '123', :id => '456'}
9
9
  # as the content. So the :placeholders sections of your routes become
10
- # a hash of arguments to your controller methods. These two methods map
11
- # the default /controller/action and /controller/action/id urls you will
12
- # use so you will want these two by default, in this order:
13
- # r.add '/:class/:method/:id', {}
14
- # r.add '/:class/:method', {}
15
- #
16
- # This is what a route prepare definition looks like. I have added
17
- # code that will spit out the compiled routing lambda so you can see
18
- # what is generated:
19
- #
20
- # puts "Compiling routes: \n"
21
- # Merb::RouteMatcher.prepare do |r|
22
- # r.add '/foo/:bar/baz/:id', :class => 'Test', :method => 'foo'
23
- # r.add '/:class/:method/:id', {}
24
- # r.add '/:class/:method', {}
25
- # end
26
- # m = Merb::RouteMatcher.new
27
- # puts m.compiled_statement
10
+ # a hash of arguments to your controller methods. This route maps
11
+ # the default /controller/action and /controller/action/id urls:
12
+ # r.add '/:class/:method/:id'
28
13
  class RouteMatcher
29
14
 
30
15
  attr_accessor :sections
31
- SECTION_REGEX = /(?::([a-z]+))/ unless defined?SECTION_REGEX
16
+ @@section_regexp = /(?::([a-z*]+))/.freeze
32
17
 
33
18
  def self.prepare
34
19
  @@routes = Array.new
@@ -50,30 +35,42 @@ module Merb
50
35
  end
51
36
 
52
37
  def self.add(*route)
53
- @@routes << route
38
+ @@routes << [route[0], (route[1] || {})]
54
39
  end
55
40
 
56
41
  def self.compile_router
57
- router_lambda = @@routes.inject('lambda{|path| ') { |m,r|
42
+ router_lambda = @@routes.inject("lambda{|path| \n case path\n") { |m,r|
58
43
  m << compile(r)
59
- } << "\n return {:class=>'NoRouteFound', :method=>'noroute'}\n}"
44
+ } <<" else\n return {:controller=>'Noroutefound', :action=>'noroute'}\n end\n}"
60
45
  @@compiled_statement = router_lambda
61
46
  define_method(:route_request, &eval(router_lambda))
62
47
  end
63
48
 
64
49
  def self.compile(route)
65
50
  raise ArgumentError unless String === route[0]
51
+ if route[0] == '/:controller/:action/:id'
52
+ return ' when /\A\/([^\/;.,?]+)(?:\/?\Z|\/([^\/;.,?]+)\/?)(?:\/?\Z|\/([^\/;.,?]+)\/?)\Z/
53
+ @sections[:controller] = $1
54
+ @sections[:action] = $2 || \'index\'
55
+ @sections[:id] = $3 || nil
56
+ return @sections'<<"\n"
57
+ end
66
58
  code, count = '', 0
67
- while route[0] =~ SECTION_REGEX
59
+ while route[0] =~ @@section_regexp
68
60
  route[0] = route[0].dup
69
61
  name = $1
62
+ (name =~ /(\*+)(\w+)/) ? (flag = true; name = $2) : (flag = false)
70
63
  count += 1
71
- route[0].sub!(SECTION_REGEX, '(.+)')
64
+ if flag
65
+ route[0].sub!(@@section_regexp, "([^;.,?]+)")
66
+ else
67
+ route[0].sub!(@@section_regexp, "([^\/;.,?]+)")
68
+ end
72
69
  code << " @sections[:#{name}] = $#{count}\n"
73
70
  end
74
- condition = " if Regexp.new('#{route[0]}') =~ path"
75
- statement = "\n#{condition}\n#{code}"
76
- statement << " return #{route[1].inspect}.merge(@sections)\n end\n"
71
+ condition = " when Regexp.new('#{route[0]}')"
72
+ statement = "#{condition}\n#{code}"
73
+ statement << " return #{route[1].inspect}.merge(@sections)\n"
77
74
  statement
78
75
  end
79
76
 
@@ -1,21 +1,110 @@
1
1
  class String
2
2
  def import
3
- Merb::Server.config[:allow_reloading] ? load( self + '.rb' ) : require( self )
3
+ Merb::Server.config[:allow_reloading] ? load( self.snake_case + '.rb' ) : require( self.snake_case )
4
4
  end
5
-
6
- def controller_class_name
7
- self.capitalize
5
+
6
+ def snake_case
7
+ return self unless self =~ %r/[A-Z]/
8
+ self.reverse.scan(%r/[A-Z]+|[^A-Z]*[A-Z]+?/).reverse.map{|word| word.reverse.downcase}.join '_'
8
9
  end
9
- end
10
10
 
11
- class MerbHash < HashWithIndifferentAccess
12
- def method_missing(m,*a)
13
- m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m]:raise(NoMethodError,"#{m}")
11
+
12
+ def camel_case
13
+ return self if self =~ %r/[A-Z]/ and self !~ %r/_/
14
+ words = self.strip.split %r/\s*_+\s*/
15
+ words.map!{|w| w.downcase.sub(%r/^./){|c| c.upcase}}
16
+ words.join
14
17
  end
18
+
15
19
  end
16
20
 
17
21
  class Symbol
18
22
  def to_s
19
23
  @str_rep || (@str_rep = id2name.freeze)
20
24
  end
21
- end
25
+ def to_proc
26
+ Proc.new{|*args| args.shift.__send__(self, *args)}
27
+ end
28
+ end
29
+
30
+ class Object
31
+ def returning(value)
32
+ yield(value)
33
+ value
34
+ end
35
+ end
36
+
37
+ class Hash
38
+ def with_indifferent_access
39
+ MerbHash.new(self)
40
+ end
41
+ end
42
+
43
+ class MerbHash < Hash
44
+ def initialize(constructor = {})
45
+ if constructor.is_a?(Hash)
46
+ super()
47
+ update(constructor)
48
+ else
49
+ super(constructor)
50
+ end
51
+ end
52
+
53
+ def default(key)
54
+ self[key.to_s] if key.is_a?(Symbol)
55
+ end
56
+
57
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
58
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
59
+
60
+ def []=(key, value)
61
+ regular_writer(convert_key(key), convert_value(value))
62
+ end
63
+
64
+ def update(other_hash)
65
+ other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
66
+ self
67
+ end
68
+
69
+ alias_method :merge!, :update
70
+
71
+ def key?(key)
72
+ super(convert_key(key))
73
+ end
74
+
75
+ alias_method :include?, :key?
76
+ alias_method :has_key?, :key?
77
+ alias_method :member?, :key?
78
+
79
+ def fetch(key, *extras)
80
+ super(convert_key(key), *extras)
81
+ end
82
+
83
+ def values_at(*indices)
84
+ indices.collect {|key| self[convert_key(key)]}
85
+ end
86
+
87
+ def dup
88
+ MerbHash.new(self)
89
+ end
90
+
91
+ def merge(hash)
92
+ self.dup.update(hash)
93
+ end
94
+
95
+ def delete(key)
96
+ super(convert_key(key))
97
+ end
98
+
99
+ def method_missing(m,*a)
100
+ m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m]:raise(NoMethodError,"#{m}")
101
+ end
102
+
103
+ protected
104
+ def convert_key(key)
105
+ key.kind_of?(Symbol) ? key.to_s : key
106
+ end
107
+ def convert_value(value)
108
+ value.is_a?(Hash) ? value.with_indifferent_access : value
109
+ end
110
+ end
@@ -1,6 +1,6 @@
1
1
  class Noroutefound < Merb::Controller
2
2
 
3
- def method_missing(*args)
3
+ def method_missing(sym, *args, &blk)
4
4
  "<html><body><h1>No Matching Route</h1></body></html>"
5
5
  end
6
6
 
data/lib/merb.rb CHANGED
@@ -1,13 +1,13 @@
1
1
  require 'rubygems'
2
2
  require 'mongrel'
3
3
  require 'fileutils'
4
- require 'erb'
4
+ require 'erubis'
5
5
  require 'merb_config'
6
- require 'active_support'
6
+
7
7
 
8
8
  module Merb
9
- VERSION='0.0.3' unless defined?VERSION
9
+ VERSION='0.0.4' unless defined?VERSION
10
10
  end
11
11
 
12
12
  lib = File.join(File.dirname(__FILE__), 'merb')
13
- Dir.foreach(lib) {|fn| require File.join(lib, fn) if fn =~ /\.rb$/}
13
+ Dir.foreach(lib) {|fn| require File.join(lib, fn) if fn =~ /\.rb$/}
data/lib/merb_config.rb CHANGED
@@ -11,7 +11,7 @@ class Merb::Config
11
11
  }
12
12
 
13
13
  begin
14
- options = defaults.merge(YAML.load(ERB.new(IO.read("#{defaults[:merb_root]}/conf/merb.yml")).result))
14
+ options = defaults.merge(YAML.load(ERB.new(IO.read("#{defaults[:merb_root]}/dist/conf/merb.yml")).result))
15
15
  rescue
16
16
  options = defaults
17
17
  end
@@ -10,13 +10,9 @@ class RouteMatcherTest < Test::Unit::TestCase
10
10
 
11
11
  def setup
12
12
  Merb::RouteMatcher.prepare do |r|
13
- r.add '/foo/:bar/baz/:id', :class => 'Test', :method => 'foo'
14
- r.add '/hey/:there/you/:guys', :class => 'Test', :method => 'hello'
15
- r.add '/these/:routes/are/:sweet', :class => 'Upload', :method => 'start'
16
- r.add '/h/:a/b/:c/d/:f/g/:id', :class => 'Test'
17
- r.add '/:class/:method/:id', {}
18
- r.add '/:class/:method', {}
19
- r.add '.*', {:class => 'NoRouteFound', :method => 'noroute'}
13
+ r.add '/foo/:bar/baz/:id', :controller => 'Test', :action => 'foo'
14
+ r.add '/:controller/:action/:id'
15
+ r.add '/bar/:*rest', :controller => 'Test', :action => 'glob'
20
16
  end
21
17
  end
22
18
 
@@ -30,35 +26,20 @@ class RouteMatcherTest < Test::Unit::TestCase
30
26
 
31
27
  def test_route_matching
32
28
  routes = Merb::RouteMatcher.new
33
- assert_same_hash( {:class=>"Test", :id =>"456", :bar =>"123", :method=>"foo"}, routes.route_request("/foo/123/baz/456"))
29
+ assert_same_hash( {:controller=>"Test", :id =>"456", :bar =>"123", :action=>"foo"}, routes.route_request("/foo/123/baz/456"))
34
30
  routes = Merb::RouteMatcher.new
35
- assert_same_hash( {:class=>"Test", :guys =>"girls", :there =>"jon", :method=>"hello"}, routes.route_request( "/hey/jon/you/girls"))
31
+ assert_same_hash({:controller=>"upload", :id=>"12", :action=>"test"},routes.route_request( "/upload/test/12"))
36
32
  routes = Merb::RouteMatcher.new
37
- assert_same_hash({:class=>"upload", :id=>"12", :method=>"test"},routes.route_request( "/upload/test/12"))
33
+ assert_same_hash({:controller=>"upload", :id=>nil, :action=>"index"},routes.route_request( "/upload"))
38
34
  routes = Merb::RouteMatcher.new
39
- assert_same_hash({:class=>"nik", :method=>"nak"},routes.route_request( "/nik/nak"))
35
+ assert_same_hash({:controller=>"nik", :action=>"nak", :id => nil},routes.route_request( "/nik/nak"))
40
36
  routes = Merb::RouteMatcher.new
41
- assert_same_hash({:class=>"lots", :method=>"of", :id => "12"},routes.route_request( "/lots/of/12"))
42
- routes = Merb::RouteMatcher.new
43
- assert_same_hash({:class => 'NoRouteFound', :method => 'noroute'},routes.route_request( "/"))
44
- routes = Merb::RouteMatcher.new
45
- assert_same_hash({:class => 'NoRouteFound', :method => 'noroute'},routes.route_request( "/dfnasfnafn"))
46
- routes = Merb::RouteMatcher.new
47
- assert_same_hash( {:class=>"Upload", :routes=>"234", :sweet=>"yup", :method=>"start"}, routes.route_request( "/these/234/are/yup"))
48
- routes = Merb::RouteMatcher.new
49
- assert_same_hash( {:class=>"Test", :a=>"12", :c=>"red", :id=>"12", :f=>"blue"}, routes.route_request( '/h/12/b/red/d/blue/g/12'))
37
+ assert_same_hash({:controller=>"lots", :action=>"of", :id => "12"},routes.route_request( "/lots/of/12"))
50
38
  end
51
39
 
52
- def test_route_doesnt_match
53
- routes = Merb::RouteMatcher.new
54
- assert_same_hash({:class=>"NoRouteFound", :method=>"noroute"},routes.route_request( '/hdsfvsdfsdfdsf'))
55
- end
56
-
57
- def test_regexes_included_in_compiled_statement
40
+ def test_catch_all_glob_route
58
41
  routes = Merb::RouteMatcher.new
59
- routes.routes.each do |r|
60
- assert Regexp.new(r[0]) =~ routes.compiled_statement
61
- end
62
- end
63
-
42
+ assert_same_hash({:controller=>"Test", :action=>"glob", :rest => "hey/you/guys"},routes.route_request( "/bar/hey/you/guys"))
43
+ end
44
+
64
45
  end