gin 0.0.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +3 -3
- data/.gitignore +7 -0
- data/History.rdoc +3 -6
- data/Manifest.txt +36 -2
- data/README.rdoc +24 -14
- data/Rakefile +2 -9
- data/lib/gin.rb +122 -1
- data/lib/gin/app.rb +595 -0
- data/lib/gin/config.rb +50 -0
- data/lib/gin/controller.rb +602 -0
- data/lib/gin/core_ext/cgi.rb +15 -0
- data/lib/gin/core_ext/gin_class.rb +10 -0
- data/lib/gin/errorable.rb +113 -0
- data/lib/gin/filterable.rb +200 -0
- data/lib/gin/reloadable.rb +90 -0
- data/lib/gin/request.rb +76 -0
- data/lib/gin/response.rb +51 -0
- data/lib/gin/router.rb +222 -0
- data/lib/gin/stream.rb +56 -0
- data/public/400.html +14 -0
- data/public/404.html +13 -0
- data/public/500.html +14 -0
- data/public/error.html +38 -0
- data/public/favicon.ico +0 -0
- data/public/gin.css +61 -0
- data/public/gin_sm.png +0 -0
- data/test/app/app_foo.rb +15 -0
- data/test/app/controllers/app_controller.rb +16 -0
- data/test/app/controllers/foo_controller.rb +3 -0
- data/test/mock_config/backend.yml +7 -0
- data/test/mock_config/memcache.yml +10 -0
- data/test/mock_config/not_a_config.txt +0 -0
- data/test/test_app.rb +592 -0
- data/test/test_config.rb +33 -0
- data/test/test_controller.rb +808 -0
- data/test/test_errorable.rb +221 -0
- data/test/test_filterable.rb +126 -0
- data/test/test_gin.rb +59 -0
- data/test/test_helper.rb +5 -0
- data/test/test_request.rb +81 -0
- data/test/test_response.rb +68 -0
- data/test/test_router.rb +193 -0
- metadata +80 -15
- data/bin/gin +0 -3
- data/test/gin_test.rb +0 -8
@@ -0,0 +1,113 @@
|
|
1
|
+
module Gin::Errorable
|
2
|
+
extend GinClass
|
3
|
+
|
4
|
+
def self.included klass
|
5
|
+
klass.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
##
|
12
|
+
# Define an error handler for this Controller. Configurable with exceptions
|
13
|
+
# or status codes. Omitting the err_types argument acts as a catch-all for
|
14
|
+
# non-explicitly handled errors.
|
15
|
+
#
|
16
|
+
# error 502, 503, 504 do
|
17
|
+
# # handle unexpected upstream error
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# error do |err|
|
21
|
+
# # catch-all
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# error Timeout::Error do |err|
|
25
|
+
# # something timed out
|
26
|
+
# end
|
27
|
+
|
28
|
+
def error *err_types, &block
|
29
|
+
return unless block_given?
|
30
|
+
err_types << nil if err_types.empty?
|
31
|
+
|
32
|
+
err_types.each do |name|
|
33
|
+
self.error_handlers[name] = block
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
##
|
39
|
+
# Run after an error has been raised and optionally handled by an
|
40
|
+
# error callback. The block will get run on all errors and is given
|
41
|
+
# the exception instance as an argument.
|
42
|
+
# Note: This block will not get run after http status error handlers.
|
43
|
+
|
44
|
+
def all_errors &block
|
45
|
+
return unless block_given?
|
46
|
+
self.error_handlers[:all] = block
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
##
|
51
|
+
# Hash of error handlers defined by Gin::Controller.error.
|
52
|
+
|
53
|
+
def error_handlers
|
54
|
+
@err_handlers ||= {}
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
##
|
59
|
+
# Find the appropriate error handler for the given error.
|
60
|
+
# First looks for handler in the current class, then looks
|
61
|
+
# in parent classes if none is found.
|
62
|
+
|
63
|
+
def error_handler_for err #:nodoc:
|
64
|
+
handler =
|
65
|
+
case err
|
66
|
+
when Integer
|
67
|
+
error_handlers[err] || error_handlers[nil]
|
68
|
+
|
69
|
+
when Exception
|
70
|
+
klasses = err.class.ancestors[0...-3]
|
71
|
+
key = klasses.find{|klass| error_handlers[klass] }
|
72
|
+
error_handlers[key]
|
73
|
+
|
74
|
+
else
|
75
|
+
error_handlers[err]
|
76
|
+
end
|
77
|
+
|
78
|
+
handler ||
|
79
|
+
self.superclass.respond_to?(:error_handler_for) &&
|
80
|
+
self.superclass.error_handler_for(err)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
class_proxy :error_handlers, :error_handler_for
|
86
|
+
|
87
|
+
##
|
88
|
+
# Calls the appropriate error handlers for the given error.
|
89
|
+
# Re-raises the error if no handler is found.
|
90
|
+
|
91
|
+
def handle_error err
|
92
|
+
(@env[Gin::App::RACK_KEYS[:errors]] ||= []) << err
|
93
|
+
status(err.http_status) if err.respond_to?(:http_status)
|
94
|
+
status(500) unless (400..599).include? status
|
95
|
+
|
96
|
+
handler = error_handler_for(err)
|
97
|
+
instance_exec(err, &handler) if handler
|
98
|
+
|
99
|
+
ahandler = error_handler_for(:all)
|
100
|
+
instance_exec(err, &ahandler) if ahandler
|
101
|
+
|
102
|
+
raise err unless handler
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
##
|
107
|
+
# Calls the appropriate error handlers for the given status code.
|
108
|
+
|
109
|
+
def handle_status code
|
110
|
+
handler = error_handler_for(code)
|
111
|
+
instance_exec(&handler) if handler
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
module Gin::Filterable
|
2
|
+
|
3
|
+
extend GinClass
|
4
|
+
|
5
|
+
class InvalidFilterError < Gin::Error; end
|
6
|
+
|
7
|
+
def self.included klass
|
8
|
+
klass.extend ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
|
14
|
+
##
|
15
|
+
# Create a filter for controller actions.
|
16
|
+
# filter :logged_in do
|
17
|
+
# @user && @user.logged_in?
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# Use Gin::Controller.before_filter and Gin::Controller.after_filter to
|
21
|
+
# apply filters.
|
22
|
+
|
23
|
+
def filter name, &block
|
24
|
+
self.filters[name.to_sym] = block
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
##
|
29
|
+
# Hash of filters defined by Gin::Controller.filter.
|
30
|
+
# This attribute is inherited.
|
31
|
+
|
32
|
+
def filters
|
33
|
+
@filters ||= self.superclass.respond_to?(:filters) ?
|
34
|
+
self.superclass.filters.dup : {}
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def modify_filter_stack filter_hsh, name, *names #:nodoc:
|
39
|
+
names = [name].concat(names)
|
40
|
+
opts = Hash === names[-1] ? names.pop : {}
|
41
|
+
names.map!(&:to_sym)
|
42
|
+
|
43
|
+
if opts[:only]
|
44
|
+
Array(opts[:only]).each do |action|
|
45
|
+
action = action.to_sym
|
46
|
+
filter_hsh[action] ||= filter_hsh[nil].dup
|
47
|
+
yield filter_hsh, action, names
|
48
|
+
end
|
49
|
+
|
50
|
+
elsif opts[:except]
|
51
|
+
except = Array(opts[:except])
|
52
|
+
filter_hsh.keys.each do |action|
|
53
|
+
next if action.nil?
|
54
|
+
filter_hsh[action] ||= filter_hsh[nil].dup and next if
|
55
|
+
except.include?(action)
|
56
|
+
|
57
|
+
yield filter_hsh, action, names
|
58
|
+
end
|
59
|
+
yield filter_hsh, nil, names
|
60
|
+
|
61
|
+
else
|
62
|
+
filter_hsh.keys.each do |action|
|
63
|
+
yield filter_hsh, action, names
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
def append_filters filter_hsh, name, *names #:nodoc:
|
70
|
+
modify_filter_stack(filter_hsh, name, *names) do |h,k,n|
|
71
|
+
h[k].concat n
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
def skip_filters filter_hsh, name, *names #:nodoc:
|
77
|
+
modify_filter_stack(filter_hsh, name, *names) do |h,k,n|
|
78
|
+
h[k] -= n
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
##
|
84
|
+
# Assign one or more filters to run before calling an action.
|
85
|
+
# Set for all actions by default.
|
86
|
+
# This attribute is inherited.
|
87
|
+
# Supports an options hash as the last argument with :only and :except
|
88
|
+
# keys.
|
89
|
+
#
|
90
|
+
# before_filter :logged_in, :except => :index do
|
91
|
+
# verify_session! || halt 401
|
92
|
+
# end
|
93
|
+
|
94
|
+
def before_filter name, *opts, &block
|
95
|
+
filter(name, &block) if block_given?
|
96
|
+
append_filters(before_filters, name, *opts)
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
##
|
101
|
+
# List of before filters.
|
102
|
+
# This attribute is inherited.
|
103
|
+
|
104
|
+
def before_filters
|
105
|
+
return @before_filters if @before_filters
|
106
|
+
@before_filters ||= {nil => []}
|
107
|
+
|
108
|
+
if superclass.respond_to?(:before_filters)
|
109
|
+
superclass.before_filters.each{|k,v| @before_filters[k] = v.dup }
|
110
|
+
end
|
111
|
+
|
112
|
+
@before_filters
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
##
|
117
|
+
# Skip a before filter in the context of the controller.
|
118
|
+
# This attribute is inherited.
|
119
|
+
# Supports an options hash as the last argument with :only and :except
|
120
|
+
# keys.
|
121
|
+
|
122
|
+
def skip_before_filter name, *names
|
123
|
+
skip_filters(self.before_filters, name, *names)
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
##
|
128
|
+
# Assign one or more filters to run after calling an action.
|
129
|
+
# Set for all actions by default.
|
130
|
+
# This attribute is inherited.
|
131
|
+
# Supports an options hash as the last argument with :only and :except
|
132
|
+
# keys.
|
133
|
+
#
|
134
|
+
# after_filter :clear_cookies, :only => :logout do
|
135
|
+
# session[:user] = nil
|
136
|
+
# end
|
137
|
+
|
138
|
+
def after_filter name, *opts, &block
|
139
|
+
filter(name, &block) if block_given?
|
140
|
+
append_filters(self.after_filters, name, *opts)
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
##
|
145
|
+
# List of after filters.
|
146
|
+
|
147
|
+
def after_filters
|
148
|
+
return @after_filters if @after_filters
|
149
|
+
@after_filters ||= {nil => []}
|
150
|
+
|
151
|
+
if superclass.respond_to?(:after_filters)
|
152
|
+
superclass.after_filters.each{|k,v| @after_filters[k] = v.dup }
|
153
|
+
end
|
154
|
+
|
155
|
+
@after_filters
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
##
|
160
|
+
# Skip an after filter in the context of the controller.
|
161
|
+
# This attribute is inherited.
|
162
|
+
# Supports an options hash as the last argument with :only and :except
|
163
|
+
# keys.
|
164
|
+
|
165
|
+
def skip_after_filter name, *names
|
166
|
+
skip_filters(self.after_filters, name, *names)
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
##
|
171
|
+
# Get an Array of before filter names for the given action.
|
172
|
+
|
173
|
+
def before_filters_for action
|
174
|
+
before_filters[action] || before_filters[nil] || []
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
##
|
179
|
+
# Get an Array of after filter names for the given action.
|
180
|
+
|
181
|
+
def after_filters_for action
|
182
|
+
after_filters[action] || after_filters[nil] || []
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
class_proxy :filters, :before_filters, :after_filters,
|
188
|
+
:before_filters_for, :after_filters_for
|
189
|
+
|
190
|
+
##
|
191
|
+
# Chain-call filters from an action. Raises the filter exception if any
|
192
|
+
# filter in the chain fails.
|
193
|
+
# filter :logged_in, :admin
|
194
|
+
|
195
|
+
def filter *names
|
196
|
+
names.each do |n|
|
197
|
+
instance_eval(&self.filters[n.to_sym])
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Gin::Reloadable #:nodoc:
|
2
|
+
extend GinClass
|
3
|
+
|
4
|
+
def self.included klass
|
5
|
+
klass.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
module ClassMethods #:nodoc:
|
10
|
+
def reloadables
|
11
|
+
@reloadables ||= {}
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def erase_dependencies!
|
16
|
+
reloadables.each do |key, (path, files, consts)|
|
17
|
+
erase! files, consts
|
18
|
+
end
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def erase! files, consts, parent=nil
|
24
|
+
parent ||= Object
|
25
|
+
files.each{|f| $LOADED_FEATURES.delete f }
|
26
|
+
clear_constants parent, consts
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def clear_constants parent, names=nil
|
31
|
+
names ||= parent.constants
|
32
|
+
|
33
|
+
names.each do |name|
|
34
|
+
const = parent.const_get(name)
|
35
|
+
next unless const
|
36
|
+
|
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
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def without_warnings &block
|
49
|
+
warn_level = $VERBOSE
|
50
|
+
$VERBOSE = nil
|
51
|
+
yield
|
52
|
+
ensure
|
53
|
+
$VERBOSE = warn_level
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def track_require file
|
58
|
+
old_consts = Object.constants
|
59
|
+
old_features = $LOADED_FEATURES.dup
|
60
|
+
|
61
|
+
filepath = Gin.find_loadpath file
|
62
|
+
|
63
|
+
if !reloadables[filepath]
|
64
|
+
success = Object.send(:require, file)
|
65
|
+
|
66
|
+
else reloadables[filepath]
|
67
|
+
without_warnings{
|
68
|
+
success = Object.send(:require, file)
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
reloadables[filepath] = [
|
73
|
+
file,
|
74
|
+
$LOADED_FEATURES - old_features,
|
75
|
+
Object.constants - old_consts
|
76
|
+
] if success
|
77
|
+
|
78
|
+
success
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
def require file
|
83
|
+
if autoreload
|
84
|
+
track_require file
|
85
|
+
else
|
86
|
+
super file
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/gin/request.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
class Gin::Request < Rack::Request
|
2
|
+
|
3
|
+
def initialize env
|
4
|
+
super
|
5
|
+
self.params.update env[Gin::App::RACK_KEYS[:path_params]] if
|
6
|
+
env[Gin::App::RACK_KEYS[:path_params]]
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
def forwarded?
|
11
|
+
@env.include? "HTTP_X_FORWARDED_HOST"
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def ssl?
|
16
|
+
scheme == 'https'
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def safe?
|
21
|
+
get? or head? or options? or trace?
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def idempotent?
|
26
|
+
safe? or put? or delete?
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def params
|
31
|
+
unless @params
|
32
|
+
super
|
33
|
+
@params = process_params @params
|
34
|
+
end
|
35
|
+
|
36
|
+
@params
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
M_BOOLEAN = /^true|false$/ #:nodoc:
|
43
|
+
M_FLOAT = /^\d+\.\d+$/ #:nodoc:
|
44
|
+
M_INTEGER = /^\d+$/ #:nodoc:
|
45
|
+
|
46
|
+
##
|
47
|
+
# Enable string or symbol key access to the nested params hash.
|
48
|
+
# Make String numbers into Numerics.
|
49
|
+
|
50
|
+
def process_params object
|
51
|
+
case object
|
52
|
+
when Hash
|
53
|
+
new_hash = indifferent_hash
|
54
|
+
object.each { |key, value| new_hash[key] = process_params(value) }
|
55
|
+
new_hash
|
56
|
+
when Array
|
57
|
+
object.map { |item| process_params(item) }
|
58
|
+
when M_BOOLEAN
|
59
|
+
object == "true"
|
60
|
+
when M_FLOAT
|
61
|
+
object.to_f
|
62
|
+
when M_INTEGER
|
63
|
+
object.to_i
|
64
|
+
else
|
65
|
+
object
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
##
|
71
|
+
# Creates a Hash with indifferent access.
|
72
|
+
|
73
|
+
def indifferent_hash
|
74
|
+
Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
|
75
|
+
end
|
76
|
+
end
|