merb 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README +135 -0
- data/Rakefile +126 -0
- data/bin/merb +7 -0
- data/examples/sample_app/app/controllers/noroutefound.rb +7 -0
- data/examples/sample_app/app/controllers/test.rb +21 -0
- data/examples/sample_app/app/controllers/upload.rb +21 -0
- data/examples/sample_app/app/views/test/foo.rhtml +6 -0
- data/examples/sample_app/app/views/test/hello.rhtml +12 -0
- data/examples/sample_app/app/views/upload/start.rhtml +23 -0
- data/examples/sample_app/app/views/upload/upload.rhtml +4 -0
- data/examples/sample_app/conf/merb_init.rb +3 -0
- data/examples/sample_app/conf/router.rb +7 -0
- data/lib/merb.rb +13 -0
- data/lib/merb/merb_controller.rb +87 -0
- data/lib/merb/merb_daemon.rb +76 -0
- data/lib/merb/merb_handler.rb +58 -0
- data/lib/merb/merb_router.rb +87 -0
- data/lib/merb/merb_utils.rb +21 -0
- data/lib/merb_config.rb +21 -0
- data/server.rb +13 -0
- data/test/test_helper.rb +1 -0
- data/test/unit/route_matcher_test.rb +59 -0
- metadata +100 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2006 Ezra Zygmuntowicz
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
<html><body>
|
2
|
+
<pre>
|
3
|
+
Copyright (c) 2006 Ezra Zygmuntowicz
|
4
|
+
|
5
|
+
Merb. Mongrel+Erb
|
6
|
+
|
7
|
+
Little bitty lightweight ruby app server. FOr when you really need performance
|
8
|
+
for simple dynamic pages.
|
9
|
+
|
10
|
+
**Sample APP included**
|
11
|
+
|
12
|
+
Unpack the gem and grab the sample app
|
13
|
+
|
14
|
+
$ gem unpack merb
|
15
|
+
|
16
|
+
cd merb*
|
17
|
+
cd examples/smaple_app
|
18
|
+
|
19
|
+
$ merb start -t
|
20
|
+
|
21
|
+
then the sample app will be running on port 4000 in the foreground.
|
22
|
+
you can go to a few urls:
|
23
|
+
|
24
|
+
http://localhost:4000/upload/start
|
25
|
+
|
26
|
+
http://localhost:4000/foo/123/baz/12234345
|
27
|
+
|
28
|
+
|
29
|
+
**FEATURES** 1. Mongrel handler built in that parses incoming requests
|
30
|
+
including multipart uploads and post as well as ?query=strings. Puts the
|
31
|
+
params into @params and the cookies into @cookies when it instantiates your
|
32
|
+
controller class.
|
33
|
+
|
34
|
+
2. New RouteMatcher and route compiler. Reads your route definition and compiles
|
35
|
+
a method on the fly that will match the request path against each route and do the
|
36
|
+
right thing. So the following routes:
|
37
|
+
|
38
|
+
Merb::RouteMatcher.prepare do |r|
|
39
|
+
r.add '/foo/:bar/baz/:id', :class => 'Test', :method => 'foo'
|
40
|
+
r.add '/hey/:there/you/:guys', :class => 'Test', :method => 'hello'
|
41
|
+
r.add '/:class/:method/:id', {}
|
42
|
+
r.add '/:class/:method', {}
|
43
|
+
r.add '.*', {:class => 'NoRouteFound', :method => 'noroute'}
|
44
|
+
end
|
45
|
+
|
46
|
+
Will be compiled and defined as a method with this lambda as the body:
|
47
|
+
|
48
|
+
lambda{|path|
|
49
|
+
if Regexp.new('/foo/(.+)/baz/(.+)') =~ path
|
50
|
+
@sections[:bar] = $1
|
51
|
+
@sections[:id] = $2
|
52
|
+
return {:class=>"Test", :method=>"foo"}.merge(@sections)
|
53
|
+
end
|
54
|
+
|
55
|
+
if Regexp.new('/hey/(.+)/you/(.+)') =~ path
|
56
|
+
@sections[:there] = $1
|
57
|
+
@sections[:guys] = $2
|
58
|
+
return {:class=>"Test", :method=>"hello"}.merge(@sections)
|
59
|
+
end
|
60
|
+
|
61
|
+
if Regexp.new('/(.+)/(.+)/(.+)') =~ path
|
62
|
+
@sections[:class] = $1
|
63
|
+
@sections[:method] = $2
|
64
|
+
@sections[:id] = $3
|
65
|
+
return {}.merge(@sections)
|
66
|
+
end
|
67
|
+
|
68
|
+
if Regexp.new('/(.+)/(.+)') =~ path
|
69
|
+
@sections[:class] = $1
|
70
|
+
@sections[:method] = $2
|
71
|
+
return {}.merge(@sections)
|
72
|
+
end
|
73
|
+
|
74
|
+
if Regexp.new('.*') =~ path
|
75
|
+
return {:class=>"NoRouteFound", :method=>"noroute"}.merge(@sections)
|
76
|
+
end
|
77
|
+
|
78
|
+
return default_route
|
79
|
+
}
|
80
|
+
|
81
|
+
3. Simple controller classes with built in render method and template handling
|
82
|
+
with instance vars available in the views automatically.
|
83
|
+
|
84
|
+
class Test < Merb::Controller
|
85
|
+
template_dir :test
|
86
|
+
|
87
|
+
def hello(args)
|
88
|
+
# args is a hash of the remaining templated
|
89
|
+
# parameters from the url matched in routing.
|
90
|
+
# @params and @cookies are avaialable here. string
|
91
|
+
# keys and not symbols just yet.
|
92
|
+
# Assign the parameter to an instance variable
|
93
|
+
@name = args[:name]
|
94
|
+
render
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
<html>
|
99
|
+
<head>
|
100
|
+
<title>Hello, <%= @name %></title>
|
101
|
+
</head>
|
102
|
+
<body>
|
103
|
+
<h1><%= @params.inspect %></h1>
|
104
|
+
<h1><%= @cookies.inspect %></h1>
|
105
|
+
<% 5.times do %>
|
106
|
+
<h1>Hello, <%= @name %>!</h1>
|
107
|
+
<% end %>
|
108
|
+
</body>
|
109
|
+
</html>
|
110
|
+
|
111
|
+
4. the server. right now you add your routes in
|
112
|
+
the appdir/conf/router.rb file. So by default it runs on port 4000
|
113
|
+
|
114
|
+
$ cd /path/to/your/merb/app
|
115
|
+
$ merb start
|
116
|
+
|
117
|
+
5. File uploads. This is the thing that Merb was written for. Rails doesn't allow
|
118
|
+
multiple concurrent file uploads at once without blocking an entire rails backend
|
119
|
+
for each file upload. Merb allows multiple file uploads at once. Start the server
|
120
|
+
and browse to http://localhost:4000/upload/start and you will get a file upload
|
121
|
+
form. There is a very simple upload controller example that handles this upload.
|
122
|
+
When a file is uploaded with Merb, it gets put in a Tempfile. So you just want to
|
123
|
+
copy it to the right place on the filesystem.
|
124
|
+
|
125
|
+
def upload(args)
|
126
|
+
puts @params[:file].inspect
|
127
|
+
|
128
|
+
FileUtils.mv @params[:file][:tempfile].path, MERB_ROOT+"/uploads/#{@params[:file][:filename]}"
|
129
|
+
|
130
|
+
render
|
131
|
+
end
|
132
|
+
</pre>
|
133
|
+
</body>
|
134
|
+
</html>
|
135
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/clean'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require 'rake/testtask'
|
6
|
+
require 'fileutils'
|
7
|
+
include FileUtils
|
8
|
+
|
9
|
+
NAME = "merb"
|
10
|
+
VERS = "0.0.1"
|
11
|
+
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
|
12
|
+
RDOC_OPTS = ['--quiet', '--title', "Merb Documentation",
|
13
|
+
"--opname", "index.html",
|
14
|
+
"--line-numbers",
|
15
|
+
"--main", "README",
|
16
|
+
"--inline-source"]
|
17
|
+
|
18
|
+
desc "Packages up Merb."
|
19
|
+
task :default => [:package]
|
20
|
+
task :package => [:clean]
|
21
|
+
|
22
|
+
task :doc => [:rdoc]
|
23
|
+
|
24
|
+
Rake::RDocTask.new do |rdoc|
|
25
|
+
rdoc.rdoc_dir = 'doc/rdoc'
|
26
|
+
rdoc.options += RDOC_OPTS
|
27
|
+
rdoc.main = "README"
|
28
|
+
rdoc.title = "Merb Documentation"
|
29
|
+
rdoc.rdoc_files.add ['README', 'LICENSE', 'lib/*.rb', 'server.rb']
|
30
|
+
end
|
31
|
+
|
32
|
+
spec = Gem::Specification.new do |s|
|
33
|
+
s.name = NAME
|
34
|
+
s.version = VERS
|
35
|
+
s.platform = Gem::Platform::RUBY
|
36
|
+
s.has_rdoc = true
|
37
|
+
s.extra_rdoc_files = ["README", "LICENSE"]
|
38
|
+
s.rdoc_options += RDOC_OPTS +
|
39
|
+
['--exclude', '^(app|uploads)']
|
40
|
+
s.summary = "Merb == Mongrel + Erb. Pocket rocket web framework."
|
41
|
+
s.description = s.summary
|
42
|
+
s.author = "Ezra Zygmuntowicz"
|
43
|
+
s.email = 'ez@engineyard.com'
|
44
|
+
s.homepage = 'http://merb.devjavu.com'
|
45
|
+
s.executables = ['merb']
|
46
|
+
|
47
|
+
s.add_dependency('mongrel')
|
48
|
+
s.required_ruby_version = '>= 1.8.4'
|
49
|
+
|
50
|
+
s.files = %w(LICENSE README Rakefile server.rb) + Dir.glob("{app,bin,doc,test,lib,examples}/**/*")
|
51
|
+
|
52
|
+
s.require_path = "lib"
|
53
|
+
s.bindir = "bin"
|
54
|
+
end
|
55
|
+
|
56
|
+
Rake::GemPackageTask.new(spec) do |p|
|
57
|
+
p.need_tar = true
|
58
|
+
p.gem_spec = spec
|
59
|
+
end
|
60
|
+
|
61
|
+
task :install do
|
62
|
+
sh %{rake package}
|
63
|
+
sh %{sudo gem install pkg/#{NAME}-#{VERS}}
|
64
|
+
end
|
65
|
+
|
66
|
+
task :uninstall => [:clean] do
|
67
|
+
sh %{sudo gem uninstall #{NAME}}
|
68
|
+
end
|
69
|
+
|
70
|
+
task :doc_rforge do
|
71
|
+
sh %{rake doc}
|
72
|
+
sh %{scp -r doc/rdoc/* ezmobius@rubyforge.org:/var/www/gforge-projects/merb}
|
73
|
+
end
|
74
|
+
|
75
|
+
desc 'Run unit tests'
|
76
|
+
Rake::TestTask.new('test_unit') do |t|
|
77
|
+
t.libs << 'test'
|
78
|
+
t.pattern = 'test/unit/*_test.rb'
|
79
|
+
t.verbose = true
|
80
|
+
end
|
81
|
+
|
82
|
+
desc 'Run functional tests'
|
83
|
+
Rake::TestTask.new('test_functional') do |t|
|
84
|
+
t.libs << 'test'
|
85
|
+
t.pattern = 'test/functional/*_test.rb'
|
86
|
+
t.verbose = true
|
87
|
+
end
|
88
|
+
|
89
|
+
desc 'Run all tests'
|
90
|
+
Rake::TestTask.new('test') do |t|
|
91
|
+
t.libs << 'test'
|
92
|
+
t.pattern = 'test/**/*_test.rb'
|
93
|
+
t.verbose = true
|
94
|
+
end
|
95
|
+
|
96
|
+
desc 'Run all tests, specs and finish with rcov'
|
97
|
+
task :aok do
|
98
|
+
sh %{rake rcov}
|
99
|
+
sh %{rake spec}
|
100
|
+
end
|
101
|
+
|
102
|
+
##############################################################################
|
103
|
+
# Statistics
|
104
|
+
##############################################################################
|
105
|
+
|
106
|
+
STATS_DIRECTORIES = [
|
107
|
+
%w(Code lib/),
|
108
|
+
%w(Unit\ tests test/unit),
|
109
|
+
%w(Functional\ tests test/functional)
|
110
|
+
].collect { |name, dir| [ name, "./#{dir}" ] }.select { |name, dir| File.directory?(dir) }
|
111
|
+
|
112
|
+
desc "Report code statistics (KLOCs, etc) from the application"
|
113
|
+
task :stats do
|
114
|
+
require 'extra/stats'
|
115
|
+
verbose = true
|
116
|
+
CodeStatistics.new(*STATS_DIRECTORIES).to_s
|
117
|
+
end
|
118
|
+
|
119
|
+
##############################################################################
|
120
|
+
# SVN
|
121
|
+
##############################################################################
|
122
|
+
|
123
|
+
desc "Add new files to subversion"
|
124
|
+
task :svn_add do
|
125
|
+
system "svn status | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\ /g' | xargs svn add"
|
126
|
+
end
|
data/bin/merb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
class Test < Merb::Controller
|
3
|
+
template_dir :test
|
4
|
+
|
5
|
+
def hello(args)
|
6
|
+
# Assign the parameter to an instance variable
|
7
|
+
@name = args[:id]
|
8
|
+
render
|
9
|
+
end
|
10
|
+
|
11
|
+
def foo(args)
|
12
|
+
|
13
|
+
@args = args
|
14
|
+
render
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"<html><body>Test controller, no mathing action</body></html>"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Upload < Merb::Controller
|
2
|
+
template_dir :upload
|
3
|
+
|
4
|
+
def start(*args)
|
5
|
+
# Assign the parameter to an instance variable
|
6
|
+
@args = args
|
7
|
+
render
|
8
|
+
end
|
9
|
+
|
10
|
+
def upload(*args)
|
11
|
+
puts @params[:file].inspect
|
12
|
+
|
13
|
+
FileUtils.mv @params[:file][:tempfile].path, ::Merb::Server.config[:merb_root]+"/public/files/#{@params[:file][:filename]}"
|
14
|
+
|
15
|
+
render
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"<html><body>Upload controller, no mathing action</body></html>"
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>Upload#start</title>
|
4
|
+
</head>
|
5
|
+
<body>
|
6
|
+
|
7
|
+
|
8
|
+
<h2>Test file uploading.</h2>
|
9
|
+
|
10
|
+
<form action="/upload/upload" enctype="multipart/form-data" method="post">
|
11
|
+
|
12
|
+
<dl>
|
13
|
+
<dt><label>Select a file</label></dt>
|
14
|
+
<dd><input name="file" type="file" /></dd>
|
15
|
+
</dl>
|
16
|
+
|
17
|
+
|
18
|
+
<p>
|
19
|
+
<input name="commit" type="submit" value="Upload Asset" />
|
20
|
+
</p>
|
21
|
+
</form>
|
22
|
+
</body>
|
23
|
+
</html>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
Merb::RouteMatcher.prepare do |r|
|
2
|
+
r.add '/foo/:bar/baz/:id', :class => 'Test', :method => 'foo'
|
3
|
+
r.add '/hey/:there/you/:guys', :class => 'Test', :method => 'hello'
|
4
|
+
r.add '/:class/:method/:id', {}
|
5
|
+
r.add '/:class/:method', {}
|
6
|
+
r.add '.*', {:class => 'NoRouteFound', :method => 'noroute'}
|
7
|
+
end
|
data/lib/merb.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'mongrel'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'erb'
|
5
|
+
require 'merb_config'
|
6
|
+
require 'active_support'
|
7
|
+
|
8
|
+
module Merb
|
9
|
+
VERSION='0.0.1' unless defined?VERSION
|
10
|
+
end
|
11
|
+
|
12
|
+
lib = File.join(File.dirname(__FILE__), 'merb')
|
13
|
+
Dir.foreach(lib) {|fn| require File.join(lib, fn) if fn =~ /\.rb$/}
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Merb
|
2
|
+
|
3
|
+
class Controller
|
4
|
+
attr_accessor :response_code, :content_type
|
5
|
+
# Stolen from Camping without a twinge of remorse ;)
|
6
|
+
def initialize(req, env, method=(env['REQUEST_METHOD']||"GET")) #:nodoc:
|
7
|
+
@response_code = 200
|
8
|
+
@content_type = 'text/html'
|
9
|
+
env = MerbHash[env.to_hash]
|
10
|
+
puts env.inspect if $DEBUG
|
11
|
+
puts req.inspect if $DEBUG
|
12
|
+
@status, @method, @env, @headers, @root = 200, method.downcase, env,
|
13
|
+
{'Content-Type'=>'text/html'}, env['SCRIPT_NAME'].sub(/\/$/,'')
|
14
|
+
@k = query_parse(env['HTTP_COOKIE'], ';,')
|
15
|
+
qs = query_parse(env['QUERY_STRING'])
|
16
|
+
@in = req
|
17
|
+
if %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)|n.match(env['CONTENT_TYPE'])
|
18
|
+
b = /(?:\r?\n|\A)#{Regexp::quote("--#$1")}(?:--)?\r$/
|
19
|
+
until @in.eof?
|
20
|
+
fh=MerbHash[]
|
21
|
+
for l in @in
|
22
|
+
case l
|
23
|
+
when "\r\n" : break
|
24
|
+
when /^Content-Disposition: form-data;/
|
25
|
+
fh.update MerbHash[*$'.scan(/(?:\s(\w+)="([^"]+)")/).flatten]
|
26
|
+
when /^Content-Type: (.+?)(\r$|\Z)/m
|
27
|
+
puts "=> fh[type] = #$1"
|
28
|
+
fh[:type] = $1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
fn=fh[:name]
|
32
|
+
o=if fh[:filename]
|
33
|
+
o=fh[:tempfile]=Tempfile.new(:Merb)
|
34
|
+
o.binmode
|
35
|
+
else
|
36
|
+
fh=""
|
37
|
+
end
|
38
|
+
while l=@in.read(16384)
|
39
|
+
if l=~b
|
40
|
+
o<<$`.chomp
|
41
|
+
@in.seek(-$'.size,IO::SEEK_CUR)
|
42
|
+
break
|
43
|
+
end
|
44
|
+
o<<l
|
45
|
+
end
|
46
|
+
qs[fn]=fh if fn
|
47
|
+
fh[:tempfile].rewind if fh.is_a?MerbHash
|
48
|
+
end
|
49
|
+
elsif @method == "post"
|
50
|
+
qs.merge!(query_parse(@in.read))
|
51
|
+
end
|
52
|
+
@cookies, @params = @k.dup, qs.dup
|
53
|
+
end
|
54
|
+
|
55
|
+
def query_parse(qs, d = '&;')
|
56
|
+
m = proc {|_,o,n|o.update(n,&m)rescue([*o]<<n)}
|
57
|
+
(qs||'').split(/[#{d}] */n).inject(MerbHash[]) { |h,p|
|
58
|
+
k, v=unescape(p).split('=',2)
|
59
|
+
h.update(k.split(/[\]\[]+/).reverse.
|
60
|
+
inject(v) { |x,i| MerbHash[i,x] },&m)
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def escape(s)
|
65
|
+
Mongrel::HttpRequest.escape(s)
|
66
|
+
end
|
67
|
+
|
68
|
+
def unescape(s)
|
69
|
+
Mongrel::HttpRequest.unescape(s)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.template_dir(loc)
|
73
|
+
@@template_dir = File.expand_path(Merb::Server.config[:merb_root] + "/app/views/#{loc}")
|
74
|
+
end
|
75
|
+
|
76
|
+
def current_method_name(depth=0)
|
77
|
+
caller[depth] =~ /`(.*)'$/
|
78
|
+
$1
|
79
|
+
end
|
80
|
+
|
81
|
+
def render(template=current_method_name(1),b=binding)
|
82
|
+
template = ERB.new( IO.read( @@template_dir + "/#{template}.rhtml" ) )
|
83
|
+
template.result( b )
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'daemons'
|
3
|
+
require 'merb'
|
4
|
+
require 'optparse'
|
5
|
+
require 'ostruct'
|
6
|
+
|
7
|
+
module Merb
|
8
|
+
end
|
9
|
+
|
10
|
+
module Daemons
|
11
|
+
class Controller
|
12
|
+
attr_accessor :app_part
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Merb::Server
|
17
|
+
|
18
|
+
def self.merb_config
|
19
|
+
options = Merb::Config.setup
|
20
|
+
|
21
|
+
opts = OptionParser.new do |opts|
|
22
|
+
opts.on("-c", "--config [String]") do |config|
|
23
|
+
options[:config] = config
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on("-p", "--port [Integer]") do |port|
|
27
|
+
options[:port] = port
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on("-n", "--num-procs [Integer]") do |numprocs|
|
31
|
+
options[:numprocs] = numprocs
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on("-h", "--host [String]") do |host|
|
35
|
+
options[:host] = host
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on("-m", "--merb-root [String]") do |merb_root|
|
39
|
+
options[:merb_root] = merb_root
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.parse!(@@merb_raw_opts)
|
44
|
+
|
45
|
+
|
46
|
+
@@merb_opts = options
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.run
|
50
|
+
|
51
|
+
options = {
|
52
|
+
:dir => Dir.pwd + '/log/',
|
53
|
+
:dir_mode => :normal
|
54
|
+
}
|
55
|
+
|
56
|
+
@@merb_raw_opts = Daemons::Controller.new(options, ARGV).app_part
|
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)
|
67
|
+
h.register("/favicon.ico", Mongrel::Error404Handler.new(""))
|
68
|
+
h.run.join
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.config
|
73
|
+
@@merb_opts
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class MerbHandler < Mongrel::HttpHandler
|
2
|
+
|
3
|
+
def process(request, response)
|
4
|
+
if response.socket.closed?
|
5
|
+
return
|
6
|
+
end
|
7
|
+
controller, method, args = handle(request)
|
8
|
+
begin
|
9
|
+
output = if (controller && controller.kind_of?(Merb::Controller))
|
10
|
+
if method
|
11
|
+
( args ? controller.send( method, args ) : controller.send(method) )
|
12
|
+
else
|
13
|
+
controller.to_s
|
14
|
+
end
|
15
|
+
else
|
16
|
+
response_code = 500
|
17
|
+
"<html><body>Error: no merb controller found for this url.</body></html>"
|
18
|
+
end
|
19
|
+
response_code = controller.response_code
|
20
|
+
content_type = controller.content_type
|
21
|
+
rescue Exception => e
|
22
|
+
response_code = 500
|
23
|
+
output = "<html><h2>Merb Error!</h2><pre>#{ e.message } - (#{ e.class })" <<
|
24
|
+
"\n" << "#{(e.backtrace or []).join('\n')}</pre></html>"
|
25
|
+
end
|
26
|
+
response.start(response_code||200) do |head,out|
|
27
|
+
head["Content-Type"] = content_type || "text/html"
|
28
|
+
out << output
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def handle(request)
|
33
|
+
path = request.params["PATH_INFO"].sub(/\/+/, '/')
|
34
|
+
path = path[0..-2] if (path[-1] == ?/)
|
35
|
+
routes = Merb::RouteMatcher.new
|
36
|
+
route = routes.route_request(path)
|
37
|
+
if route
|
38
|
+
[ instantiate_controller(route.delete(:class), request.body, request.params),
|
39
|
+
route.delete(:method), route ]
|
40
|
+
else
|
41
|
+
["<html><body>Error: no route matches!</body></html>", nil, nil]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def instantiate_controller(controller_name, req, env)
|
46
|
+
if !File.exist?(Merb::Server.config[:merb_root]+"/app/controllers/#{controller_name}.rb")
|
47
|
+
return Object.const_get(:Noroutefound).new(req, env)
|
48
|
+
end
|
49
|
+
controller_name.import
|
50
|
+
begin
|
51
|
+
return Object.const_get( controller_name.controller_class_name ).new(req, env)
|
52
|
+
rescue Exception
|
53
|
+
warn "Error getting instance of '#{controller_name.controller_class_name}': #{$!}"
|
54
|
+
raise $!
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Merb
|
2
|
+
|
3
|
+
class RouteMatcher
|
4
|
+
|
5
|
+
attr_accessor :sections
|
6
|
+
SECTION_REGEX = /(?::([a-z]+))/ unless defined?SECTION_REGEX
|
7
|
+
|
8
|
+
def self.prepare
|
9
|
+
@@routes = Array.new
|
10
|
+
@@compiled_statement = String.new
|
11
|
+
yield self
|
12
|
+
compile_router
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@sections = Hash.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def routes
|
20
|
+
@@routes
|
21
|
+
end
|
22
|
+
|
23
|
+
def compiled_statement
|
24
|
+
@@compiled_statement
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.add(*route)
|
28
|
+
@@routes << route
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.compile_router
|
32
|
+
router_lambda = @@routes.inject('lambda{|path| ') { |m,r|
|
33
|
+
m << compile(r)
|
34
|
+
} << "\n return {:class=>'NoRouteFound', :method=>'noroute'}\n}"
|
35
|
+
@@compiled_statement = router_lambda
|
36
|
+
define_method(:route_request, &eval(router_lambda))
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.compile(route)
|
40
|
+
raise ArgumentError unless String === route[0]
|
41
|
+
code, count = '', 0
|
42
|
+
while route[0] =~ SECTION_REGEX
|
43
|
+
route[0] = route[0].dup
|
44
|
+
name = $1
|
45
|
+
count += 1
|
46
|
+
route[0].sub!(SECTION_REGEX, '(.+)')
|
47
|
+
code << " @sections[:#{name}] = $#{count}\n"
|
48
|
+
end
|
49
|
+
condition = " if Regexp.new('#{route[0]}') =~ path"
|
50
|
+
statement = "\n#{condition}\n#{code}"
|
51
|
+
statement << " return #{route[1].inspect}.merge(@sections)\n end\n"
|
52
|
+
statement
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
=begin
|
60
|
+
if __FILE__ == $0
|
61
|
+
Merb::RouteMatcher.prepare do |r|
|
62
|
+
r.add '/foo/:bar/baz/:id', :class => 'Test', :method => 'foo'
|
63
|
+
r.add '/hey/:there/you/:guys', :class => 'Test', :method => 'hello'
|
64
|
+
r.add '/these/:routes/are/:sweet', :class => 'Upload', :method => 'start'
|
65
|
+
r.add '/h/:a/b/:c/d/:f/g/:id', :class => 'Test'
|
66
|
+
r.add '/:class/:method/:id', {}
|
67
|
+
r.add '/:class/:method', {}
|
68
|
+
end
|
69
|
+
|
70
|
+
routes = Merb::RouteMatcher.new
|
71
|
+
puts routes.compiled_statement
|
72
|
+
p routes.route_request( "/foo/234/baz/dsdsd")
|
73
|
+
routes = Merb::RouteMatcher.new
|
74
|
+
p routes.route_request( "/hey/jon/you/girls")
|
75
|
+
routes = Merb::RouteMatcher.new
|
76
|
+
p routes.route_request( "/upload/test/12")
|
77
|
+
routes = Merb::RouteMatcher.new
|
78
|
+
p routes.route_request( "/these/234/are/yup")
|
79
|
+
routes = Merb::RouteMatcher.new
|
80
|
+
p routes.route_request( '/h/12/b/red/d/blue/g/12')
|
81
|
+
routes = Merb::RouteMatcher.new
|
82
|
+
p routes.route_request( '/hdsfvsdfsdfdsf')
|
83
|
+
|
84
|
+
p routes.routes
|
85
|
+
|
86
|
+
end
|
87
|
+
=end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class String
|
2
|
+
def import
|
3
|
+
Merb::Server.config[:allow_reloading] ? load( self + '.rb' ) : require( self )
|
4
|
+
end
|
5
|
+
|
6
|
+
def controller_class_name
|
7
|
+
self.capitalize
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class MerbHash < HashWithIndifferentAccess
|
12
|
+
def method_missing(m,*a)
|
13
|
+
m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m]:raise(NoMethodError,"#{m}")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Symbol
|
18
|
+
def to_s
|
19
|
+
@str_rep || (@str_rep = id2name.freeze)
|
20
|
+
end
|
21
|
+
end
|
data/lib/merb_config.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Merb
|
2
|
+
end
|
3
|
+
|
4
|
+
class Merb::Config
|
5
|
+
def self.setup
|
6
|
+
defaults = {
|
7
|
+
:host => "0.0.0.0",
|
8
|
+
:port => "4000",
|
9
|
+
:allow_reloading => true,
|
10
|
+
:merb_root => Dir.pwd
|
11
|
+
}
|
12
|
+
|
13
|
+
begin
|
14
|
+
options = defaults.merge(YAML.load(ERB.new(IO.read("#{defaults[:merb_root]}/conf/merb.yml")).result))
|
15
|
+
rescue
|
16
|
+
options = defaults
|
17
|
+
end
|
18
|
+
|
19
|
+
options
|
20
|
+
end
|
21
|
+
end
|
data/server.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
require 'rubygems'
|
3
|
+
$LOAD_PATH.unshift( File.join( File.dirname( __FILE__ ) , 'lib' ) )
|
4
|
+
$LOAD_PATH.unshift( File.join( File.dirname( __FILE__ ) , 'app/controllers' ) )
|
5
|
+
require 'merb'
|
6
|
+
MERB_ROOT = File.dirname( __FILE__ )
|
7
|
+
|
8
|
+
h = Mongrel::HttpServer.new("0.0.0.0", PORT, 40)
|
9
|
+
h.register("/", MerbHandler.new)
|
10
|
+
h.register("/favicon.ico", Mongrel::Error404Handler.new(""))
|
11
|
+
h.run.join
|
12
|
+
|
13
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'test/unit'
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
require File.dirname(__FILE__) + '/../../lib/merb'
|
3
|
+
|
4
|
+
class RouteMatcherTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
Merb::RouteMatcher.prepare do |r|
|
8
|
+
r.add '/foo/:bar/baz/:id', :class => 'Test', :method => 'foo'
|
9
|
+
r.add '/hey/:there/you/:guys', :class => 'Test', :method => 'hello'
|
10
|
+
r.add '/these/:routes/are/:sweet', :class => 'Upload', :method => 'start'
|
11
|
+
r.add '/h/:a/b/:c/d/:f/g/:id', :class => 'Test'
|
12
|
+
r.add '/:class/:method/:id', {}
|
13
|
+
r.add '/:class/:method', {}
|
14
|
+
r.add '.*', {:class => 'NoRouteFound', :method => 'noroute'}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_compiled_statement
|
19
|
+
r = Merb::RouteMatcher.new
|
20
|
+
assert( r.compiled_statement.kind_of?(String))
|
21
|
+
assert( r.compiled_statement =~ /lambda\{\|path/)
|
22
|
+
assert( r.compiled_statement =~ /Regexp\.new/)
|
23
|
+
assert( r.compiled_statement =~ /merge\(@sections\)/)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_route_matching
|
27
|
+
routes = Merb::RouteMatcher.new
|
28
|
+
assert_equal( {:class=>"Test", :bar=>"123", :id=>"456", :method=>"foo"}, routes.route_request("/foo/123/baz/456"))
|
29
|
+
routes = Merb::RouteMatcher.new
|
30
|
+
assert_equal( {:class=>"Test", :there=>"jon", :guys=>"girls", :method=>"hello"}, routes.route_request( "/hey/jon/you/girls"))
|
31
|
+
routes = Merb::RouteMatcher.new
|
32
|
+
assert_equal({:class=>"upload", :id=>"12", :method=>"test"},routes.route_request( "/upload/test/12"))
|
33
|
+
routes = Merb::RouteMatcher.new
|
34
|
+
assert_equal({:class=>"nik", :method=>"nak"},routes.route_request( "/nik/nak"))
|
35
|
+
routes = Merb::RouteMatcher.new
|
36
|
+
assert_equal({:class=>"lots", :method=>"of", :id => "12"},routes.route_request( "/lots/of/12"))
|
37
|
+
routes = Merb::RouteMatcher.new
|
38
|
+
assert_equal({:class => 'NoRouteFound', :method => 'noroute'},routes.route_request( "/"))
|
39
|
+
routes = Merb::RouteMatcher.new
|
40
|
+
assert_equal({:class => 'NoRouteFound', :method => 'noroute'},routes.route_request( "/dfnasfnafn"))
|
41
|
+
routes = Merb::RouteMatcher.new
|
42
|
+
assert_equal( {:class=>"Upload", :routes=>"234", :sweet=>"yup", :method=>"start"}, routes.route_request( "/these/234/are/yup"))
|
43
|
+
routes = Merb::RouteMatcher.new
|
44
|
+
assert_equal( {:class=>"Test", :a=>"12", :c=>"red", :id=>"12", :f=>"blue"}, routes.route_request( '/h/12/b/red/d/blue/g/12'))
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_route_doesnt_match
|
48
|
+
routes = Merb::RouteMatcher.new
|
49
|
+
assert_equal({:class=>"NoRouteFound", :method=>"noroute"},routes.route_request( '/hdsfvsdfsdfdsf'))
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_regexes_included_in_compiled_statement
|
53
|
+
routes = Merb::RouteMatcher.new
|
54
|
+
routes.routes.each do |r|
|
55
|
+
assert Regexp.new(r[0]) =~ routes.compiled_statement
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.0
|
3
|
+
specification_version: 1
|
4
|
+
name: merb
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.0.1
|
7
|
+
date: 2006-10-15 00:00:00 -07:00
|
8
|
+
summary: Merb == Mongrel + Erb. Pocket rocket web framework.
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: ez@engineyard.com
|
12
|
+
homepage: http://merb.devjavu.com
|
13
|
+
rubyforge_project:
|
14
|
+
description: Merb == Mongrel + Erb. Pocket rocket web framework.
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.8.4
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Ezra Zygmuntowicz
|
31
|
+
files:
|
32
|
+
- LICENSE
|
33
|
+
- README
|
34
|
+
- Rakefile
|
35
|
+
- server.rb
|
36
|
+
- bin/merb
|
37
|
+
- test/test_helper.rb
|
38
|
+
- test/unit
|
39
|
+
- test/unit/route_matcher_test.rb
|
40
|
+
- lib/merb
|
41
|
+
- lib/merb.rb
|
42
|
+
- lib/merb_config.rb
|
43
|
+
- lib/merb/merb_controller.rb
|
44
|
+
- lib/merb/merb_daemon.rb
|
45
|
+
- lib/merb/merb_handler.rb
|
46
|
+
- lib/merb/merb_router.rb
|
47
|
+
- lib/merb/merb_utils.rb
|
48
|
+
- examples/sample_app
|
49
|
+
- examples/sample_app/app
|
50
|
+
- examples/sample_app/conf
|
51
|
+
- examples/sample_app/lib
|
52
|
+
- examples/sample_app/log
|
53
|
+
- examples/sample_app/public
|
54
|
+
- examples/sample_app/app/controllers
|
55
|
+
- examples/sample_app/app/views
|
56
|
+
- examples/sample_app/app/controllers/noroutefound.rb
|
57
|
+
- examples/sample_app/app/controllers/test.rb
|
58
|
+
- examples/sample_app/app/controllers/upload.rb
|
59
|
+
- examples/sample_app/app/views/test
|
60
|
+
- examples/sample_app/app/views/upload
|
61
|
+
- examples/sample_app/app/views/test/foo.rhtml
|
62
|
+
- examples/sample_app/app/views/test/hello.rhtml
|
63
|
+
- examples/sample_app/app/views/upload/start.rhtml
|
64
|
+
- examples/sample_app/app/views/upload/upload.rhtml
|
65
|
+
- examples/sample_app/conf/merb_init.rb
|
66
|
+
- examples/sample_app/conf/router.rb
|
67
|
+
- examples/sample_app/public/files
|
68
|
+
test_files: []
|
69
|
+
|
70
|
+
rdoc_options:
|
71
|
+
- --quiet
|
72
|
+
- --title
|
73
|
+
- Merb Documentation
|
74
|
+
- --opname
|
75
|
+
- index.html
|
76
|
+
- --line-numbers
|
77
|
+
- --main
|
78
|
+
- README
|
79
|
+
- --inline-source
|
80
|
+
- --exclude
|
81
|
+
- ^(app|uploads)
|
82
|
+
extra_rdoc_files:
|
83
|
+
- README
|
84
|
+
- LICENSE
|
85
|
+
executables:
|
86
|
+
- merb
|
87
|
+
extensions: []
|
88
|
+
|
89
|
+
requirements: []
|
90
|
+
|
91
|
+
dependencies:
|
92
|
+
- !ruby/object:Gem::Dependency
|
93
|
+
name: mongrel
|
94
|
+
version_requirement:
|
95
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">"
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: 0.0.0
|
100
|
+
version:
|