gin 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,16 @@
1
- === 1.0.2 / 2013-03-12
1
+ === 1.0.4 / 2013-03-19
2
+
3
+ * Minor Enhancements
4
+ * Gin no longer relies on Rack::CommonLogger
5
+ * Allow routes to default with a custom HTTP verb
6
+ * Gin::Controller#delete_cookie helper method
7
+
8
+ * Bugfixes
9
+ * Fix for reloading of required files
10
+ * Fix for routing HTTP verbs that don't have routes mounted to them
11
+ * Better check before extending the CGI class
12
+
13
+ === 1.0.3 / 2013-03-12
2
14
 
3
15
  * Minor Enhancements
4
16
  * Routes have priority over static assets
@@ -7,9 +7,11 @@ Rakefile
7
7
  lib/gin.rb
8
8
  lib/gin/app.rb
9
9
  lib/gin/config.rb
10
+ lib/gin/constants.rb
10
11
  lib/gin/controller.rb
11
12
  lib/gin/core_ext/cgi.rb
12
13
  lib/gin/core_ext/gin_class.rb
14
+ lib/gin/core_ext/rack_commonlogger.rb
13
15
  lib/gin/errorable.rb
14
16
  lib/gin/filterable.rb
15
17
  lib/gin/reloadable.rb
@@ -4,10 +4,8 @@
4
4
 
5
5
  == Description
6
6
 
7
- Gin is a small web framework built from the redistillation of
8
- Sinatra and Rails idioms. Specifically, it uses much of Sinatra's
9
- request flow and HTTP helper methods with dedicated controller classes
10
- that support Rails-like filters.
7
+ Gin is a small Ruby web framework, built on Rack, which borrows from
8
+ Sinatra expressiveness, and targets larger applications.
11
9
 
12
10
  == Hello World
13
11
 
data/lib/gin.rb CHANGED
@@ -1,8 +1,7 @@
1
- require 'logger'
2
1
  require 'rack'
3
2
 
4
3
  class Gin
5
- VERSION = '1.0.3'
4
+ VERSION = '1.0.4'
6
5
 
7
6
  LIB_DIR = File.expand_path("..", __FILE__) #:nodoc:
8
7
  PUBLIC_DIR = File.expand_path("../../public/", __FILE__) #:nodoc:
@@ -108,7 +107,9 @@ class Gin
108
107
 
109
108
  require 'gin/core_ext/cgi'
110
109
  require 'gin/core_ext/gin_class'
110
+ require 'gin/core_ext/rack_commonlogger'
111
111
 
112
+ require 'gin/constants'
112
113
  require 'gin/app'
113
114
  require 'gin/router'
114
115
  require 'gin/config'
@@ -13,21 +13,10 @@
13
13
 
14
14
  class Gin::App
15
15
  extend GinClass
16
+ include Gin::Constants
16
17
 
17
18
  class RouterError < Gin::Error; end
18
19
 
