pakyow-core 0.6.3.1 → 0.7.0

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.
@@ -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: