fastr 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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 # OS X")
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"
@@ -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
- do_dispatch(env)
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 do_dispatch(env)
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.env = env
102
+ setup_controller(obj, env)
47
103
 
48
- ret = obj.send(action)
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
- #[200, {}, "ok"]
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
- log.debug "Loading application classes..."
89
- Dir["#{self.app_path}/app/controllers/*.rb"].each do |f|
90
- log.debug "Loading: #{f}"
91
- load(f)
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
- Dir["#{self.app_path}/app/controllers/*.rb"].each do |f|
102
- EM.watch_file(f, Handler)
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.info "Reloading file: #{path}"
252
+ app.log.debug "Reloading file: #{path}"
109
253
  load(path)
110
254
  end
111
255
  end
@@ -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
@@ -23,6 +23,10 @@ module Fastr
23
23
  EM.defer(operation, callback)
24
24
  end
25
25
 
26
+ def closed(&cb)
27
+ self.errback(&cb)
28
+ end
29
+
26
30
  def finish
27
31
  self.succeed
28
32
  end
@@ -0,0 +1,10 @@
1
+ class String
2
+ def camelcase
3
+ split = self.split('_')
4
+ newStr = []
5
+
6
+ split.each { |s| newStr << s.capitalize }
7
+
8
+ newStr.join('')
9
+ end
10
+ end
@@ -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 = Logger.new(STDOUT)
8
- @logger.level = Logger::DEBUG
9
- @logger.formatter = Fastr::Log::Formatter.new(kls)
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
@@ -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
@@ -0,0 +1,5 @@
1
+ module Fastr
2
+ module Session
3
+
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ module Fastr
2
+ class Settings
3
+ include Fastr::Log
4
+
5
+ attr_accessor :cache_templates
6
+
7
+ def initialize(app)
8
+ @app = app
9
+ @cache_templates = true
10
+ end
11
+
12
+ def log_level=(level)
13
+ Fastr::Log.level = level
14
+ end
15
+ end
16
+ end
@@ -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(txt)
26
- [200, {"Content-Type" => 'text/plain'}, txt]
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
@@ -0,0 +1,5 @@
1
+ router.draw do |route|
2
+ route.for '/:controller/:action'
3
+ route.for '/home/:action', :action => '[A-Za-z]+'
4
+ route.for '/test', :to => 'home#index'
5
+ end
@@ -0,0 +1,2 @@
1
+ class FastrAppController
2
+ 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
@@ -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: 27
4
+ hash: 25
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 2
10
- version: 0.0.2
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-08 00:00:00 -07:00
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/test_fastr.rb
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/test_fastr.rb
136
+ - test/test_application.rb
137
+ - test/test_deferrable.rb
138
+ - test/test_deferrable_response.rb
139
+ - test/test_router.rb
data/test/test_fastr.rb DELETED
@@ -1,7 +0,0 @@
1
- require 'helper'
2
-
3
- class TestFastr < Test::Unit::TestCase
4
- should "probably rename this file and start testing for real" do
5
- flunk "hey buddy, you should probably rename this file and start testing for real"
6
- end
7
- end