pakyow-core 0.6.3.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,6 +9,9 @@ if ARGV.first == 'new'
9
9
  elsif ARGV.first == 'server'
10
10
  ARGV.shift
11
11
  require 'commands/server'
12
+ elsif ARGV.first == 'console'
13
+ ARGV.shift
14
+ require 'commands/console'
12
15
  elsif ARGV.first == '--help' || ARGV.first == '-h' || ARGV.empty?
13
16
  puts File.open(File.join(CORE_PATH, 'commands/USAGE')).read
14
17
  end
@@ -0,0 +1,12 @@
1
+ Usage:
2
+ pakyow console [environment]
3
+
4
+ Description:
5
+ The 'pakyow console' command stages the application and starts an interactive
6
+ session. If environment is not specified, the default_environment defined by
7
+ your application will be used.
8
+
9
+ Example:
10
+ pakyow console development
11
+
12
+ This starts the console with the 'development' configuration.
@@ -0,0 +1,18 @@
1
+ if ARGV.first == '--help' || ARGV.first == '-h'
2
+ puts File.open(File.join(CORE_PATH, 'commands/USAGE-CONSOLE')).read
3
+ else
4
+ $:.unshift(Dir.pwd)
5
+
6
+ require File.join('config', 'application')
7
+ PakyowApplication::Application.stage(ARGV.first)
8
+
9
+ def reload
10
+ puts "Reloading..."
11
+ Pakyow.app.reload
12
+ end
13
+
14
+ require 'irb'
15
+ ARGV.clear
16
+ IRB.start
17
+ end
18
+
@@ -1,18 +1,18 @@
1
1
  module Pakyow
2
2
  class Application
3
3
  class << self
4
- attr_accessor :routes_proc, :middleware_proc, :configurations, :error_handlers
5
-
4
+ attr_accessor :routes_proc, :handlers_proc, :middleware_proc, :configurations
5
+
6
6
  # Sets the path to the application file so it can be reloaded later.
7
7
  #
8
8
  def inherited(subclass)
9
9
  Pakyow::Configuration::App.application_path = parse_path_from_caller(caller[0])
10
10
  end
11
-
11
+
12
12
  def parse_path_from_caller(caller)
