gin 0.0.0 → 1.0.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.
- 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
|