merb 0.0.1
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/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:
|