19
- RACK_KEYS = { #:nodoc:
20
- :stack => 'gin.stack'.freeze,
21
- :http_route => 'gin.http_route'.freeze,
22
- :path_params => 'gin.path_query_hash'.freeze,
23
- :controller => 'gin.controller'.freeze,
24
- :action => 'gin.action'.freeze,
25
- :static => 'gin.static'.freeze,
26
- :reloaded => 'gin.reloaded'.freeze,
27
- :errors => 'gin.errors'.freeze
28
- }.freeze
29
-
30
-
31
20
  CALLERS_TO_IGNORE = [ # :nodoc:
32
21
  /\/gin(\/(.*?))?\.rb$/, # all gin code
33
22
  /lib\/tilt.*\.rb$/, # all tilt code
@@ -76,7 +65,7 @@ class Gin::App
76
65
  end
77
66
 
78
67
  if @autoreload && (!defined?(Gin::Reloadable) || !include?(Gin::Reloadable))
79
- require 'gin/reloadable'
68
+ Object.send :require, 'gin/reloadable'
80
69
  include Gin::Reloadable
81
70
  end
82
71
 
@@ -84,6 +73,18 @@ class Gin::App
84
73
  end
85
74
 
86
75
 
76
+ ##
77
+ # Custom require used for auto-reloading.
78
+
79
+ def self.require file
80
+ if autoreload
81
+ track_require file
82
+ else
83
+ super file
84
+ end
85
+ end
86
+
87
+
87
88
  ##
88
89
  # Mount a Gin::Controller into the App and specify a base path. If controller
89
90
  # mounts at root, use "/" as the base path.
@@ -352,7 +353,7 @@ class Gin::App
352
353
 
353
354
  def self.environment env=nil
354
355
  @environment = env if env
355
- @environment ||= ENV['RACK_ENV'] || "development"
356
+ @environment ||= ENV['RACK_ENV'] || ENV_DEV
356
357
  end
357
358
 
358
359
 
@@ -360,7 +361,7 @@ class Gin::App
360
361
  # Check if running in development mode.
361
362
 
362
363
  def self.development?
363
- self.environment == "development"
364
+ self.environment == ENV_DEV
364
365
  end
365
366
 
366
367
 
@@ -368,7 +369,7 @@ class Gin::App
368
369
  # Check if running in test mode.
369
370
 
370
371
  def self.test?
371
- self.environment == "test"
372
+ self.environment == ENV_TEST
372
373
  end
373
374
 
374
375
 
@@ -376,7 +377,7 @@ class Gin::App
376
377
  # Check if running in staging mode.
377
378
 
378
379
  def self.staging?
379
- self.environment == "staging"
380
+ self.environment == ENV_STAGE
380
381
  end
381
382
 
382
383
 
@@ -384,7 +385,7 @@ class Gin::App
384
385
  # Check if running in production mode.
385
386
 
386
387
  def self.production?
387
- self.environment == "production"
388
+ self.environment == ENV_PROD
388
389
  end
389
390
 
390
391
 
@@ -412,12 +413,12 @@ class Gin::App
412
413
  def initialize rack_app=nil, logger=nil
413
414
  load_config
414
415
 
415
- if !rack_app.respond_to?(:call) && rack_app.respond_to?(:log) && logger.nil?
416
+ if !rack_app.respond_to?(:call) && rack_app.respond_to?(:<<) && logger.nil?
416
417
  @rack_app = nil
417
418
  @logger = rack_app
418
419
  else
419
420
  @rack_app = rack_app
420
- @logger = Logger.new $stdout
421
+ @logger = $stdout
421
422
  end
422
423
 
423
424
  validate_all_controllers!
@@ -434,7 +435,6 @@ class Gin::App
434
435
  # If you use this in production, you're gonna have a bad time.
435
436
 
436
437
  def reload!
437
- return unless autoreload
438
438
  @mutex ||= Mutex.new
439
439
 
440
440
  @mutex.synchronize do
@@ -443,7 +443,7 @@ class Gin::App
443
443
  self.class.namespace
444
444
 
445
445
  self.class.erase_dependencies!
446
- Object.send(:require, self.class.source_file)
446
+ require self.class.source_file
447
447
  @app = self.class.source_class.new @rack_app, @logger
448
448
  end
449
449
  end
@@ -474,8 +474,8 @@ class Gin::App
474
474
  # Check if autoreload is needed and reload.
475
475
 
476
476
  def try_autoreload env
477
- return if env[RACK_KEYS[:reloaded]]
478
- env[RACK_KEYS[:reloaded]] = true
477
+ return if env[GIN_RELOADED] || !autoreload
478
+ env[GIN_RELOADED] = true
479
479
  reload!
480
480
  end
481
481
 
@@ -484,13 +484,14 @@ class Gin::App
484
484
  # Call App instance stack without static file lookup or reloading.
485
485
 
486
486
  def call! env
487
- if env[RACK_KEYS[:stack]]
488
- env.delete RACK_KEYS[:stack]
489
- dispatch env, env[RACK_KEYS[:controller]], env[RACK_KEYS[:action]]
487
+ if env[GIN_STACK]
488
+ dispatch env, env[GIN_CTRL], env[GIN_ACTION]
490
489
 
491
490
  else
492
- env[RACK_KEYS[:stack]] = true
493
- @stack.call env
491
+ env[GIN_STACK] = true
492
+ with_log_request(env) do
493
+ @stack.call env
494
+ end
494
495
  end
495
496
  end
496
497
 
@@ -500,7 +501,9 @@ class Gin::App
500
501
  # env filename.
501
502
 
502
503
  def call_static env
503
- error_delegate.exec(self, env){ send_file env[RACK_KEYS[:static]] }
504
+ with_log_request(env) do
505
+ error_delegate.exec(self, env){ send_file env[GIN_STATIC] }
506
+ end
504
507
  end
505
508
 
506
509
 
@@ -509,13 +512,13 @@ class Gin::App
509
512
  # variable to the filepath.
510
513
 
511
514
  def static! env
512
- filepath = %w{GET HEAD}.include?(env['REQUEST_METHOD']) &&
513
- asset(env['PATH_INFO'])
515
+ filepath = %w{GET HEAD}.include?(env[REQ_METHOD]) &&
516
+ asset(env[PATH_INFO])
514
517
 
515
- filepath ? (env[RACK_KEYS[:static]] = filepath) :
516
- env.delete(RACK_KEYS[:static])
518
+ filepath ? (env[GIN_STATIC] = filepath) :
519
+ env.delete(GIN_STATIC)
517
520
 
518
- !!env[RACK_KEYS[:static]]
521
+ !!env[GIN_STATIC]
519
522
  end
520
523
 
521
524
 
@@ -525,15 +528,15 @@ class Gin::App
525
528
  # and gin.http_route env variables.
526
529
 
527
530
  def route! env
528
- http_route = "#{env['REQUEST_METHOD']} #{env['PATH_INFO']}"
529
- return true if env[RACK_KEYS[:http_route]] == http_route
531
+ http_route = "#{env[REQ_METHOD]} #{env[PATH_INFO]}"
532
+ return true if env[GIN_ROUTE] == http_route
530
533
 
531
- env[RACK_KEYS[:controller]], env[RACK_KEYS[:action]], env[RACK_KEYS[:path_params]] =
532
- router.resources_for env['REQUEST_METHOD'], env['PATH_INFO']
534
+ env[GIN_CTRL], env[GIN_ACTION], env[GIN_PATH_PARAMS] =
535
+ router.resources_for env[REQ_METHOD], env[PATH_INFO]
533
536
 
534
- env[RACK_KEYS[:http_route]] = http_route
537
+ env[GIN_ROUTE] = http_route
535
538
 
536
- !!(env[RACK_KEYS[:controller]] && env[RACK_KEYS[:action]])
539
+ !!(env[GIN_CTRL] && env[GIN_ACTION])
537
540
  end
538
541
 
539
542
 
@@ -560,7 +563,7 @@ class Gin::App
560
563
 
561
564
  def dispatch env, ctrl, action
562
565
  raise Gin::NotFound,
563
- "No route exists for: #{env['REQUEST_METHOD']} #{env['PATH_INFO']}" unless
566
+ "No route exists for: #{env[REQ_METHOD]} #{env[PATH_INFO]}" unless
564
567
  ctrl && action
565
568
 
566
569
  ctrl.new(self, env).call_action action
@@ -577,8 +580,6 @@ class Gin::App
577
580
  delegate = error_delegate
578
581
 
579
582
  begin
580
- trace = Gin.app_trace(Array(err.backtrace)).join("\n")
581
- logger.error("#{err.class.name}: #{err.message}\n#{trace}")
582
583
  delegate.exec(self, env){ handle_error(err) }
583
584
 
584
585
  rescue ::Exception => err
@@ -591,6 +592,39 @@ class Gin::App
591
592
  private
592
593
 
593
594
 
595
+ LOG_FORMAT = %{%s - %s [%s] "%s %s%s" %s %d %s %0.4f %s\n}.freeze #:nodoc:
596
+ TIME_FORMAT = "%d/%b/%Y %H:%M:%S".freeze #:nodoc:
597
+
598
+ def log_request env, resp
599
+ now = Time.now
600
+ time = now - env[GIN_TIMESTAMP] if env[GIN_TIMESTAMP]
601
+
602
+ ctrl, action = env[GIN_CTRL], env[GIN_ACTION]
603
+ target = "#{ctrl}##{action}" if ctrl && action
604
+
605
+ @logger << ( LOG_FORMAT % [
606
+ env[FWD_FOR] || env[REMOTE_ADDR] || "-",
607
+ env[REMOTE_USER] || "-",
608
+ now.strftime(TIME_FORMAT),
609
+ env[REQ_METHOD],
610
+ env[PATH_INFO],
611
+ env[QUERY_STRING].to_s.empty? ? "" : "?#{env[QUERY_STRING]}",
612
+ env[HTTP_VERSION] || "HTTP/1.1",
613
+ resp[0],
614
+ resp[1][CNT_LENGTH] || "-",
615
+ time || "-",
616
+ target || "-" ] )
617
+ end
618
+
619
+
620
+ def with_log_request env
621
+ env[GIN_TIMESTAMP] ||= Time.now
622
+ resp = yield
623
+ log_request env, resp
624
+ resp
625
+ end
626
+
627
+
594
628
  def build_app builder
595
629
  setup_sessions builder
596
630
  setup_protection builder
@@ -0,0 +1,47 @@
1
+ module Gin::Constants
2
+ EPOCH = Time.at(0)
3
+
4
+ # Rack env constants
5
+ FWD_FOR = 'HTTP_X_FORWARDED_FOR'.freeze
6
+ FWD_HOST = 'HTTP_X_FORWARDED_HOST'.freeze
7
+ REMOTE_ADDR = 'REMOTE_ADDR'.freeze
8
+ REMOTE_USER = 'REMOTE_USER'.freeze
9
+ HTTP_VERSION = 'HTTP_VERSION'.freeze
10
+ REQ_METHOD = 'REQUEST_METHOD'.freeze
11
+ PATH_INFO = 'PATH_INFO'.freeze
12
+ QUERY_STRING = 'QUERY_STRING'.freeze
13
+ IF_MATCH = 'HTTP_IF_MATCH'.freeze
14
+ IF_NONE_MATCH = 'HTTP_IF_NONE_MATCH'.freeze
15
+ IF_MOD_SINCE = 'HTTP_IF_MODIFIED_SINCE'.freeze
16
+ IF_UNMOD_SINCE = 'HTTP_IF_UNMODIFIED_SINCE'.freeze
17
+
18
+ ASYNC_CALLBACK = 'async.callback'.freeze
19
+
20
+ # Rack response header constants
21
+ ETAG = 'ETag'.freeze
22
+ CNT_LENGTH = 'Content-Length'.freeze
23
+ CNT_TYPE = 'Content-Type'.freeze
24
+ CNT_DISPOSITION = 'Content-Disposition'.freeze
25
+ LOCATION = 'Location'.freeze
26
+ LAST_MOD = 'Last-Modified'.freeze
27
+ CACHE_CTRL = 'Cache-Control'.freeze
28
+ EXPIRES = 'Expires'.freeze
29
+ PRAGMA = 'Pragma'.freeze
30
+
31
+ # Gin env constants
32
+ GIN_STACK = 'gin.stack'.freeze
33
+ GIN_ROUTE = 'gin.http_route'.freeze
34
+ GIN_PATH_PARAMS = 'gin.path_query_hash'.freeze
35
+ GIN_CTRL = 'gin.controller'.freeze
36
+ GIN_ACTION = 'gin.action'.freeze
37
+ GIN_STATIC = 'gin.static'.freeze
38
+ GIN_RELOADED = 'gin.reloaded'.freeze
39
+ GIN_ERRORS = 'gin.errors'.freeze
40
+ GIN_TIMESTAMP = 'gin.timestamp'.freeze
41
+
42
+ # Environment names
43
+ ENV_DEV = "development".freeze
44
+ ENV_TEST = "test".freeze
45
+ ENV_STAGE = "staging".freeze
46
+ ENV_PROD = "production".freeze
47
+ end
@@ -1,10 +1,15 @@
1
1
  class Gin::Controller
2
2
  extend GinClass
3
+ include Gin::Constants
3
4
  include Gin::Filterable
4
5
  include Gin::Errorable
5
6
 
6
7
 
7
8
  error Gin::NotFound, Gin::BadRequest, ::Exception do |err|
9
+ trace = Gin.app_trace(Array(err.backtrace)).join(" \n")
10
+ trace = " " << trace << "\n\n" unless trace.empty?
11
+ logger << "[ERROR] #{err.class.name}: #{err.message}\n#{trace}"
12
+
8
13
  status( err.respond_to?(:http_status) ? err.http_status : 500 )
9
14
  @response.headers.clear
10
15
  content_type :html
@@ -71,8 +76,7 @@ class Gin::Controller
71
76
  def call_action action #:nodoc:
72
77
  invoke{ dispatch action }
73
78
  invoke{ handle_status(@response.status) }
74
- content_type self.class.content_type unless
75
- @response[Gin::Response::H_CTYPE]
79
+ content_type self.class.content_type unless @response[CNT_TYPE]
76
80
  @response.finish
77
81
  end
78
82
 
@@ -107,7 +111,7 @@ class Gin::Controller
107
111
  # Get or set the HTTP response Content-Type header.
108
112
 
109
113
  def content_type type=nil, params={}
110
- return @response[Gin::Response::H_CTYPE] unless type
114
+ return @response[CNT_TYPE] unless type
111
115
 
112
116
  default = params.delete(:default)
113
117
  mime_type = mime_type(type) || default
@@ -127,7 +131,7 @@ class Gin::Controller
127
131
  end.join(', ')
128
132
  end
129
133
 
130
- @response[Gin::Response::H_CTYPE] = mime_type
134
+ @response[CNT_TYPE] = mime_type
131
135
  end
132
136
 
133
137
 
@@ -175,15 +179,15 @@ class Gin::Controller
175
179
 
176
180
  value = '"%s"' % value
177
181
  value = 'W/' + value if kind == :weak
178
- @response['ETag'] = value
182
+ @response[ETAG] = value
179
183
 
180
184
  if (200..299).include?(status) || status == 304
181
- if etag_matches? @env['HTTP_IF_NONE_MATCH'], new_resource
185
+ if etag_matches? @env[IF_NONE_MATCH], new_resource
182
186
  halt(@request.safe? ? 304 : 412)
183
187
  end
184
188
 
185
- if @env['HTTP_IF_MATCH']
186
- halt 412 unless etag_matches? @env['HTTP_IF_MATCH'], new_resource
189
+ if @env[IF_MATCH]
190
+ halt 412 unless etag_matches? @env[IF_MATCH], new_resource
187
191
  end
188
192
  end
189
193
  end
@@ -191,7 +195,7 @@ class Gin::Controller
191
195
 
192
196
  def etag_matches? list, new_resource=@request.post? #:nodoc:
193
197
  return !new_resource if list == '*'
194
- list.to_s.split(/\s*,\s*/).include? response['ETag']
198
+ list.to_s.split(/\s*,\s*/).include? response[ETAG]
195
199
  end
196
200
 
197
201
 
@@ -216,7 +220,7 @@ class Gin::Controller
216
220
  # end
217
221
 
218
222
  def stream keep_open=false, &block
219
- scheduler = env['async.callback'] ? EventMachine : Gin::Stream
223
+ scheduler = env[ASYNC_CALLBACK] ? EventMachine : Gin::Stream
220
224
  body Gin::Stream.new(scheduler, keep_open){ |out| yield(out) }
221
225
  end
222
226
 
@@ -270,6 +274,15 @@ class Gin::Controller
270
274
  end
271
275
 
272
276
 
277
+ ##
278
+ # Delete the response cookie with the given name.
279
+ # Does not affect request cookies.
280
+
281
+ def delete_cookie name
282
+ @response.delete_cookie name
283
+ end
284
+
285
+
273
286
  ##
274
287
  # Build a path to the given controller and action or route name,
275
288
  # with any expected params. If no controller is specified and the
@@ -344,13 +357,13 @@ class Gin::Controller
344
357
  # redirect to(:show_foo, :id => 123)
345
358
 
346
359
  def redirect uri, *args
347
- if @env['HTTP_VERSION'] == 'HTTP/1.1' && @env["REQUEST_METHOD"] != 'GET'
360
+ if @env[HTTP_VERSION] == 'HTTP/1.1' && @env[REQ_METHOD] != 'GET'
348
361
  status 303
349
362
  else
350
363
  status 302
351
364
  end
352
365
 
353
- @response['Location'] = url_to(uri.to_s)
366
+ @response[LOCATION] = url_to(uri.to_s)
354
367
  halt(*args)
355
368
  end
356
369
 
@@ -360,7 +373,7 @@ class Gin::Controller
360
373
  # Produces a 404 response if no file is found.
361
374
 
362
375
  def send_file path, opts={}
363
- if opts[:type] || !@response[Gin::Response::H_CTYPE]
376
+ if opts[:type] || !@response[CNT_TYPE]
364
377
  content_type opts[:type] || File.extname(path),
365
378
  :default => 'application/octet-stream'
366
379
  end
@@ -371,14 +384,14 @@ class Gin::Controller
371
384
  filename = File.basename(path) if filename.nil?
372
385
 
373
386
  if disposition
374
- @response['Content-Disposition'] =
387
+ @response[CNT_DISPOSITION] =
375
388
  "%s; filename=\"%s\"" % [disposition, filename]
376
389
  end
377
390
 
378
391
  last_modified opts[:last_modified] || File.mtime(path).httpdate
379
392
  halt 200 if @request.head?
380
393
 
381
- @response['Content-Length'] = File.size?(path).to_s
394
+ @response[CNT_LENGTH] = File.size?(path).to_s
382
395
  halt 200, File.open(path, "rb")
383
396
 
384
397
  rescue Errno::ENOENT
@@ -398,20 +411,20 @@ class Gin::Controller
398
411
  time = Time.parse(time) if String === time
399
412
  time = time.to_time if time.respond_to?(:to_time)
400
413
 
401
- @response['Last-Modified'] = time.httpdate
402
- return if @env['HTTP_IF_NONE_MATCH']
414
+ @response[LAST_MOD] = time.httpdate
415
+ return if @env[IF_NONE_MATCH]
403
416
 
404
- if status == 200 && @env['HTTP_IF_MODIFIED_SINCE']
417
+ if status == 200 && @env[IF_MOD_SINCE]
405
418
  # compare based on seconds since epoch
406
- since = Time.httpdate(@env['HTTP_IF_MODIFIED_SINCE']).to_i
419
+ since = Time.httpdate(@env[IF_MOD_SINCE]).to_i
407
420
  halt 304 if since >= time.to_i
408
421
  end
409
422
 
410
- if @env['HTTP_IF_UNMODIFIED_SINCE'] &&
423
+ if @env[IF_UNMOD_SINCE] &&
411
424
  ((200..299).include?(status) || status == 412)
412
425
 
413
426
  # compare based on seconds since epoch
414
- since = Time.httpdate(@env['HTTP_IF_UNMODIFIED_SINCE']).to_i
427
+ since = Time.httpdate(@env[IF_UNMOD_SINCE]).to_i
415
428
  halt 412 if since < time.to_i
416
429
  end
417
430
  rescue ArgumentError
@@ -442,7 +455,7 @@ class Gin::Controller
442
455
  values << [key, value].join('=')
443
456
  end
444
457
 
445
- @response['Cache-Control'] = values.join(', ') if values.any?
458
+ @response[CACHE_CTRL] = values.join(', ') if values.any?
446
459
  end
447
460
 
448
461
 
@@ -470,7 +483,7 @@ class Gin::Controller
470
483
  values.last.merge!(:max_age => max_age) unless values.last[:max_age]
471
484
  cache_control(*values)
472
485
 
473
- @response['Expires'] = time.httpdate
486
+ @response[EXPIRES] = time.httpdate
474
487
  end
475
488
 
476
489
 
@@ -479,9 +492,8 @@ class Gin::Controller
479
492
  # not to cache the response.
480
493
 
481
494
  def expire_cache_control
482
- @response['Pragma'] = 'no-cache'
483
- expires Time.new("1990","01","01"),
484
- :no_cache, :no_store, :must_revalidate, max_age: 0
495
+ @response[PRAGMA] = 'no-cache'
496
+ expires EPOCH, :no_cache, :no_store, :must_revalidate, max_age: 0
485
497
  end
486
498
 
487
499
 
@@ -579,7 +591,7 @@ class Gin::Controller
579
591
  private
580
592
 
581
593
 
582
- DEV_ERROR_HTML = File.read(File.join(Gin::PUBLIC_DIR, "error.html")) #:nodoc:
594
+ DEV_ERROR_HTML = File.read(File.join(Gin::PUBLIC_DIR, "error.html")).freeze #:nodoc:
583
595
 
584
596
  BAD_REQ_MSG = "Expected param `%s'" #:nodoc:
585
597
 
@@ -1,6 +1,6 @@
1
1
  require 'cgi'
2
2
 
3
- unless RUBY_VERSION >= "2.0"
3
+ unless CGI.escapeHTML("'") == "&#39;"
4
4
 
5
5
  class CGI #:nodoc:
6
6
  class << self
@@ -8,7 +8,7 @@ class CGI #:nodoc:
8
8
  end
9
9
 
10
10
  def self.escapeHTML str
11
- __escapeHTML(str).gsub!("'", "&#39;")
11
+ __escapeHTML(str).gsub("'", "&#39;")
12
12
  end
13
13
  end
14
14
 
@@ -0,0 +1,7 @@
1
+ class Rack::CommonLogger
2
+ alias log_without_check log unless method_defined? :log_without_check
3
+
4
+ def log env, *args
5
+ log_without_check(env, *args) unless env[Gin::Constants::GIN_TIMESTAMP]
6
+ end
7
+ end
@@ -89,7 +89,7 @@ module Gin::Errorable
89
89
  # Re-raises the error if no handler is found.
90
90
 
91
91
  def handle_error err
92
- (@env[Gin::App::RACK_KEYS[:errors]] ||= []) << err
92
+ (@env[Gin::Constants::GIN_ERRORS] ||= []) << err
93
93
  status(err.http_status) if err.respond_to?(:http_status)
94
94
  status(500) unless (400..599).include? status
95
95
 
@@ -13,7 +13,7 @@ module Gin::Reloadable #:nodoc:
13
13
 
14
14
 
15
15
  def erase_dependencies!
16
- reloadables.each do |key, (path, files, consts)|
16
+ reloadables.each do |key, (_, files, consts)|
17
17
  erase! files, consts
18
18
  end
19
19
  true
@@ -21,26 +21,45 @@ module Gin::Reloadable #:nodoc:
21
21
 
22
22
 
23
23
  def erase! files, consts, parent=nil
24
- parent ||= Object
24
+ parent ||= ::Object
25
25
  files.each{|f| $LOADED_FEATURES.delete f }
26
26
  clear_constants parent, consts
27
27
  end
28
28
 
29
29
 
30
- def clear_constants parent, names=nil
31
- names ||= parent.constants
30
+ def clear_constants root, names=nil
31
+ each_constant(root, names) do |name, const, parent|
32
+ if Module === const || !gin_constants[const.object_id]
33
+ parent.send(:remove_const, name) rescue nil
34
+ end
35
+ end
36
+ end
32
37
 
38
+
39
+ def gin_constants
40
+ return @gin_constants if @gin_constants
41
+ @gin_constants = {Gin.object_id => ::Gin}
42
+
43
+ each_constant(Gin) do |name, const, _|
44
+ @gin_constants[const.object_id] = const
45
+ end
46
+
47
+ @gin_constants
48
+ end
49
+
50
+
51
+ def each_constant parent, names=nil, &block
52
+ names ||= parent.constants
33
53
  names.each do |name|
34
54
  const = parent.const_get(name)
35
55
  next unless const
36
56
 
37
- if Class === const
38
- next unless parent == Object || const.name =~ /(^|::)#{parent.name}::/
39
- clear_constants const
40
- parent.send(:remove_const, name)
41
- else
42
- parent.send(:remove_const, name) rescue nil
57
+ if ::Module === const
58
+ next unless parent == ::Object || const.name =~ /(^|::)#{parent.name}::/
59
+ each_constant(const, &block)
43
60
  end
61
+
62
+ block.call name, const, parent
44
63
  end
45
64
  end
46
65
 
@@ -55,36 +74,27 @@ module Gin::Reloadable #:nodoc:
55
74
 
56
75
 
57
76
  def track_require file
58
- old_consts = Object.constants
77
+ old_consts = ::Object.constants
59
78
  old_features = $LOADED_FEATURES.dup
60
79
 
61
80
  filepath = Gin.find_loadpath file
62
81
 
63
82
  if !reloadables[filepath]
64
- success = Object.send(:require, file)
83
+ success = ::Object.send(:require, file)
65
84
 
66
85
  else reloadables[filepath]
67
86
  without_warnings{
68
- success = Object.send(:require, file)
87
+ success = ::Object.send(:require, file)
69
88
  }
70
89
  end
71
90
 
72
91
  reloadables[filepath] = [
73
92
  file,
74
93
  $LOADED_FEATURES - old_features,
75
- Object.constants - old_consts
94
+ ::Object.constants - old_consts
76
95
  ] if success
77
96
 
78
97
  success
79
98
  end
80
-
81
-
82
- def require file
83
- if autoreload
84
- track_require file
85
- else
86
- super file
87
- end
88
- end
89
99
  end
90
100
  end
@@ -1,14 +1,14 @@
1
1
  class Gin::Request < Rack::Request
2
+ include Gin::Constants
2
3
 
3
4
  def initialize env
4
5
  super
5
- self.params.update env[Gin::App::RACK_KEYS[:path_params]] if
6
- env[Gin::App::RACK_KEYS[:path_params]]
6
+ self.params.update env[GIN_PATH_PARAMS] if env[GIN_PATH_PARAMS]
7
7
  end
8
8
 
9
9
 
10
10
  def forwarded?
11
- @env.include? "HTTP_X_FORWARDED_HOST"
11
+ @env.include? FWD_HOST
12
12
  end
13
13
 
14
14
 
@@ -28,12 +28,7 @@ class Gin::Request < Rack::Request
28
28
 
29
29
 
30
30
  def params
31
- unless @params
32
- super
33
- @params = process_params @params
34
- end
35
-
36
- @params
31
+ @params ||= process_params(super) || {}
37
32
  end
38
33
 
39
34
 
@@ -1,8 +1,7 @@
1
1
  class Gin::Response < Rack::Response
2
+ include Gin::Constants
2
3
 
3
4
  NO_HEADER_STATUSES = [100, 101, 204, 205, 304].freeze #:nodoc:
4
- H_CTYPE = "Content-Type".freeze #:nodoc:
5
- H_CLENGTH = "Content-Length".freeze #:nodoc:
6
5
 
7
6
  attr_accessor :status
8
7
  attr_reader :body
@@ -16,11 +15,11 @@ class Gin::Response < Rack::Response
16
15
 
17
16
  def finish
18
17
  body_out = body
19
- header[H_CTYPE] ||= 'text/html;charset=UTF-8'
18
+ header[CNT_TYPE] ||= 'text/html;charset=UTF-8'
20
19
 
21
20
  if NO_HEADER_STATUSES.include?(status.to_i)
22
- header.delete H_CTYPE
23
- header.delete H_CLENGTH
21
+ header.delete CNT_TYPE
22
+ header.delete CNT_LENGTH
24
23
 
25
24
  if status.to_i > 200
26
25
  close
@@ -37,14 +36,14 @@ class Gin::Response < Rack::Response
37
36
  private
38
37
 
39
38
  def update_content_length
40
- if header[H_CTYPE] && !header[H_CLENGTH]
39
+ if header[CNT_TYPE] && !header[CNT_LENGTH]
41
40
  case body
42
41
  when Array
43
- header[H_CLENGTH] = body.inject(0) do |l, p|
44
- l + Rack::Utils.bytesize(p)
45
- end.to_s
42
+ header[CNT_LENGTH] = body.inject(0) do |l, p|
43
+ l + Rack::Utils.bytesize(p)
44
+ end.to_s
46
45
  when File
47
- header[H_CLENGTH] = body.size.to_s
46
+ header[CNT_LENGTH] = body.size.to_s
48
47
  end
49
48
  end
50
49
  end
@@ -33,10 +33,12 @@ class Gin::Router
33
33
 
34
34
 
35
35
  # Create restful routes if they aren't taken already.
36
- def defaults restful_only=false
36
+ def defaults default_verb=nil
37
+ default_verb = (default_verb || 'get').to_s.downcase
38
+
37
39
  (@ctrl.actions - @actions).each do |action|
38
40
  verb, path = DEFAULT_ACTION_MAP[action]
39
- verb, path = ['get', "/#{action}"] if !restful_only && verb.nil?
41
+ verb, path = [default_verb, "/#{action}"] if verb.nil?
40
42
 
41
43
  add(verb, action, path) unless verb.nil? ||
42
44
  @routes.any?{|(r,n,(c,a,p))| r == make_route(verb, path)[0] }
@@ -194,6 +196,7 @@ class Gin::Router
194
196
  def resources_for http_verb, path
195
197
  param_vals = []
196
198
  curr_node = @routes_tree[http_verb.to_s.downcase]
199
+ return unless curr_node
197
200
 
198
201
  path.scan(%r{/([^/]+|$)}) do |(key)|
199
202
  next if key.empty?
@@ -87,8 +87,8 @@ class AppTest < Test::Unit::TestCase
87
87
  FooApp.instance_variable_set("@autoreload", nil)
88
88
 
89
89
  @error_io = StringIO.new
90
- @app = FooApp.new Logger.new(@error_io)
91
- @rapp = FooApp.new lambda{|env| [200,{'Content-Type'=>'text/html'},["HI"]]}
90
+ @app = FooApp.new @error_io
91
+ @rapp = FooApp.new lambda{|env| [200,{'Content-Type'=>'text/html'},["HI"]]}, @error_io
92
92
  end
93
93
 
94
94
 
@@ -242,7 +242,7 @@ class AppTest < Test::Unit::TestCase
242
242
  assert_equal [FooMiddleware, :foo, :bar], FooApp.middleware[0]
243
243
  assert !FooMiddleware.called?
244
244
 
245
- myapp = FooApp.new
245
+ myapp = FooApp.new @error_io
246
246
  myapp.call({'rack.input' => "", 'PATH_INFO' => '/foo', 'REQUEST_METHOD' => 'GET'})
247
247
  assert FooMiddleware.called?
248
248
 
@@ -254,7 +254,7 @@ class AppTest < Test::Unit::TestCase
254
254
 
255
255
  def test_call_reload
256
256
  FooApp.autoreload true
257
- myapp = FooApp.new
257
+ myapp = FooApp.new @error_io
258
258
 
259
259
  assert !myapp.reloaded?
260
260
  myapp.call 'rack.input' => "", 'PATH_INFO' => '/foo', 'REQUEST_METHOD' => 'GET'
@@ -277,7 +277,7 @@ class AppTest < Test::Unit::TestCase
277
277
  env = {'rack.input' => "", 'PATH_INFO' => '/bad', 'REQUEST_METHOD' => 'GET'}
278
278
  expected = [200, {'Content-Length'=>"5"}, "AHOY!"]
279
279
  myapp = lambda{|env| expected }
280
- @app = FooApp.new myapp
280
+ @app = FooApp.new myapp, @error_io
281
281
 
282
282
  resp = @app.call env
283
283
  assert_equal expected, resp
@@ -330,7 +330,7 @@ class AppTest < Test::Unit::TestCase
330
330
  assert_equal 'text/html;charset=UTF-8', resp[1]['Content-Type']
331
331
  assert_equal @app.asset("404.html"), resp[2].path
332
332
 
333
- msg = "ERROR -- : Gin::NotFound: No route exists for: GET /foo"
333
+ msg = "[ERROR] Gin::NotFound: No route exists for: GET /foo"
334
334
  @error_io.rewind
335
335
  assert @error_io.read.include?(msg)
336
336
  end
@@ -344,7 +344,7 @@ class AppTest < Test::Unit::TestCase
344
344
  assert_equal 500, resp[0]
345
345
  assert_equal @app.asset("500.html"), resp[2].path
346
346
  @error_io.rewind
347
- assert @error_io.read.empty?
347
+ assert @error_io.read.include?("[ERROR] RuntimeError: Something bad happened\n")
348
348
  end
349
349
 
350
350
 
@@ -530,8 +530,9 @@ class AppTest < Test::Unit::TestCase
530
530
 
531
531
 
532
532
  def test_init
533
- assert Logger === @app.logger, "logger attribute should be a Logger"
534
- assert Logger === @rapp.logger, "logger attribute should be a Logger"
533
+ @app = FooApp.new
534
+ assert_equal $stdout, @app.logger, "logger should default to $stdout"
535
+ assert_equal $stdout, @rapp.logger, "logger should default to $stdout"
535
536
  assert_nil @app.rack_app, "Rack application should be nil by default"
536
537
  assert Proc === @rapp.rack_app, "Rack application should be a Proc"
537
538
  assert Gin::Router === @app.router, "Should have a Gin::Router"
@@ -69,7 +69,7 @@ class ControllerTest < Test::Unit::TestCase
69
69
  def setup
70
70
  MockApp.instance_variable_set("@environment", nil)
71
71
  MockApp.instance_variable_set("@asset_host", nil)
72
- @app = MockApp.new Logger.new(StringIO.new)
72
+ @app = MockApp.new StringIO.new
73
73
  @ctrl = BarController.new(@app, rack_env)
74
74
  end
75
75
 
@@ -230,7 +230,7 @@ class ControllerTest < Test::Unit::TestCase
230
230
  assert_equal 'no-cache', @ctrl.response['Pragma']
231
231
  assert_equal 'no-cache, no-store, must-revalidate, max-age=0',
232
232
  @ctrl.response['Cache-Control']
233
- assert_equal Time.new("1990","01","01").httpdate,
233
+ assert_equal Gin::Constants::EPOCH.httpdate,
234
234
  @ctrl.response['Expires']
235
235
  end
236
236
 
@@ -249,6 +249,15 @@ class ControllerTest < Test::Unit::TestCase
249
249
  end
250
250
 
251
251
 
252
+ def test_delete_cookie
253
+ test_set_cookie
254
+
255
+ @ctrl.delete_cookie "test"
256
+ assert_equal "test=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000",
257
+ @ctrl.response['Set-Cookie']
258
+ end
259
+
260
+
252
261
  def test_set_session
253
262
  @ctrl.session['test'] = 'user@example.com'
254
263
  assert_equal({"test"=>"user@example.com"}, @ctrl.env['rack.session'])
@@ -113,7 +113,7 @@ class RouterTest < Test::Unit::TestCase
113
113
  end
114
114
 
115
115
 
116
- def test_add_all_restful_routes
116
+ def test_add_all_routes_as_defaults
117
117
  @router.add MyCtrl, "/" do
118
118
  get :show, "/:id"
119
119
  defaults
@@ -124,6 +124,23 @@ class RouterTest < Test::Unit::TestCase
124
124
  end
125
125
 
126
126
 
127
+ def test_add_all_with_default_verb
128
+ @router.add MyCtrl, "/" do
129
+ get :show, "/:id"
130
+ defaults :post
131
+ end
132
+
133
+ assert_equal [MyCtrl, :index, {}],
134
+ @router.resources_for("GET", "/")
135
+
136
+ assert_equal [MyCtrl, :show, {'id' => '123'}],
137
+ @router.resources_for("GET", "/123")
138
+
139
+ assert_equal [MyCtrl, :unmounted_action, {}],
140
+ @router.resources_for("POST", "/unmounted_action")
141
+ end
142
+
143
+
127
144
  def test_add_all
128
145
  @router.add MyCtrl, "/"
129
146
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gin
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-12 00:00:00.000000000 Z
12
+ date: 2013-03-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -75,13 +75,9 @@ dependencies:
75
75
  - - ~>
76
76
  - !ruby/object:Gem::Version
77
77
  version: '3.5'
78
- description: ! 'Gin is a small web framework built from the redistillation of
78
+ description: ! 'Gin is a small Ruby web framework, built on Rack, which borrows from
79
79
 
80
- Sinatra and Rails idioms. Specifically, it uses much of Sinatra''s
81
-
82
- request flow and HTTP helper methods with dedicated controller classes
83
-
84
- that support Rails-like filters.'
80
+ Sinatra expressiveness, and targets larger applications.'
85
81
  email:
86
82
  - yaksnrainbows@gmail.com
87
83
  executables: []
@@ -100,9 +96,11 @@ files:
100
96
  - lib/gin.rb
101
97
  - lib/gin/app.rb
102
98
  - lib/gin/config.rb
99
+ - lib/gin/constants.rb
103
100
  - lib/gin/controller.rb
104
101
  - lib/gin/core_ext/cgi.rb
105
102
  - lib/gin/core_ext/gin_class.rb
103
+ - lib/gin/core_ext/rack_commonlogger.rb
106
104
  - lib/gin/errorable.rb
107
105
  - lib/gin/filterable.rb
108
106
  - lib/gin/reloadable.rb
@@ -150,7 +148,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
150
148
  version: '0'
151
149
  segments:
152
150
  - 0
153
- hash: 3408999320760955315
151
+ hash: -1880592358909000516
154
152
  required_rubygems_version: !ruby/object:Gem::Requirement
155
153
  none: false
156
154
  requirements:
@@ -162,8 +160,8 @@ rubyforge_project: gin
162
160
  rubygems_version: 1.8.24
163
161
  signing_key:
164
162
  specification_version: 3
165
- summary: Gin is a small web framework built from the redistillation of Sinatra and
166
- Rails idioms
163
+ summary: Gin is a small Ruby web framework, built on Rack, which borrows from Sinatra
164
+ expressiveness, and targets larger applications.
167
165
  test_files:
168
166
  - test/test_app.rb
169
167
  - test/test_config.rb