fastr 0.0.2 → 0.0.3
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/README.rdoc +88 -0
- data/bin/fastr +10 -2
- data/lib/fastr/application.rb +160 -16
- data/lib/fastr/controller.rb +24 -1
- data/lib/fastr/deferrable.rb +4 -0
- data/lib/fastr/extensions/string.rb +10 -0
- data/lib/fastr/filter.rb +44 -0
- data/lib/fastr/logger.rb +25 -3
- data/lib/fastr/plugin.rb +41 -0
- data/lib/fastr/session/cookie.rb +0 -0
- data/lib/fastr/session.rb +5 -0
- data/lib/fastr/settings.rb +16 -0
- data/lib/fastr/template.rb +9 -4
- data/lib/fastr.rb +4 -0
- data/test/fastr_app/app/config/routes.rb +5 -0
- data/test/fastr_app/app/controllers/fastr_app_controller.rb +2 -0
- data/test/helper.rb +36 -0
- data/test/test_application.rb +39 -0
- data/test/test_deferrable.rb +55 -0
- data/test/test_deferrable_response.rb +35 -0
- data/test/test_router.rb +100 -0
- metadata +66 -8
- data/test/test_fastr.rb +0 -7
data/README.rdoc
CHANGED
@@ -14,6 +14,7 @@ Micro web framework for Ruby. Should be used with an EventMachine rack server.
|
|
14
14
|
The directory structure is similar to rails:
|
15
15
|
|
16
16
|
* app/(config/controller/views/models)
|
17
|
+
* public
|
17
18
|
* lib
|
18
19
|
* test
|
19
20
|
|
@@ -27,6 +28,13 @@ The routes are configured in app/config/routes.rb
|
|
27
28
|
#route.for '/test', :to => 'home#index'
|
28
29
|
end
|
29
30
|
|
31
|
+
== Settings
|
32
|
+
|
33
|
+
Various settings can be configured in app/config/settings.rb
|
34
|
+
|
35
|
+
config.log_level = Logger::DEBUG
|
36
|
+
config.cache_templates = true
|
37
|
+
|
30
38
|
== Controller
|
31
39
|
|
32
40
|
class HomeController < Fastr::Controller
|
@@ -34,6 +42,24 @@ The routes are configured in app/config/routes.rb
|
|
34
42
|
render(:text, "Hello, world!")
|
35
43
|
end
|
36
44
|
end
|
45
|
+
|
46
|
+
= Request/Response Information
|
47
|
+
|
48
|
+
== Headers
|
49
|
+
|
50
|
+
You can set response headers by accessing the attribute headers:
|
51
|
+
|
52
|
+
self.headers['My-Header'] = 'value'
|
53
|
+
|
54
|
+
== Cookies
|
55
|
+
|
56
|
+
You can read cookies by accessing the attribute cookies:
|
57
|
+
|
58
|
+
puts self.cookies['MY_SESS_COOKIE']
|
59
|
+
|
60
|
+
Set cookie:
|
61
|
+
|
62
|
+
set_cookie("sess", myuniquekey, {:expires => Time.now + 3600})
|
37
63
|
|
38
64
|
== Return a view in a controller
|
39
65
|
|
@@ -46,6 +72,10 @@ You can also use the following render methods:
|
|
46
72
|
With HAML, the template is rendered and any instance variables in your controller are available in the template.
|
47
73
|
|
48
74
|
render(:haml, :template => "index") # this searches for index.haml in your app/views/ folder
|
75
|
+
|
76
|
+
JSON:
|
77
|
+
|
78
|
+
render(:json, {:status => "ok", :message => "done"})
|
49
79
|
|
50
80
|
== Deferred Responses
|
51
81
|
|
@@ -73,9 +103,67 @@ The following is an example of a controller action.
|
|
73
103
|
response.succeed
|
74
104
|
}
|
75
105
|
|
106
|
+
# This is used to get a callback when the request's connection is closed
|
107
|
+
response.closed do
|
108
|
+
puts "Connection closed."
|
109
|
+
end
|
110
|
+
|
76
111
|
response.task(long_task, callback)
|
77
112
|
end
|
78
113
|
end
|
114
|
+
|
115
|
+
== Plugins
|
116
|
+
|
117
|
+
Fastr searches the custom/plugins directory in your application's root directory for loading plugins.
|
118
|
+
|
119
|
+
Example structure:
|
120
|
+
|
121
|
+
* custom/plugins/my_test/plugin.rb
|
122
|
+
* custom/plugins/my_test/lib/*.rb
|
123
|
+
|
124
|
+
When a plugin is found, the plugin.rb file is loaded. It should contain a module that matches the name of your plugin, ending with the word plugin.
|
125
|
+
|
126
|
+
Example:
|
127
|
+
|
128
|
+
Directory: my_test
|
129
|
+
|
130
|
+
Module name: MyTestPlugin
|
131
|
+
|
132
|
+
Here is an example plugin and what is currently supported:
|
133
|
+
|
134
|
+
module MyTestPlugin
|
135
|
+
def self.after_boot(app)
|
136
|
+
puts "booted: #{app}"
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.before_dispatch(app, env)
|
140
|
+
env
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.after_dispatch(app, env, response)
|
144
|
+
response
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
== Static Files
|
149
|
+
|
150
|
+
Anything stored in the public folder in your project's root directory will be served as a static file. This directory is checked before the routes. The mime type is set based on the file's extension.
|
151
|
+
|
152
|
+
== Change Log
|
153
|
+
|
154
|
+
0.3
|
155
|
+
|
156
|
+
* Added kqueue/epoll.
|
157
|
+
* Fixed dependencies.
|
158
|
+
* Added loading of model and lib directories.
|
159
|
+
* Added settings file.
|
160
|
+
* Added controller params.
|
161
|
+
* Added ability to disable template caching.
|
162
|
+
* Added closed callback for deferred responses.
|
163
|
+
* Added static file serving.
|
164
|
+
* Added JSON rendering.
|
165
|
+
* Added response headers and cookie handling.
|
166
|
+
* Added plugins.
|
79
167
|
|
80
168
|
== Current Status
|
81
169
|
|
data/bin/fastr
CHANGED
@@ -11,7 +11,7 @@ module Fastr
|
|
11
11
|
|
12
12
|
# Directory Structre
|
13
13
|
|
14
|
-
dirs = ['app/config', 'app/controllers', 'app/views', 'app/models', 'lib', 'test']
|
14
|
+
dirs = ['app/config', 'app/controllers', 'app/views', 'app/models', 'lib', 'test', 'public', 'custom/plugins']
|
15
15
|
dirs.each do |dir|
|
16
16
|
FileUtils.mkdir_p("#{app_name}/#{dir}")
|
17
17
|
end
|
@@ -29,7 +29,8 @@ module Fastr
|
|
29
29
|
# Rack file
|
30
30
|
File.open("#{app_name}/config.ru", "w") do |f|
|
31
31
|
f.puts("require 'fastr'")
|
32
|
-
f.puts("EM.kqueue = true
|
32
|
+
f.puts("EM.kqueue = true if EM.kqueue?")
|
33
|
+
f.puts("EM.epoll = true if EM.epoll?")
|
33
34
|
f.puts("fastrApp = Fastr::Application.new(File.expand_path(File.dirname(__FILE__)))")
|
34
35
|
f.puts("app = lambda { |env|")
|
35
36
|
f.puts("\tfastrApp.dispatch(env)")
|
@@ -38,6 +39,13 @@ module Fastr
|
|
38
39
|
f.close
|
39
40
|
end
|
40
41
|
|
42
|
+
# Settings file
|
43
|
+
File.open("#{app_name}/app/config/settings.rb", "w") do |f|
|
44
|
+
f.puts("config.log_level = Logger::DEBUG")
|
45
|
+
f.puts("config.cache_templates = true")
|
46
|
+
f.close
|
47
|
+
end
|
48
|
+
|
41
49
|
# Gemfile
|
42
50
|
File.open("#{app_name}/Gemfile", 'a') do |f|
|
43
51
|
f.puts "source :gemcutter"
|
data/lib/fastr/application.rb
CHANGED
@@ -1,32 +1,88 @@
|
|
1
1
|
require 'logger'
|
2
|
+
require 'cgi'
|
3
|
+
require 'mime/types'
|
2
4
|
|
3
5
|
module Fastr
|
4
6
|
class Application
|
5
7
|
include Fastr::Log
|
6
|
-
|
7
|
-
attr_accessor :router, :app_path
|
8
8
|
|
9
|
+
SETTINGS_FILE = "app/config/settings.rb"
|
10
|
+
INIT_FILE = "app/config/init.rb"
|
11
|
+
PUBLIC_FOLDER = "public"
|
12
|
+
|
13
|
+
attr_accessor :router, :app_path, :settings, :plugins
|
14
|
+
|
15
|
+
# These are resources we are watching to change.
|
16
|
+
# They will be reloaded upon change.
|
17
|
+
@@load_paths = {
|
18
|
+
:controller => "app/controllers/*.rb",
|
19
|
+
:model => "app/models/*.rb",
|
20
|
+
:lib => "lib/*.rb"
|
21
|
+
}
|
22
|
+
|
23
|
+
# Sets the application's initial state to booting and then kicks off the boot.
|
9
24
|
def initialize(path)
|
10
25
|
self.app_path = path
|
11
|
-
|
26
|
+
self.settings = Fastr::Settings.new(self)
|
27
|
+
self.plugins = []
|
12
28
|
@booting = true
|
13
29
|
boot
|
14
30
|
end
|
15
31
|
|
32
|
+
# Convenience wrapper for do_dispatch
|
33
|
+
# This is the heart of the server, called indirectly by a Rack aware server.
|
16
34
|
def dispatch(env)
|
17
35
|
return [500, {}, "Server Not Ready"] if @booting
|
18
36
|
|
19
37
|
begin
|
20
|
-
|
38
|
+
new_env = plugin_before_dispatch(env)
|
39
|
+
plugin_after_dispatch(new_env, do_dispatch(new_env))
|
21
40
|
rescue Exception => e
|
22
41
|
bt = e.backtrace.join("\n")
|
23
42
|
[500, {}, "Exception: #{e}\n\n#{bt}"]
|
24
43
|
end
|
25
44
|
end
|
26
45
|
|
27
|
-
def
|
46
|
+
def plugin_before_dispatch(env)
|
47
|
+
new_env = env
|
48
|
+
|
49
|
+
self.plugins.each do |plugin|
|
50
|
+
if plugin.respond_to? :before_dispatch
|
51
|
+
new_env = plugin.send(:before_dispatch, self, env)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
new_env
|
56
|
+
end
|
57
|
+
|
58
|
+
def plugin_after_dispatch(env, response)
|
59
|
+
new_response = response
|
60
|
+
|
61
|
+
self.plugins.each do |plugin|
|
62
|
+
if plugin.respond_to? :after_dispatch
|
63
|
+
new_response = plugin.send(:after_dispatch, self, env, response)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
new_response
|
68
|
+
end
|
69
|
+
|
70
|
+
def plugin_after_boot
|
71
|
+
self.plugins.each do |plugin|
|
72
|
+
if plugin.respond_to? :after_boot
|
73
|
+
new_env = plugin.send(:after_boot, self)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Route, instantiate controller, return response from controller's action.
|
79
|
+
def do_dispatch(env)
|
28
80
|
path = env['PATH_INFO']
|
29
81
|
|
82
|
+
# Try to serve a public file
|
83
|
+
ret = dispatch_public(env, path)
|
84
|
+
return ret if not ret.nil?
|
85
|
+
|
30
86
|
log.debug "Checking for routes that match: #{path}"
|
31
87
|
route = router.match(env)
|
32
88
|
|
@@ -43,12 +99,14 @@ module Fastr
|
|
43
99
|
log.info "Routing to controller: #{klass}, action: #{action}"
|
44
100
|
|
45
101
|
obj = Module.const_get(klass).new
|
46
|
-
obj
|
102
|
+
setup_controller(obj, env)
|
47
103
|
|
48
|
-
|
104
|
+
code, hdrs, body = *obj.send(action)
|
105
|
+
|
106
|
+
# Merge headers with anything specified in the controller
|
107
|
+
hdrs.merge!(obj.headers)
|
49
108
|
|
50
|
-
|
51
|
-
ret
|
109
|
+
[code, hdrs, body]
|
52
110
|
else
|
53
111
|
[404, {"Content-Type" => "text/plain"}, "404 Not Found: #{path}"]
|
54
112
|
end
|
@@ -56,6 +114,57 @@ module Fastr
|
|
56
114
|
|
57
115
|
private
|
58
116
|
|
117
|
+
def dispatch_public(env, path)
|
118
|
+
path = "#{self.app_path}/#{PUBLIC_FOLDER}/#{path[1..(path.length - 1)]}"
|
119
|
+
if not File.directory? path and File.exists? path
|
120
|
+
f = File.open(path)
|
121
|
+
hdrs = {}
|
122
|
+
|
123
|
+
type = MIME::Types.type_for(File.basename(path))
|
124
|
+
|
125
|
+
hdrs["Content-Type"] = type.to_s if not type.nil?
|
126
|
+
|
127
|
+
return [200, hdrs, f.read]
|
128
|
+
else
|
129
|
+
return nil
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def setup_controller(controller, env)
|
134
|
+
controller.env = env
|
135
|
+
controller.params = {}
|
136
|
+
controller.headers = {}
|
137
|
+
|
138
|
+
CGI::parse(env['QUERY_STRING']).each do |k,v|
|
139
|
+
if v.length == 1
|
140
|
+
controller.params[k] = v[0]
|
141
|
+
else
|
142
|
+
controller.params[k] = v
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
controller.cookies = get_cookies(env)
|
147
|
+
|
148
|
+
|
149
|
+
controller.app = self
|
150
|
+
end
|
151
|
+
|
152
|
+
def get_cookies(env)
|
153
|
+
if env.has_key? "HTTP_COOKIE"
|
154
|
+
cookies = env['HTTP_COOKIE'].split(';')
|
155
|
+
c = {}
|
156
|
+
cookies.each do |cookie|
|
157
|
+
info = cookie.strip.split("=")
|
158
|
+
if info.length == 2
|
159
|
+
c[info[0].strip] = info[1].strip
|
160
|
+
end
|
161
|
+
end
|
162
|
+
c
|
163
|
+
else
|
164
|
+
{}
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
59
168
|
#
|
60
169
|
# This is used to initialize the application.
|
61
170
|
# It runs in a thread because startup depends on EventMachine running
|
@@ -65,11 +174,20 @@ module Fastr
|
|
65
174
|
sleep 1 until EM.reactor_running?
|
66
175
|
|
67
176
|
begin
|
177
|
+
log.info "Loading application..."
|
178
|
+
|
179
|
+
load_settings
|
180
|
+
Fastr::Plugin.load(self)
|
68
181
|
load_app_classes
|
69
182
|
setup_router
|
70
183
|
setup_watcher
|
71
184
|
|
185
|
+
log.info "Application loaded successfully."
|
186
|
+
|
72
187
|
@booting = false
|
188
|
+
|
189
|
+
plugin_after_boot
|
190
|
+
app_init
|
73
191
|
rescue Exception => e
|
74
192
|
log.error "#{e}"
|
75
193
|
puts e.backtrace
|
@@ -79,33 +197,59 @@ module Fastr
|
|
79
197
|
end
|
80
198
|
end
|
81
199
|
|
200
|
+
# Initializes the router and loads the routes.
|
82
201
|
def setup_router
|
83
202
|
self.router = Fastr::Router.new(self)
|
84
203
|
self.router.load
|
85
204
|
end
|
86
205
|
|
206
|
+
# Loads all application classes. Called on startup.
|
87
207
|
def load_app_classes
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
208
|
+
@@load_paths.each do |name, path|
|
209
|
+
log.debug "Loading #{name} classes..."
|
210
|
+
|
211
|
+
Dir["#{self.app_path}/#{path}"].each do |f|
|
212
|
+
log.debug "Loading: #{f}"
|
213
|
+
load(f)
|
214
|
+
end
|
92
215
|
end
|
93
216
|
end
|
94
217
|
|
218
|
+
def app_init
|
219
|
+
return if not File.exists? INIT_FILE
|
220
|
+
|
221
|
+
init_file = File.open(INIT_FILE)
|
222
|
+
self.instance_eval(init_file.read)
|
223
|
+
end
|
224
|
+
|
225
|
+
def load_settings
|
226
|
+
return if not File.exists? SETTINGS_FILE
|
227
|
+
|
228
|
+
config_file = File.open(SETTINGS_FILE)
|
229
|
+
self.instance_eval(config_file.read)
|
230
|
+
end
|
231
|
+
|
232
|
+
# Watch for any file changes in the load paths.
|
95
233
|
def setup_watcher
|
96
234
|
this = self
|
97
235
|
Handler.send(:define_method, :app) do
|
98
236
|
this
|
99
237
|
end
|
100
238
|
|
101
|
-
|
102
|
-
|
239
|
+
@@load_paths.each do |name, path|
|
240
|
+
Dir["#{self.app_path}/#{path}"].each do |f|
|
241
|
+
EM.watch_file(f, Handler)
|
242
|
+
end
|
103
243
|
end
|
104
244
|
end
|
105
245
|
|
246
|
+
def config
|
247
|
+
return self.settings
|
248
|
+
end
|
249
|
+
|
106
250
|
module Handler
|
107
251
|
def file_modified
|
108
|
-
app.log.
|
252
|
+
app.log.debug "Reloading file: #{path}"
|
109
253
|
load(path)
|
110
254
|
end
|
111
255
|
end
|
data/lib/fastr/controller.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Fastr
|
2
2
|
class Controller
|
3
|
-
attr_accessor :env
|
3
|
+
attr_accessor :env, :params, :app, :headers, :cookies
|
4
4
|
|
5
5
|
include Fastr::Template
|
6
6
|
include Fastr::Deferrable
|
@@ -8,5 +8,28 @@ module Fastr
|
|
8
8
|
def self.inherited(kls)
|
9
9
|
kls.instance_eval('include Fastr::Log')
|
10
10
|
end
|
11
|
+
|
12
|
+
def set_cookie(key, value, options={})
|
13
|
+
cookie = ["#{key}=#{value};"]
|
14
|
+
|
15
|
+
|
16
|
+
if options.has_key? :expires and options[:expires].kind_of? Time
|
17
|
+
options[:expires] = options[:expires].utc.strftime('%a, %d-%b-%Y %H:%M:%S GMT')
|
18
|
+
end
|
19
|
+
|
20
|
+
options.each do |k,v|
|
21
|
+
cookie << "#{k}=#{v.to_s};"
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
cookie_val = cookie.join(' ')
|
27
|
+
|
28
|
+
if self.headers['Set-Cookie'].nil?
|
29
|
+
self.headers['Set-Cookie'] = [cookie_val]
|
30
|
+
else
|
31
|
+
self.headers['Set-Cookie'] << cookie_val
|
32
|
+
end
|
33
|
+
end
|
11
34
|
end
|
12
35
|
end
|
data/lib/fastr/deferrable.rb
CHANGED
data/lib/fastr/filter.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Fastr
|
2
|
+
module Filter
|
3
|
+
def self.included(kls)
|
4
|
+
|
5
|
+
kls.extend(ClassMethods)
|
6
|
+
puts kls
|
7
|
+
puts kls.instance_variable_get(:@filters).inspect
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def before_filter(*args)
|
12
|
+
methods = []
|
13
|
+
options = {}
|
14
|
+
puts self
|
15
|
+
args.each do |arg|
|
16
|
+
if arg.kind_of? Symbol
|
17
|
+
methods << arg
|
18
|
+
elsif arg.kind_of? Hash
|
19
|
+
options = arg
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
setup_filter(methods, options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def setup_filter(methods, options)
|
27
|
+
if self.instance_variable_defined? :@filters
|
28
|
+
f = self.instance_variable_get(:@filters)
|
29
|
+
else
|
30
|
+
f = []
|
31
|
+
self.instance_variable_set(:@filters, f)
|
32
|
+
end
|
33
|
+
|
34
|
+
if options.has_key? :only and options[:only].kind_of? Array
|
35
|
+
f << {:methods => methods, :only => options[:only]}
|
36
|
+
elsif options.has_key? :except and options[:except].kind_of? Array
|
37
|
+
f << {:methods => methods, :except => options[:except]}
|
38
|
+
else
|
39
|
+
f << {:methods => methods, :all => true}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/fastr/logger.rb
CHANGED
@@ -2,11 +2,19 @@ require 'logger'
|
|
2
2
|
|
3
3
|
module Fastr
|
4
4
|
module Log
|
5
|
+
@log_level = Logger::DEBUG
|
6
|
+
@log_location = STDOUT
|
7
|
+
@log_classes = []
|
8
|
+
|
5
9
|
def self.included(kls)
|
10
|
+
level = @log_level
|
11
|
+
log_classes = @log_classes
|
12
|
+
log_location = @log_location
|
13
|
+
|
6
14
|
kls.instance_eval do
|
7
|
-
@logger =
|
8
|
-
|
9
|
-
@logger
|
15
|
+
@logger = Fastr::Log.create_logger(log_location, level, kls)
|
16
|
+
|
17
|
+
log_classes << @logger
|
10
18
|
|
11
19
|
def logger
|
12
20
|
@logger
|
@@ -19,6 +27,20 @@ module Fastr
|
|
19
27
|
end
|
20
28
|
end
|
21
29
|
end
|
30
|
+
|
31
|
+
def self.create_logger(location, level, kls)
|
32
|
+
logger = Logger.new(location)
|
33
|
+
logger.level = level
|
34
|
+
logger.formatter = Fastr::Log::Formatter.new(kls)
|
35
|
+
logger
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.level=(level)
|
39
|
+
@log_level = level
|
40
|
+
@log_classes.each do |log|
|
41
|
+
log.level = level
|
42
|
+
end
|
43
|
+
end
|
22
44
|
|
23
45
|
class Formatter < Logger::Formatter
|
24
46
|
attr_accessor :progname
|
data/lib/fastr/plugin.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Fastr
|
2
|
+
module Plugin
|
3
|
+
include Fastr::Log
|
4
|
+
|
5
|
+
PLUGIN_PATH = "custom/plugins"
|
6
|
+
|
7
|
+
def self.load(app)
|
8
|
+
logger.debug "Loading plugins..."
|
9
|
+
|
10
|
+
if File.directory? "#{app.app_path}/#{PLUGIN_PATH}"
|
11
|
+
load_plugins_dir(app)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.load_plugins_dir(app)
|
16
|
+
Dir.foreach("#{app.app_path}/#{PLUGIN_PATH}") do |filename|
|
17
|
+
if filename != '.' and filename != '..'
|
18
|
+
load_plugin(app, filename, "#{app.app_path}/#{PLUGIN_PATH}/#{filename}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.load_plugin(app, name, dir)
|
24
|
+
plugin_name = "#{name.camelcase}Plugin"
|
25
|
+
logger.debug "Loading plugin #{plugin_name}..."
|
26
|
+
|
27
|
+
begin
|
28
|
+
require("#{dir}/plugin.rb")
|
29
|
+
m = Module.const_get(plugin_name)
|
30
|
+
|
31
|
+
if File.directory? "#{dir}/lib"
|
32
|
+
Dir.glob(File.join("#{dir}/lib/**", "*.rb")).each { |f| require("#{f}") }
|
33
|
+
end
|
34
|
+
|
35
|
+
app.plugins << m
|
36
|
+
rescue => e
|
37
|
+
logger.error "Unable to load plugin: #{e}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
File without changes
|
data/lib/fastr/template.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'haml'
|
2
|
+
require 'json'
|
2
3
|
|
3
4
|
module Fastr
|
4
5
|
module Template
|
@@ -22,8 +23,12 @@ module Fastr
|
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
25
|
-
def render_text(
|
26
|
-
[200, {"Content-Type" => 'text/plain'},
|
26
|
+
def render_text(*args)
|
27
|
+
[200, {"Content-Type" => 'text/plain'}, [args[0]]]
|
28
|
+
end
|
29
|
+
|
30
|
+
def render_json(*args)
|
31
|
+
[200, {"Content-Type" => 'application/json'}, args[0][0].to_json.to_s]
|
27
32
|
end
|
28
33
|
|
29
34
|
def render_haml(args)
|
@@ -34,12 +39,12 @@ module Fastr
|
|
34
39
|
else
|
35
40
|
tpl_data = File.read("app/views/#{tpl}.haml")
|
36
41
|
haml_engine = Haml::Engine.new(tpl_data)
|
37
|
-
@@tpl_cache[tpl] = haml_engine
|
42
|
+
@@tpl_cache[tpl] = haml_engine if self.app.settings.cache_templates
|
38
43
|
end
|
39
44
|
|
40
45
|
resp = haml_engine.render(self)
|
41
46
|
|
42
|
-
[200, {"Content-Type" => "text/html"}, resp]
|
47
|
+
[200, {"Content-Type" => "text/html"}, [resp]]
|
43
48
|
end
|
44
49
|
end
|
45
50
|
end
|
data/lib/fastr.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module Fastr
|
2
2
|
ROOT = File.expand_path(File.dirname(__FILE__))
|
3
3
|
|
4
|
+
require "#{ROOT}/fastr/extensions/string"
|
5
|
+
|
4
6
|
autoload :Application, "#{ROOT}/fastr/application"
|
5
7
|
autoload :Log, "#{ROOT}/fastr/logger"
|
6
8
|
autoload :Router, "#{ROOT}/fastr/router"
|
@@ -8,4 +10,6 @@ module Fastr
|
|
8
10
|
autoload :Controller, "#{ROOT}/fastr/controller"
|
9
11
|
autoload :Template, "#{ROOT}/fastr/template"
|
10
12
|
autoload :Deferrable, "#{ROOT}/fastr/deferrable"
|
13
|
+
autoload :Settings, "#{ROOT}/fastr/settings"
|
14
|
+
autoload :Plugin, "#{ROOT}/fastr/plugin"
|
11
15
|
end
|
data/test/helper.rb
CHANGED
@@ -1,10 +1,46 @@
|
|
1
1
|
require 'rubygems'
|
2
|
+
require 'eventmachine'
|
2
3
|
require 'test/unit'
|
3
4
|
require 'shoulda'
|
5
|
+
require 'mocha'
|
4
6
|
|
5
7
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
6
8
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
9
|
+
|
10
|
+
APP_PATH = "#{File.expand_path(File.dirname(__FILE__))}/fastr_app"
|
11
|
+
|
7
12
|
require 'fastr'
|
13
|
+
EM.kqueue = true
|
14
|
+
|
15
|
+
class NueteredBootingApplication < Fastr::Application
|
16
|
+
attr_reader :booted
|
17
|
+
def boot
|
18
|
+
@booted = true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class ManualBootingApplication < Fastr::Application
|
23
|
+
include Fastr::Log
|
24
|
+
|
25
|
+
def initialize(path)
|
26
|
+
self.app_path = path
|
27
|
+
self.plugins = []
|
28
|
+
@booting = true
|
29
|
+
end
|
30
|
+
end
|
8
31
|
|
9
32
|
class Test::Unit::TestCase
|
33
|
+
def em_setup
|
34
|
+
EM.run do
|
35
|
+
yield
|
36
|
+
EM.stop
|
37
|
+
end
|
38
|
+
end
|
10
39
|
end
|
40
|
+
|
41
|
+
class Fastr::Log::Formatter
|
42
|
+
def call(severity, time, progname, msg)
|
43
|
+
#block all logging output during testing
|
44
|
+
#puts "[#{severity}] [#{self.progname}]: #{msg}"
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestApplication < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "New Application" do
|
6
|
+
setup do
|
7
|
+
em_setup{ @application = NueteredBootingApplication.new("/some/path") }
|
8
|
+
end
|
9
|
+
|
10
|
+
should "store startup path" do
|
11
|
+
assert_equal "/some/path",@application.app_path
|
12
|
+
end
|
13
|
+
|
14
|
+
should "boot application" do
|
15
|
+
assert @application.booted
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "Application Boot" do
|
20
|
+
setup do
|
21
|
+
em_setup {
|
22
|
+
@application = ManualBootingApplication.new(APP_PATH)
|
23
|
+
@application.send(:boot).join
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
should "load app controllers" do
|
28
|
+
assert defined?(FastrAppController)
|
29
|
+
end
|
30
|
+
|
31
|
+
should "create router" do
|
32
|
+
assert_not_nil @application.router
|
33
|
+
end
|
34
|
+
|
35
|
+
should "setup routes from route file" do
|
36
|
+
assert_equal 3,@application.router.routes.size
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestDeferrable < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "Deferrable includer" do
|
6
|
+
setup do
|
7
|
+
@deferrable = DeferrableHost.new
|
8
|
+
em_setup do
|
9
|
+
@deferrable.defer_response(200, {"Content-Type" => "text/plain"}) do |response|
|
10
|
+
response.send_data("hey\n")
|
11
|
+
|
12
|
+
long_task = proc {
|
13
|
+
response.send_data("processing...\n")
|
14
|
+
return "finished"
|
15
|
+
}
|
16
|
+
|
17
|
+
callback = proc { |result|
|
18
|
+
response.send_data("#{result}\n")
|
19
|
+
response.succeed
|
20
|
+
}
|
21
|
+
|
22
|
+
response.task(long_task, callback)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
should "pass sent data data through to server callback" do
|
28
|
+
assert_not_nil @deferrable.callbacks.index("hey\n")
|
29
|
+
end
|
30
|
+
|
31
|
+
should "run long task sent through response" do
|
32
|
+
assert_not_nil @deferrable.callbacks.index("processing...\n")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class DeferrableHost
|
38
|
+
include Fastr::Deferrable
|
39
|
+
attr_accessor :callbacks
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
@callbacks = []
|
43
|
+
end
|
44
|
+
|
45
|
+
def env
|
46
|
+
{"async.callback"=>
|
47
|
+
Proc.new{|array|
|
48
|
+
response = array[2]
|
49
|
+
response.each do |arg|
|
50
|
+
@callbacks << arg
|
51
|
+
end
|
52
|
+
}
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestDeferrableResponse < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "DeferrableResponse" do
|
6
|
+
setup do
|
7
|
+
Fastr::Deferrable
|
8
|
+
@response = Fastr::DeferrableResponse.new
|
9
|
+
end
|
10
|
+
|
11
|
+
should "hold onto callback to push data through to" do
|
12
|
+
@response.each {|data| assert_equal "Some Data",data }
|
13
|
+
@response.send_data("Some Data")
|
14
|
+
end
|
15
|
+
|
16
|
+
should "execute deferred tasks" do
|
17
|
+
Object.expects(:touch!).times(2)
|
18
|
+
em_setup do
|
19
|
+
task = proc { Object.touch! }
|
20
|
+
callback = proc { Object.touch! }
|
21
|
+
@response.task(task,callback)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
should "call success callback when told to finish" do
|
26
|
+
callback_path = nil
|
27
|
+
em_setup do
|
28
|
+
@response.callback { callback_path = "success" }
|
29
|
+
@response.errback { callback_path = "failure" }
|
30
|
+
@response.finish
|
31
|
+
end
|
32
|
+
assert_equal "success",callback_path
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/test/test_router.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestRouter < Test::Unit::TestCase
|
4
|
+
context "" do
|
5
|
+
setup { Fastr::Router.any_instance.stubs(:setup_watcher) }
|
6
|
+
|
7
|
+
context "Newly Created Router" do
|
8
|
+
setup do
|
9
|
+
@router = Fastr::Router.new(NueteredBootingApplication.new(APP_PATH))
|
10
|
+
end
|
11
|
+
|
12
|
+
should "initialize an empty routes structure" do
|
13
|
+
assert_equal [],@router.routes
|
14
|
+
end
|
15
|
+
|
16
|
+
should "infer directory of routes file" do
|
17
|
+
assert_equal File.join(APP_PATH,"/app/config/routes.rb"),@router.route_file
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when matching routes" do
|
21
|
+
setup { @router.for('/:controller/:action') }
|
22
|
+
|
23
|
+
should "map vars on valid route" do
|
24
|
+
assert_equal({:ok => {:controller=>"test_controller",:action=>"test_action"}}, @router.match({'PATH_INFO' => "/test_controller/test_action"}))
|
25
|
+
end
|
26
|
+
|
27
|
+
should "return error map on invalid route" do
|
28
|
+
assert_equal({:error => :not_found},@router.match({'PATH_INFO' => "/2o4598g7vher0023801293479/123twretbnsf g//sdfb s/test_action"}))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "when parsing mapping" do
|
33
|
+
context "for standard controller" do
|
34
|
+
setup { parse_route('/:controller/:action') }
|
35
|
+
|
36
|
+
should "build proper regex" do
|
37
|
+
assert_equal "^/(\\w+)/(\\w+)$",@route_map[:regex]
|
38
|
+
end
|
39
|
+
|
40
|
+
should "have no extra arguments" do
|
41
|
+
assert_nil @route_map[:args]
|
42
|
+
end
|
43
|
+
|
44
|
+
should "have var for controller and action" do
|
45
|
+
assert_equal [:controller,:action],@route_map[:vars]
|
46
|
+
end
|
47
|
+
|
48
|
+
should "have an empty hash" do
|
49
|
+
assert @route_map[:hash].empty?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "with a regex match" do
|
54
|
+
setup { parse_route("/home/:action",:action => '[A-Za-z]+') }
|
55
|
+
|
56
|
+
should "build proper regex" do
|
57
|
+
assert_equal "^/home/([A-Za-z]+)$",@route_map[:regex]
|
58
|
+
end
|
59
|
+
|
60
|
+
should "have one argument for the action regex" do
|
61
|
+
assert_equal({:action=>"[A-Za-z]+"},@route_map[:args])
|
62
|
+
end
|
63
|
+
|
64
|
+
should "have var for the action matcher" do
|
65
|
+
assert_equal [:action],@route_map[:vars]
|
66
|
+
end
|
67
|
+
|
68
|
+
should "have an empty hash" do
|
69
|
+
assert @route_map[:hash].empty?
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "with an exact match" do
|
74
|
+
setup { parse_route("/test",:to => 'home#index') }
|
75
|
+
|
76
|
+
should "build proper regex" do
|
77
|
+
assert_equal "^/test$",@route_map[:regex]
|
78
|
+
end
|
79
|
+
|
80
|
+
should "have one argument for the direct route" do
|
81
|
+
assert_equal({:to=>"home#index"},@route_map[:args])
|
82
|
+
end
|
83
|
+
|
84
|
+
should "have no vars" do
|
85
|
+
assert @route_map[:vars].empty?
|
86
|
+
end
|
87
|
+
|
88
|
+
should "have a hash for the mapping" do
|
89
|
+
assert_equal({:controller=>"home", :action=>"index"},@route_map[:hash])
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def parse_route(route,*args)
|
97
|
+
@router.for(route,*args)
|
98
|
+
@route_map = @router.routes.first
|
99
|
+
end
|
100
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fastr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 3
|
10
|
+
version: 0.0.3
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Chris Moos
|
@@ -15,10 +15,52 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-06-
|
18
|
+
date: 2010-06-20 00:00:00 -07:00
|
19
19
|
default_executable: fastr
|
20
|
-
dependencies:
|
21
|
-
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: mime-types
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 47
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 16
|
33
|
+
version: "1.16"
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: eventmachine
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
version: "0"
|
48
|
+
type: :runtime
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: json
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
type: :runtime
|
63
|
+
version_requirements: *id003
|
22
64
|
description: A fast, micro-framework for Ruby that should be run under EventMachine servers (thin)
|
23
65
|
email: chris@tech9computers.com
|
24
66
|
executables:
|
@@ -34,13 +76,24 @@ files:
|
|
34
76
|
- lib/fastr/controller.rb
|
35
77
|
- lib/fastr/deferrable.rb
|
36
78
|
- lib/fastr/exception.rb
|
79
|
+
- lib/fastr/extensions/string.rb
|
80
|
+
- lib/fastr/filter.rb
|
37
81
|
- lib/fastr/logger.rb
|
82
|
+
- lib/fastr/plugin.rb
|
38
83
|
- lib/fastr/router.rb
|
84
|
+
- lib/fastr/session.rb
|
85
|
+
- lib/fastr/session/cookie.rb
|
86
|
+
- lib/fastr/settings.rb
|
39
87
|
- lib/fastr/template.rb
|
40
88
|
- LICENSE
|
41
89
|
- README.rdoc
|
90
|
+
- test/fastr_app/app/config/routes.rb
|
91
|
+
- test/fastr_app/app/controllers/fastr_app_controller.rb
|
42
92
|
- test/helper.rb
|
43
|
-
- test/
|
93
|
+
- test/test_application.rb
|
94
|
+
- test/test_deferrable.rb
|
95
|
+
- test/test_deferrable_response.rb
|
96
|
+
- test/test_router.rb
|
44
97
|
- bin/fastr
|
45
98
|
has_rdoc: true
|
46
99
|
homepage: http://github.com/chrismoos/fastr
|
@@ -77,5 +130,10 @@ signing_key:
|
|
77
130
|
specification_version: 3
|
78
131
|
summary: Another rack web framework for Ruby.
|
79
132
|
test_files:
|
133
|
+
- test/fastr_app/app/config/routes.rb
|
134
|
+
- test/fastr_app/app/controllers/fastr_app_controller.rb
|
80
135
|
- test/helper.rb
|
81
|
-
- test/
|
136
|
+
- test/test_application.rb
|
137
|
+
- test/test_deferrable.rb
|
138
|
+
- test/test_deferrable_response.rb
|
139
|
+
- test/test_router.rb
|