gin 1.0.3 → 1.0.4

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