13
13
  caller.match(/^(.+)(:?:\d+(:?:in `.+')?$)/)[1]
14
14
  end
15
-
15
+
16
16
  # Runs the application. Accepts the environment(s) to run, for example:
17
17
  # run(:development)
18
18
  # run([:development, :staging])
@@ -20,47 +20,45 @@ module Pakyow
20
20
  def run(*args)
21
21
  return if running?
22
22
  @running = true
23
-
24
- self.builder.run(self.prepare(*args))
23
+ self.builder.run(self.prepare(args))
25
24
  detect_handler.run(builder, :Host => Pakyow::Configuration::Base.server.host, :Port => Pakyow::Configuration::Base.server.port)
26
25
  end
27
-
26
+
28
27
  # Stages the application. Everything is loaded but the application is
29
28
  # not started. Accepts the same arguments as #run.
30
29
  #
31
30
  def stage(*args)
32
31
  return if staged?
33
32
  @staged = true
34
-
35
- prepare(*args)
33
+ prepare(args)
36
34
  end
37
-
35
+
38
36
  def builder
39
37
  @builder ||= Rack::Builder.new
40
38
  end
41
-
39
+
42
40
  def prepared?
43
41
  @prepared
44
42
  end
45
-
43
+
46
44
  # Returns true if the application is running.
47
45
  #
48
46
  def running?
49
47
  @running
50
48
  end
51
-
49
+
52
50
  # Returns true if the application is staged.
53
51
  #
54
52
  def staged?
55
53
  @staged
56
54
  end
57
-
55
+
58
56
  # Convenience method for base configuration class.
59
57
  #
60
58
  def config
61
59
  Pakyow::Configuration::Base
62
60
  end
63
-
61
+
64
62
  # Creates configuration for a particular environment. Example:
65
63
  # configure(:development) { app.auto_reload = true }
66
64
  #
@@ -68,68 +66,64 @@ module Pakyow
68
66
  self.configurations ||= {}
69
67
  self.configurations[environment] = block
70
68
  end
71
-
69
+
72
70
  # Creates routes. Example:
73
71
  # routes { get '/' { # do something } }
74
72
  #
75
73
  def routes(&block)
76
74
  self.routes_proc = block
77
75
  end
78
-
79
- # Creates an error handler (currently 404 and 500 errors are handled).
76
+
77
+ # Creates handlers for later execution.
80
78
  # The handler can be created one of two ways:
81
79
  #
82
- # Define a controller/action for a particular error:
83
- # error(404, :ApplicationController, :handle_404)
80
+ # Define a controller/action handler with an associate response status:
81
+ # handler(name, 404, :ApplicationController, :handle_404)
84
82
  #
85
- # Specify a block for a particular error:
86
- # error(404) { # handle error }
83
+ # Specify a block as a handler:
84
+ # handler(name, 404) { # handle error }
87
85
  #
88
- def error(*args, &block)
89
- self.error_handlers ||= {}
90
- code, controller, action = args
91
-
92
- if block
93
- self.error_handlers[code] = block
94
- else
95
- self.error_handlers[code] = {
96
- :controller => controller,
97
- :action => action
98
- }
99
- end
86
+ # If a controller calls #invoke_handler!(name) then the
87
+ # handler defined for that code will be invoked.
88
+ #
89
+ def handlers(&block)
90
+ self.handlers_proc = block
100
91
  end
101
-
92
+
102
93
  def middleware(&block)
103
94
  self.middleware_proc = block
104
95
  end
105
-
96
+
106
97
  protected
107
-
98
+
108
99
  # Prepares the application for running or staging and returns an instance
109
100
  # of the application.
110
- def prepare(*args)
101
+ def prepare(args)
111
102
  self.load_config args.empty? || args.first.nil? ? [Configuration::Base.app.default_environment] : args
112
103
  return if prepared?
113
-
104
+
114
105
  self.builder.use(Rack::MethodOverride)
115
106
  self.builder.instance_eval(&self.middleware_proc) if self.middleware_proc
107
+ self.builder.use(Pakyow::Static) if Configuration::Base.app.static
108
+ self.builder.use(Pakyow::Logger) if Configuration::Base.app.log
109
+ self.builder.use(Pakyow::Reloader) if Configuration::Base.app.auto_reload
116
110
 
117
111
  @prepared = true
118
-
112
+
119
113
  $:.unshift(Dir.pwd) unless $:.include? Dir.pwd
120
114
  return self.new
121
115
  end
122
-
116
+
123
117
  def load_config(args)
124
118
  if self.configurations
125
119
  args << Configuration::Base.app.default_environment if args.empty?
126
120
  args.each do |env|
127
- next unless config = self.configurations[env]
121
+ next unless config = self.configurations[env.to_sym]
128
122
  Configuration::Base.instance_eval(&config)
129
123
  end
130
124
  end
131
125
  end
132
-
126
+
133
127
  def detect_handler
134
128
  ['thin', 'mongrel', 'webrick'].each do |server|
135
129
  begin
@@ -142,153 +136,172 @@ module Pakyow
142
136
  end
143
137
 
144
138
  include Helpers
145
-
146
- attr_accessor :request, :response, :presenter, :route_store, :restful_routes
147
-
139
+
140
+ attr_accessor :request, :response, :presenter, :route_store, :restful_routes, :handler_store
141
+
148
142
  def initialize
149
143
  Pakyow.app = self
150
-
151
- # Create static handler
152
- @static_handler = Rack::File.new(Configuration::Base.app.public_dir)
153
-
144
+ @handler_name_to_code = {}
145
+ @handler_code_to_name = {}
146
+
154
147
  # This configuration option will be set if a presenter is to be used
155
148
  if Configuration::Base.app.presenter
156
149
  # Create a new instance of the presenter
157
150
  self.presenter = Configuration::Base.app.presenter.new
158
151
  end
159
-
152
+
160
153
  # Load application files
161
- load_app
154
+ load_app(false)
162
155
  end
163
-
156
+
164
157
  # Interrupts the application and returns response immediately.
165
158
  #
166
- def interrupt!
167
- @interrupted = true
159
+ def halt!
168
160
  throw :halt, self.response
169
161
  end
170
-
162
+
163
+ def invoke_route!(route, method=nil)
164
+ self.request.working_path = route
165
+ self.request.working_method = method if method
166
+ block = prepare_route_block(route, self.request.working_method)
167
+ throw :new_block, block
168
+ end
169
+
170
+ def invoke_handler!(name_or_code)
171
+ if block = @handler_store[name_or_code]
172
+ # we are given a name
173
+ code = @handler_name_to_code[name_or_code]
174
+ self.response.status = code if code
175
+ throw :new_block, block
176
+ elsif name = @handler_code_to_name[name_or_code]
177
+ # we are given a code
178
+ block = @handler_store[name]
179
+ self.response.status = name_or_code
180
+ throw :new_block, block
181
+ else
182
+ # no block to be found
183
+ # do we assume code if a number and set status?
184
+ self.response.status = name_or_code if name_or_code.is_a?(Fixnum)
185
+ # still need to stop execution, I think? But do nothing.
186
+ throw :new_block, nil
187
+ end
188
+ end
189
+
171
190
  # Called on every request.
172
191
  #
173
192
  def call(env)
174
- start_time = Time.now.to_f
175
-
176
- # Handle static files
177
- if env['PATH_INFO'] =~ /\.(.*)$/ && File.exists?(File.join(Configuration::Base.app.public_dir, env['PATH_INFO']))
178
- @static = true
179
- @static_handler.call(env)
180
- else
181
- # The request object
182
- self.request = Request.new(env)
183
-
184
- # Reload application files
185
- load_app
186
-
187
- if Configuration::Base.app.presenter
188
- # Handle presentation for this request
189
- self.presenter.present_for_request(request)
193
+ self.request = Request.new(env)
194
+ self.response = Rack::Response.new
195
+ self.request.working_path = self.request.path
196
+ self.request.working_method = self.request.method
197
+
198
+ has_route = false
199
+ catch(:halt) {
200
+ route_block = prepare_route_block(self.request.path, self.request.method)
201
+ has_route = true if route_block
202
+
203
+ if self.presenter
204
+ self.presenter.prepare_for_request(self.request)
190
205
  end
191
-
192
- Log.enter "Processing #{env['PATH_INFO']} (#{env['REMOTE_ADDR']} at #{Time.now}) [#{env['REQUEST_METHOD']}]"
193
-
194
- # The response object
195
- self.response = Rack::Response.new
196
- rhs = nil
197
- just_the_path, format = StringUtils.split_at_last_dot(self.request.path)
198
- self.request.format = ((format && (format[format.length - 1, 1] == '/')) ? format[0, format.length - 1] : format)
199
- catch(:halt) do
200
- rhs, packet = @route_store.get_block(just_the_path, self.request.method)
201
- request.params.merge!(HashUtils.strhash(packet[:vars]))
202
-
203
- self.request.route_spec = packet[:data][:route_spec] if packet[:data]
204
- restful_info = packet[:data][:restful] if packet[:data]
205
- self.request.restful = restful_info
206
- rhs.call() if rhs && !Pakyow::Configuration::App.ignore_routes
206
+
207
+ has_route = trampoline(route_block) if !Pakyow::Configuration::App.ignore_routes
208
+
209
+ if self.presenter
210
+ self.response.body = [self.presenter.content]
207
211
  end
208
-
209
- if !self.interrupted?
210
- if Configuration::Base.app.presenter
211
- self.response.body = [self.presenter.content]
212
- end
213
-
214
- # 404 if no facts matched and no views were found
215
- if !rhs && (!self.presenter || !self.presenter.presented?)
216
- self.handle_error(404)
217
-
218
- Log.enter "[404] Not Found"
219
- self.response.status = 404
212
+
213
+ # 404 if no route matched and no views were found
214
+ if !has_route && (!self.presenter || !self.presenter.presented?)
215
+ Log.enter "[404] Not Found"
216
+ handler404 = @handler_store[@handler_code_to_name[404]] if @handler_code_to_name[404]
217
+ if handler404
218
+ catch(:halt) {
219
+ if self.presenter
220
+ self.presenter.prepare_for_request(self.request)
221
+ end
222
+ trampoline(handler404)
223
+ if self.presenter then
224
+ self.response.body = [self.presenter.content]
225
+ end
226
+ }
220
227
  end
228
+ self.response.status = 404
221
229
  end
222
-
223
- return finish!
224
- end
230
+ } #end :halt catch block
231
+
232
+ # This needs to be in the 'return' position (last statement)
233
+ finish!
234
+
225
235
  rescue StandardError => error
226
236
  self.request.error = error
227
- self.handle_error(500)
228
-
229
- Log.enter "[500] #{error}\n"
230
- Log.enter error.backtrace.join("\n") + "\n\n"
231
-
232
- # self.response = Rack::Response.new
233
-
237
+ handler500 = @handler_store[@handler_code_to_name[500]] if @handler_code_to_name[500]
238
+ if handler500
239
+ catch(:halt) {
240
+ if self.presenter
241
+ self.presenter.prepare_for_request(self.request)
242
+ end
243
+ trampoline(handler500)
244
+ if self.presenter then
245
+ self.response.body = [self.presenter.content]
246
+ end
247
+ } #end :halt catch block
248
+ end
249
+ self.response.status = 500
250
+
234
251
  if Configuration::Base.app.errors_in_browser
235
- # Show errors in browser
236
252
  self.response.body = []
237
253
  self.response.body << "<h4>#{CGI.escapeHTML(error.to_s)}</h4>"
238
254
  self.response.body << error.backtrace.join("<br />")
239
255
  end
240
-
241
- self.response.status = 500
242
- return finish!
243
- ensure
244
- unless @static
245
- end_time = Time.now.to_f
246
- difference = ((end_time - start_time) * 1000).to_f
247
-
248
- Log.enter "Completed in #{difference}ms | #{self.response.status} | [#{self.request.url}]"
249
- Log.enter
256
+
257
+ begin
258
+ # caught by other middleware (e.g. logger)
259
+ throw :error, error
260
+ rescue ArgumentError
250
261
  end
262
+
263
+ finish!
251
264
  end
252
-
253
- # Sends a file in the response (immediately). Accepts a File object. Mime
265
+
266
+ # Sends a file in the response (immediately). Accepts a File object. Mime
254
267
  # type is automatically detected.
255
268
  #
256
- def send_file(source_file, send_as = nil, type = nil)
269
+ def send_file!(source_file, send_as = nil, type = nil)
257
270
  path = source_file.is_a?(File) ? source_file.path : source_file
258
271
  send_as ||= path
259
272
  type ||= Rack::Mime.mime_type(".#{send_as.split('.')[-1]}")
260
-
273
+
261
274
  data = ""
262
275
  File.open(path, "r").each_line { |line| data << line }
263
-
276
+
264
277
  self.response = Rack::Response.new(data, self.response.status, self.response.header.merge({ "Content-Type" => type }))
265
- interrupt!
278
+ halt!
266
279
  end
267
-
268
- # Sends data in the response (immediately). Accepts the data, mime type,
280
+
281
+ # Sends data in the response (immediately). Accepts the data, mime type,
269
282
  # and optional file name.
270
283
  #
271
- def send_data(data, type, file_name = nil)
284
+ def send_data!(data, type, file_name = nil)
272
285
  status = self.response ? self.response.status : 200
273
-
286
+
274
287
  headers = self.response ? self.response.header : {}
275
288
  headers = headers.merge({ "Content-Type" => type })
276
289
  headers = headers.merge({ "Content-disposition" => "attachment; filename=#{file_name}"}) if file_name
277
-
290
+
278
291
  self.response = Rack::Response.new(data, status, headers)
279
- interrupt!
292
+ halt!
280
293
  end
281
-
294
+
282
295
  # Redirects to location (immediately).
283
296
  #
284
- def redirect_to(location, status_code = 302)
297
+ def redirect_to!(location, status_code = 302)
285
298
  headers = self.response ? self.response.header : {}
286
299
  headers = headers.merge({'Location' => location})
287
-
300
+
288
301
  self.response = Rack::Response.new('', status_code, headers)
289
- interrupt!
302
+ halt!
290
303
  end
291
-
304
+
292
305
  # Registers a route for GET requests. Route can be defined one of two ways:
293
306
  # get('/', :ControllerClass, :action_method)
294
307
  # get('/') { # do something }
@@ -296,62 +309,62 @@ module Pakyow
296
309
  # Routes for namespaced controllers (e.g. Admin::ControllerClass) can be defined like this:
297
310
  # get('/', :Admin_ControllerClass, :action_method)
298
311
  #
299
- def get(route, controller = nil, action = nil, &block)
300
- register_route(route, block, :get, controller, action)
312
+ def get(route, *args, &block)
313
+ register_route(:user, route, block, :get, *args)
301
314
  end
302
-
315
+
303
316
  # Registers a route for POST requests (see #get).
304
317
  #
305
- def post(route, controller = nil, action = nil, &block)
306
- register_route(route, block, :post, controller, action)
318
+ def post(route, *args, &block)
319
+ register_route(:user, route, block, :post, *args)
307
320
  end
308
-
321
+
309
322
  # Registers a route for PUT requests (see #get).
310
323
  #
311
- def put(route, controller = nil, action = nil, &block)
312
- register_route(route, block, :put, controller, action)
324
+ def put(route, *args, &block)
325
+ register_route(:user, route, block, :put, *args)
313
326
  end
314
-
327
+
315
328
  # Registers a route for DELETE requests (see #get).
316
329
  #
317
- def delete(route, controller = nil, action = nil, &block)
318
- register_route(route, block, :delete, controller, action)
330
+ def delete(route, *args, &block)
331
+ register_route(:user, route, block, :delete, *args)
319
332
  end
320
-
333
+
321
334
  # Registers the default route (see #get).
322
335
  #
323
- def default(controller = nil, action = nil, &block)
324
- register_route('/', block, :get, controller, action)
336
+ def default(*args, &block)
337
+ register_route(:user, '/', block, :get, *args)
325
338
  end
326
-
327
- # Creates REST routes for a resource. Arguments: url, controller, model
339
+
340
+ # Creates REST routes for a resource. Arguments: url, controller, model, hooks
328
341
  #
329
- def restful(*args, &block)
330
- url, controller, model = args
331
-
342
+ def restful(url, controller, *args, &block)
343
+ model, hooks = parse_restful_args(args)
344
+
332
345
  with_scope(:url => url.gsub(/^[\/]+|[\/]+$/,""), :model => model) do
333
346
  nest_scope(&block) if block_given?
334
-
347
+
335
348
  @restful_routes ||= {}
336
349
  @restful_routes[model] ||= {} if model
337
-
350
+
338
351
  @@restful_actions.each do |opts|
339
- action_url = current_path
352
+ action_url = current_path
340
353
  if suffix = opts[:url_suffix]
341
354
  action_url = File.join(action_url, suffix)
342
355
  end
343
-
356
+
344
357
  # Create the route
345
- register_route(action_url, nil, opts[:method], controller, opts[:action], :restful)
346
-
358
+ register_route(:restful, action_url, nil, opts[:method], controller, opts[:action], hooks)
359
+
347
360
  # Store url for later use (currently used by Binder#action)
348
361
  @restful_routes[model][opts[:action]] = action_url if model
349
362
  end
350
-
363
+
351
364
  remove_scope
352
365
  end
353
366
  end
354
-
367
+
355
368
  @@restful_actions = [
356
369
  { :action => :edit, :method => :get, :url_suffix => 'edit/:id' },
357
370
  { :action => :show, :method => :get, :url_suffix => ':id' },
@@ -361,108 +374,196 @@ module Pakyow
361
374
  { :action => :index, :method => :get },
362
375
  { :action => :create, :method => :post }
363
376
  ]
364
-
377
+
378
+ def hook(name, controller = nil, action = nil, &block)
379
+ block = build_controller_block(controller, action) if controller
380
+ @route_store.add_hook(name, block)
381
+ end
382
+
383
+ def handler(name, *args, &block)
384
+ code, controller, action = parse_handler_args(args)
385
+
386
+ if block_given?
387
+ @handler_store[name] = block
388
+ else
389
+ @handler_store[name] = build_controller_block(controller, action)
390
+ end
391
+
392
+ if code
393
+ @handler_name_to_code[name] = code
394
+ @handler_code_to_name[code] = name
395
+ end
396
+ end
397
+
398
+ #TODO: don't like this...
399
+ def reload
400
+ load_app
401
+ end
402
+
365
403
  protected
366
-
367
- def interrupted?
368
- @interrupted
404
+
405
+ def prepare_route_block(route, method)
406
+ set_request_format_from_route(route)
407
+
408
+ controller_block, packet = @route_store.get_block(route, method)
409
+
410
+ self.request.params.merge!(HashUtils.strhash(packet[:vars]))
411
+ self.request.route_spec = packet[:data][:route_spec] if packet[:data]
412
+ self.request.restful = packet[:data][:restful] if packet[:data]
413
+
414
+ controller_block
369
415
  end
370
-
416
+
417
+ def trampoline(block)
418
+ last_call_has_block = (block == nil) ? false : true
419
+ while block do
420
+ block = catch(:new_block) {
421
+ block.call()
422
+ # Getting here means that call() returned normally (not via a throw)
423
+ :fall_through
424
+ } # end :invoke_route catch block
425
+ # If invoke_route! or invoke_handler! was called in the block, block will have a new value (nil or block).
426
+ # If neither was called, block will be :fall_through
427
+
428
+ if block == nil
429
+ last_call_has_block = false
430
+ elsif block == :fall_through
431
+ last_call_has_block = true
432
+ block = nil
433
+ end
434
+
435
+ if block && self.presenter
436
+ self.presenter.prepare_for_request(self.request)
437
+ end
438
+ end
439
+ last_call_has_block
440
+ end
441
+
442
+ def parse_route_args(args)
443
+ controller = args[0] if args[0] && (args[0].is_a?(Symbol) || args[0].is_a?(String))
444
+ action = args[1] if controller
445
+ hooks = args[2] if controller
446
+ unless controller
447
+ hooks = args[0] if args[0] && args[0].is_a?(Hash)
448
+ end
449
+ return controller, action, hooks
450
+ end
451
+
452
+ def parse_restful_args(args)
453
+ model = args[0] if args[0] && (args[0].is_a?(Symbol) || args[0].is_a?(String))
454
+ hooks = args[1] if model
455
+ unless model
456
+ hooks = args[0] if args[0] && args[0].is_a?(Hash)
457
+ end
458
+ return model, hooks
459
+ end
460
+
461
+ def parse_handler_args(args)
462
+ code = args[0] if args[0] && args[0].is_a?(Fixnum)
463
+ controller = args[1] if code && args[1]
464
+ action = args[2] if controller && args[2]
465
+ unless code
466
+ controller = args[0] if args[0]
467
+ action = args[1] if controller && args[1]
468
+ end
469
+ return code, controller, action
470
+ end
471
+
371
472
  # Handles route registration.
372
473
  #
373
- def register_route(route, block, method, controller = nil, action = nil, type = :user)
474
+ def register_route(type, route, block, method, *args)
475
+ controller, action, hooks = parse_route_args(args)
374
476
  if controller
375
- controller = eval(controller.to_s)
376
- action ||= Configuration::Base.app.default_action
377
-
378
- block = lambda {
379
- instance = controller.new
380
- request.controller = instance
381
- request.action = action
382
-
383
- instance.send(action)
384
- }
477
+ block = build_controller_block(controller, action)
385
478
  end
386
479
 
387
480
  data = {:route_type=>type, :route_spec=>route}
388
481
  if type == :restful
389
482
  data[:restful] = {:restful_action=>action}
390
483
  end
391
- @route_store.add_route(route, block, method, data)
484
+ @route_store.add_route(route, block, method, data, hooks)
392
485
  end
393
-
486
+
487
+ def build_controller_block(controller, action)
488
+ controller = eval(controller.to_s)
489
+ action ||= Configuration::Base.app.default_action
490
+
491
+ block = lambda {
492
+ instance = controller.new
493
+ request.controller = instance
494
+ request.action = action
495
+
496
+ instance.send(action)
497
+ }
498
+
499
+ block
500
+ end
501
+
502
+ def set_request_format_from_route(route)
503
+ route, format = StringUtils.split_at_last_dot(route)
504
+ self.request.format = ((format && (format[format.length - 1, 1] == '/')) ? format[0, format.length - 1] : format)
505
+ end
506
+
394
507
  def with_scope(opts)
395
508
  @scope ||= {}
396
509
  @scope[:path] ||= []
397
510
  @scope[:model] = opts[:model]
398
-
511
+
399
512
  @scope[:path] << opts[:url]
400
-
513
+
401
514
  yield
402
515
  end
403
-
516
+
404
517
  def remove_scope
405
518
  @scope[:path].pop
406
519
  end
407
-
520
+
408
521
  def nest_scope(&block)
409
522
  @scope[:path].insert(-1, ":#{StringUtils.underscore(@scope[:model].to_s)}_id")
410
523
  yield
411
524
  @scope[:path].pop
412
525
  end
413
-
526
+
414
527
  def current_path
415
528
  @scope[:path].join('/')
416
529
  end
417
-
418
- def handle_error(code)
419
- return unless self.class.error_handlers
420
- return unless handler = self.class.error_handlers[code]
421
-
422
- if handler.is_a? Proc
423
- Pakyow.app.instance_eval(&handler)
424
- else
425
- c = eval(handler[:controller].to_s).new
426
- c.send(handler[:action])
427
- end
428
-
429
- self.response.body = [self.presenter.content]
430
- end
431
-
530
+
432
531
  def set_cookies
433
532
  if self.request.cookies && self.request.cookies != {}
434
533
  self.request.cookies.each do |key, value|
435
- if value.is_a?(Hash)
534
+ if value.nil?
535
+ self.response.set_cookie(key, {:path => '/', :expires => Time.now + 604800 * -1 }.merge({:value => value}))
536
+ elsif value.is_a?(Hash)
436
537
  self.response.set_cookie(key, {:path => '/', :expires => Time.now + 604800}.merge(value))
437
- elsif value.is_a?(String)
438
- self.response.set_cookie(key, {:path => '/', :expires => Time.now + 604800}.merge({:value => value}))
439
538
  else
440
- self.response.set_cookie(key, {:path => '/', :expires => Time.now + 604800 * -1 }.merge({:value => value}))
539
+ self.response.set_cookie(key, {:path => '/', :expires => Time.now + 604800}.merge({:value => value}))
441
540
  end
442
541
  end
443
542
  end
444
543
  end
445
-
544
+
446
545
  # Reloads all application files in application_path and presenter (if specified).
447
546
  #
448
- def load_app
449
- return if @loaded && !Configuration::Base.app.auto_reload
450
- @loaded = true
451
-
452
- # Reload Application
453
- load(Configuration::App.application_path)
454
-
547
+ def load_app(reload_app = true)
548
+ load(Configuration::App.application_path) if reload_app
549
+
455
550
  @loader = Loader.new unless @loader
456
551
  @loader.load!(Configuration::Base.app.src_dir)
457
-
552
+
553
+ load_handlers
458
554
  load_routes
459
-
555
+
460
556
  # Reload views
461
- if Configuration::Base.app.presenter
462
- self.presenter.reload!
557
+ if self.presenter
558
+ self.presenter.load
463
559
  end
464
560
  end
465
-
561
+
562
+ def load_handlers
563
+ @handler_store = {}
564
+ self.instance_eval(&self.class.handlers_proc) if self.class.handlers_proc
565
+ end
566
+
466
567
  def load_routes
467
568
  @route_store = RouteStore.new
468
569
  self.instance_eval(&self.class.routes_proc) if self.class.routes_proc
@@ -471,13 +572,7 @@ module Pakyow
471
572
  # Send the response and cleanup.
472
573
  #
473
574
  def finish!
474
- @interrupted = false
475
- @static = false
476
-
477
- # Set cookies
478
575
  set_cookies
479
-
480
- # Finished
481
576
  self.response.finish
482
577
  end
483
578
 
@@ -6,7 +6,10 @@ module Pakyow
6
6
  autoload :Request, 'core/request'
7
7
  autoload :Loader, 'core/loader'
8
8
  autoload :Application, 'core/application'
9
- autoload :RouteStore, 'core/route_store'
9
+ autoload :RouteStore, 'core/route_store'
10
+ autoload :Logger, 'core/logger'
11
+ autoload :Static, 'core/static'
12
+ autoload :Reloader, 'core/reloader'
10
13
 
11
14
  # utils
12
15
  autoload :StringUtils, 'utils/string'
@@ -5,7 +5,7 @@ module Pakyow
5
5
  attr_accessor :dev_mode, :log, :public_dir, :root, :log_dir,
6
6
  :presenter, :default_action, :ignore_routes, :error_level,
7
7
  :default_environment, :application_path, :log_name, :src_dir,
8
- :auto_reload, :errors_in_browser
8
+ :auto_reload, :errors_in_browser, :static
9
9
 
10
10
  # Displays development-specific warnings.
11
11
  #
@@ -67,6 +67,15 @@ module Pakyow
67
67
  def application_path
68
68
  @application_path
69
69
  end
70
+
71
+ # Handle static files?
72
+ #
73
+ # For best performance, should be set to false if static files are
74
+ # handled by a web server (e.g. Nginx)
75
+ #
76
+ def static
77
+ @static || true
78
+ end
70
79
  end
71
80
  end
72
81
  end
@@ -7,15 +7,15 @@ module Pakyow
7
7
  def self.puts(text = "")
8
8
  return if !Configuration::Base.app.log
9
9
 
10
- @@console ||= Logger.new($stdout)
10
+ @@console ||= $stdout
11
11
  @@console << "#{text}\r\n"
12
12
 
13
13
  dir = "#{Configuration::Base.app.log_dir}"
14
14
 
15
15
  if File.exists?(dir)
16
- @@file ||= Logger.new("#{dir}/#{Configuration::Base.app.log_name}")
17
- @@file << "#{text}\r\n"
18
- end
16
+ @@file ||= File.open("#{dir}/#{Configuration::Base.app.log_name}", 'a')
17
+ @@file.write "#{text}\r\n"
18
+ end
19
19
  end
20
20
 
21
21
  class << self
@@ -0,0 +1,33 @@
1
+ module Pakyow
2
+ class Logger
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ began_at = Time.now
9
+
10
+ Log.enter "Processing #{env['PATH_INFO']} (#{env['REMOTE_ADDR']} at #{began_at}) [#{env['REQUEST_METHOD']}]"
11
+
12
+ result = nil
13
+
14
+ if error = catch(:error) {
15
+ result = @app.call(env)
16
+ nil
17
+ }
18
+ Log.enter "[500] #{error}\n"
19
+ Log.enter error.backtrace.join("\n") + "\n\n"
20
+
21
+ result = Pakyow.app.response.finish
22
+ end
23
+
24
+ ended_at = Time.now.to_f
25
+ difference = ((ended_at - began_at.to_f) * 1000).to_f
26
+
27
+ Log.enter "Completed in #{difference}ms | #{Pakyow.app.response.status} | [#{Pakyow.app.request.url}]"
28
+ Log.enter
29
+
30
+ result
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,12 @@
1
+ module Pakyow
2
+ class Reloader
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ @app.reload
9
+ @app.call(env)
10
+ end
11
+ end
12
+ end
@@ -2,7 +2,7 @@ module Pakyow
2
2
 
3
3
  # The Request object.
4
4
  class Request < Rack::Request
5
- attr_accessor :restful, :route_spec, :controller, :action, :format, :error
5
+ attr_accessor :restful, :route_spec, :controller, :action, :format, :error, :working_path, :working_method
6
6
 
7
7
  # Easy access to path_info.
8
8
  def path
@@ -5,24 +5,29 @@ module Pakyow
5
5
  @order = 1
6
6
  @store =
7
7
  {
8
- # string routes are stored, for each method, as a hash of 'route'=>{:block=>b,:order=>n}
8
+ # string routes are stored, for each method, as a hash of 'route'=>{:block=>b,:order=>n,:hooks=>{...}}}
9
9
  :string => {:get=>{}, :post=>{}, :put=>{}, :delete=>{}},
10
10
 
11
- # regex routes are stored, for each method, as an array of hashes {:regex=>r,:block=>b,:order=>n,:vars=>{n=>var}}
11
+ # regex routes are stored, for each method, as an array of hashes {:regex=>r,:block=>b,:order=>n,:vars=>{n=>var},:hooks=>{...}}
12
12
  # they are in definition order in the array
13
- :regex => {:get=>[], :post=>[], :put=>[], :delete=>[]}
13
+ :regex => {:get=>[], :post=>[], :put=>[], :delete=>[]},
14
14
 
15
15
  # :order is a global order across both string and regex routes.
16
+
17
+ # hooks are blocks stored by name and used to augment returned block from get_block
18
+ :hooks => {}
16
19
  }
17
20
  end
18
21
 
19
- def add_route(route_spec, block, method, data)
22
+ def add_route(route_spec, block, method, data, hooks)
20
23
  route_spec = normalize_route(route_spec)
24
+ hooks = normalize_hooks(hooks)
21
25
  route_to_match, vars = build_route_matcher(route_spec, data)
22
26
 
23
27
  if route_to_match.is_a?(String)
24
28
  @store[:string][method][route_to_match]={:block => block,
25
29
  :order => @order,
30
+ :hooks => hooks,
26
31
  :data => data}
27
32
  @order = @order + 1
28
33
  elsif route_to_match.is_a?(Regexp)
@@ -30,6 +35,7 @@ module Pakyow
30
35
  :block => block,
31
36
  :order => @order,
32
37
  :vars => vars,
38
+ :hooks => hooks,
33
39
  :data => data}
34
40
  @order = @order + 1
35
41
  else
@@ -43,6 +49,10 @@ module Pakyow
43
49
 
44
50
  end
45
51
 
52
+ def add_hook(name, hook)
53
+ @store[:hooks][name] = hook
54
+ end
55
+
46
56
  # returns block, {:vars=>{:var=>matched_value, ...}, :data=>data}
47
57
  def get_block(route, method)
48
58
  route = normalize_route(route)
@@ -63,25 +73,55 @@ module Pakyow
63
73
  if string_route_match && regex_route_match
64
74
  if string_route_match[:order] < regex_route_match[:order]
65
75
  data = string_route_match[:data]
66
- return string_route_match[:block], {:vars=>{}, :data=>data}
76
+ return build_hooked_block(string_route_match[:block], string_route_match[:hooks]),
77
+ {:vars=>{}, :data=>data}
67
78
  else
68
79
  data = regex_route_match[:data]
69
- return regex_route_match[:block], {:vars=>build_regex_var_values(regex_route_match[:vars], match_data), :data=>data}
80
+ return build_hooked_block(regex_route_match[:block], regex_route_match[:hooks]),
81
+ {:vars=>build_regex_var_values(regex_route_match[:vars], match_data), :data=>data}
70
82
  end
71
83
  elsif string_route_match
72
84
  data = string_route_match[:data]
73
- return string_route_match[:block], {:vars=>{}, :data=>data}
85
+ return build_hooked_block(string_route_match[:block], string_route_match[:hooks]),
86
+ {:vars=>{}, :data=>data}
74
87
  elsif regex_route_match
75
88
  data = regex_route_match[:data]
76
- return regex_route_match[:block], {:vars=>build_regex_var_values(regex_route_match[:vars], match_data), :data=>data}
89
+ return build_hooked_block(regex_route_match[:block], regex_route_match[:hooks]),
90
+ {:vars=>build_regex_var_values(regex_route_match[:vars], match_data), :data=>data}
77
91
  else
78
- return nil, {:vars=>{}, :data=>nil}
92
+ return nil,
93
+ {:vars=>{}, :data=>nil}
79
94
  end
80
95
 
81
96
  end
82
97
 
83
98
  private
84
99
 
100
+ def build_hooked_block(block, hooks)
101
+ unless hooks
102
+ return block
103
+ end
104
+ lambda {
105
+ hooks[:before].map { |h|
106
+ @store[:hooks][h].call() if @store[:hooks][h]
107
+ } if hooks && hooks[:before]
108
+
109
+ hooks[:around].map { |h|
110
+ @store[:hooks][h].call() if @store[:hooks][h]
111
+ } if hooks && hooks[:around]
112
+
113
+ block.call()
114
+
115
+ hooks[:around].map { |h|
116
+ @store[:hooks][h].call() if @store[:hooks][h]
117
+ } if hooks && hooks[:around]
118
+
119
+ hooks[:after].map { |h|
120
+ @store[:hooks][h].call() if @store[:hooks][h]
121
+ } if hooks && hooks[:after]
122
+ }
123
+ end
124
+
85
125
  # Returns a regex and an array of variable info
86
126
  def build_route_matcher(route_spec, data)
87
127
  return route_spec, [] if route_spec.is_a?(Regexp)
@@ -160,6 +200,14 @@ module Pakyow
160
200
  route_spec
161
201
  end
162
202
 
203
+ def normalize_hooks(hooks)
204
+ hooks.each_pair { |k,v|
205
+ unless v.is_a?(Array)
206
+ hooks[k] = [v]
207
+ end
208
+ } if hooks
209
+ end
210
+
163
211
  def build_regex_var_values(vars_info, match_data)
164
212
  var_values = {}
165
213
  vars_info.each { |vi|
@@ -0,0 +1,25 @@
1
+ module Pakyow
2
+ class Static
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ if is_static?(env)
9
+ Pakyow.app.response = Rack::Response.new
10
+
11
+ catch(:halt) do
12
+ Pakyow.app.send_file!(File.join(Configuration::Base.app.public_dir, env['PATH_INFO']))
13
+ end
14
+ else
15
+ @app.call(env)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def is_static?(env)
22
+ env['PATH_INFO'] =~ /\.(.*)$/ && File.exists?(File.join(Configuration::Base.app.public_dir, env['PATH_INFO']))
23
+ end
24
+ end
25
+ end
metadata CHANGED
@@ -1,54 +1,53 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: pakyow-core
3
- version: !ruby/object:Gem::Version
4
- hash: 113
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.7.0
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 6
9
- - 3
10
- - 1
11
- version: 0.6.3.1
12
6
  platform: ruby
13
- authors:
7
+ authors:
14
8
  - Bryan Powell
15
9
  - Bret Young
16
10
  autorequire:
17
11
  bindir: pakyow-core/bin
18
12
  cert_chain: []
19
-
20
- date: 2011-09-13 00:00:00 -05:00
21
- default_executable:
22
- dependencies:
23
- - !ruby/object:Gem::Dependency
13
+ date: 2011-11-19 00:00:00.000000000Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
24
16
  name: rack
25
- prerelease: false
26
- requirement: &id001 !ruby/object:Gem::Requirement
17
+ requirement: &70115565134460 !ruby/object:Gem::Requirement
27
18
  none: false
28
- requirements:
19
+ requirements:
29
20
  - - ~>
30
- - !ruby/object:Gem::Version
31
- hash: 9
32
- segments:
33
- - 1
34
- - 3
35
- version: "1.3"
21
+ - !ruby/object:Gem::Version
22
+ version: '1.3'
36
23
  type: :runtime
37
- version_requirements: *id001
24
+ prerelease: false
25
+ version_requirements: *70115565134460
26
+ - !ruby/object:Gem::Dependency
27
+ name: shoulda
28
+ requirement: &70115565133760 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '2.11'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *70115565133760
38
37
  description: pakyow-core
39
38
  email: bryan@metabahn.com
40
- executables:
39
+ executables:
41
40
  - pakyow
42
41
  extensions: []
43
-
44
42
  extra_rdoc_files: []
45
-
46
- files:
43
+ files:
47
44
  - pakyow-core/CHANGES
48
45
  - pakyow-core/README
49
46
  - pakyow-core/MIT-LICENSE
47
+ - pakyow-core/lib/commands/console.rb
50
48
  - pakyow-core/lib/commands/server.rb
51
49
  - pakyow-core/lib/commands/USAGE
50
+ - pakyow-core/lib/commands/USAGE-CONSOLE
52
51
  - pakyow-core/lib/commands/USAGE-NEW
53
52
  - pakyow-core/lib/commands/USAGE-SERVER
54
53
  - pakyow-core/lib/core/application.rb
@@ -59,15 +58,19 @@ files:
59
58
  - pakyow-core/lib/core/helpers.rb
60
59
  - pakyow-core/lib/core/loader.rb
61
60
  - pakyow-core/lib/core/log.rb
61
+ - pakyow-core/lib/core/logger.rb
62
62
  - pakyow-core/lib/core/presenter_base.rb
63
+ - pakyow-core/lib/core/reloader.rb
63
64
  - pakyow-core/lib/core/request.rb
64
65
  - pakyow-core/lib/core/route_store.rb
66
+ - pakyow-core/lib/core/static.rb
65
67
  - pakyow-core/lib/generators/pakyow/app/app_generator.rb
66
68
  - pakyow-core/lib/generators/pakyow/app/templates/app/lib/application_controller.rb
67
69
  - pakyow-core/lib/generators/pakyow/app/templates/app/views/main.html
68
70
  - pakyow-core/lib/generators/pakyow/app/templates/app/views/pakyow.html
69
71
  - pakyow-core/lib/generators/pakyow/app/templates/config/application.rb
70
72
  - pakyow-core/lib/generators/pakyow/app/templates/config.ru
73
+ - pakyow-core/lib/generators/pakyow/app/templates/logs/requests.log
71
74
  - pakyow-core/lib/generators/pakyow/app/templates/public/favicon.ico
72
75
  - pakyow-core/lib/generators/pakyow/app/templates/rakefile
73
76
  - pakyow-core/lib/generators/pakyow/app/templates/README
@@ -76,41 +79,29 @@ files:
76
79
  - pakyow-core/lib/utils/hash.rb
77
80
  - pakyow-core/lib/utils/string.rb
78
81
  - pakyow-core/bin/pakyow
79
- has_rdoc: true
80
82
  homepage: http://pakyow.com
81
83
  licenses: []
82
-
83
84
  post_install_message:
84
85
  rdoc_options: []
85
-
86
- require_paths:
86
+ require_paths:
87
87
  - pakyow-core/lib
88
- required_ruby_version: !ruby/object:Gem::Requirement
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
89
  none: false
90
- requirements:
91
- - - ">="
92
- - !ruby/object:Gem::Version
93
- hash: 57
94
- segments:
95
- - 1
96
- - 8
97
- - 7
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
98
93
  version: 1.8.7
99
- required_rubygems_version: !ruby/object:Gem::Requirement
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
95
  none: false
101
- requirements:
102
- - - ">="
103
- - !ruby/object:Gem::Version
104
- hash: 3
105
- segments:
106
- - 0
107
- version: "0"
96
+ requirements:
97
+ - - ! '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
108
100
  requirements: []
109
-
110
101
  rubyforge_project: pakyow-core
111
- rubygems_version: 1.6.2
102
+ rubygems_version: 1.8.10
112
103
  signing_key:
113
104
  specification_version: 3
114
105
  summary: pakyow-core
115
106
  test_files: []
116
-
107
+ has_rdoc: