gin 1.0.4 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.rdoc +16 -0
- data/Manifest.txt +12 -2
- data/README.rdoc +33 -0
- data/Rakefile +5 -0
- data/TODO.rdoc +7 -0
- data/bin/gin +158 -0
- data/lib/gin.rb +19 -2
- data/lib/gin/app.rb +420 -173
- data/lib/gin/cache.rb +65 -0
- data/lib/gin/config.rb +174 -18
- data/lib/gin/constants.rb +4 -0
- data/lib/gin/controller.rb +219 -11
- data/lib/gin/core_ext/gin_class.rb +16 -0
- data/lib/gin/errorable.rb +16 -2
- data/lib/gin/filterable.rb +29 -19
- data/lib/gin/reloadable.rb +1 -1
- data/lib/gin/request.rb +11 -0
- data/lib/gin/router.rb +185 -61
- data/lib/gin/rw_lock.rb +109 -0
- data/lib/gin/test.rb +702 -0
- data/public/gin.css +15 -3
- data/test/app/layouts/bar.erb +9 -0
- data/test/app/layouts/foo.erb +9 -0
- data/test/app/views/bar.erb +1 -0
- data/test/mock_app.rb +94 -0
- data/test/mock_config/invalid.yml +2 -0
- data/test/test_app.rb +160 -45
- data/test/test_cache.rb +57 -0
- data/test/test_config.rb +108 -13
- data/test/test_controller.rb +201 -11
- data/test/test_errorable.rb +1 -1
- data/test/test_gin.rb +9 -0
- data/test/test_helper.rb +3 -1
- data/test/test_router.rb +33 -0
- data/test/test_rw_lock.rb +65 -0
- data/test/test_test.rb +627 -0
- metadata +86 -6
- data/.autotest +0 -23
- data/.gitignore +0 -7
data/lib/gin/cache.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
##
|
2
|
+
# Gin::Cache is a bare-bones in-memory data store.
|
3
|
+
# It's thread-safe and built for read-bound data.
|
4
|
+
# Reads are lock-less when no writes are queued.
|
5
|
+
|
6
|
+
class Gin::Cache
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@data = {}
|
10
|
+
@lock = Gin::RWLock.new
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
##
|
15
|
+
# Set the write timeout when waiting for reader thread locks.
|
16
|
+
# Defaults to 0.05 sec. See Gin::RWLock for more details.
|
17
|
+
|
18
|
+
def write_timeout= sec
|
19
|
+
@lock.write_timeout = sec
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
##
|
24
|
+
# Get the write timeout when waiting for reader thread locks.
|
25
|
+
# See Gin::RWLock for more details.
|
26
|
+
|
27
|
+
def write_timeout
|
28
|
+
@lock.write_timeout
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
##
|
33
|
+
# Get a value from the cache with the given key.
|
34
|
+
|
35
|
+
def [] key
|
36
|
+
@lock.read_sync{ @data[key] }
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
##
|
41
|
+
# Set a value in the cache with the given key and value.
|
42
|
+
|
43
|
+
def []= key, val
|
44
|
+
@lock.write_sync{ @data[key] = val }
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
##
|
49
|
+
# Check if the current key exists in the cache.
|
50
|
+
|
51
|
+
def has_key? key
|
52
|
+
@lock.read_sync{ @data.has_key? key }
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
##
|
57
|
+
# Returns a cache value if it exists. Otherwise, locks and assigns the
|
58
|
+
# provided value or block result. Blocks get executed with the write lock
|
59
|
+
# to prevent redundant operations.
|
60
|
+
|
61
|
+
def cache key, value=nil, &block
|
62
|
+
return self[key] if self.has_key?(key)
|
63
|
+
@lock.write_sync{ @data[key] = block_given? ? yield() : value }
|
64
|
+
end
|
65
|
+
end
|
data/lib/gin/config.rb
CHANGED
@@ -1,50 +1,206 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
|
3
|
+
##
|
4
|
+
# Environment-specific config files loading mechanism.
|
5
|
+
#
|
6
|
+
# # config_dir/memcache.yml
|
7
|
+
# default: &default
|
8
|
+
# host: http://memcache.example.com
|
9
|
+
# connections: 5
|
10
|
+
#
|
11
|
+
# development: &dev
|
12
|
+
# host: localhost:123321
|
13
|
+
# connections: 1
|
14
|
+
#
|
15
|
+
# test: *dev
|
16
|
+
#
|
17
|
+
# staging:
|
18
|
+
# host: http://stage-memcache.example.com
|
19
|
+
#
|
20
|
+
# production: *default
|
21
|
+
#
|
22
|
+
#
|
23
|
+
# # config.rb
|
24
|
+
# config = Gin::Config.new 'staging', :dir => 'config/dir'
|
25
|
+
#
|
26
|
+
# config['memcache.host']
|
27
|
+
# #=> "http://stage-memcache.example.com"
|
28
|
+
#
|
29
|
+
# config['memcache.connections']
|
30
|
+
# #=> 5
|
31
|
+
#
|
32
|
+
# Config files get loaded on demand. They may also be reloaded on demand
|
33
|
+
# by setting the :ttl option to expire values. Values are only expired
|
34
|
+
# for configs with a source file whose mtime value differs from the one
|
35
|
+
# it had at its previous load time.
|
36
|
+
#
|
37
|
+
# # 5 minute expiration
|
38
|
+
# config = Gin::Config.new 'staging', :ttl => 300
|
39
|
+
|
3
40
|
class Gin::Config
|
4
41
|
|
5
|
-
|
42
|
+
attr_accessor :dir, :logger, :ttl, :environment
|
43
|
+
|
44
|
+
##
|
45
|
+
# Create a new config instance for the given environment name.
|
46
|
+
# The environment dictates which part of the config files gets exposed.
|
6
47
|
|
7
|
-
def initialize environment,
|
8
|
-
self.dir = dir
|
48
|
+
def initialize environment, opts={}
|
9
49
|
@environment = environment
|
10
|
-
@
|
11
|
-
@
|
12
|
-
|
50
|
+
@logger = opts[:logger] || $stdout
|
51
|
+
@ttl = opts[:ttl] || false
|
52
|
+
@dir = opts[:dir] || "./config"
|
53
|
+
|
54
|
+
@data = {}
|
55
|
+
@load_times = {}
|
56
|
+
@mtimes = {}
|
57
|
+
|
58
|
+
@lock = Gin::RWLock.new(opts[:write_timeout])
|
13
59
|
end
|
14
60
|
|
15
61
|
|
16
|
-
|
17
|
-
|
62
|
+
##
|
63
|
+
# Get or set the write timeout when waiting for reader thread locks.
|
64
|
+
# Defaults to 0.05 sec. See Gin::RWLock for more details.
|
65
|
+
|
66
|
+
def write_timeout sec=nil
|
67
|
+
@lock.write_timeout = sec if sec
|
68
|
+
@lock.write_timeout
|
18
69
|
end
|
19
70
|
|
20
71
|
|
72
|
+
##
|
73
|
+
# Force-load all the config files in the config directory.
|
74
|
+
|
21
75
|
def load!
|
22
76
|
return unless @dir
|
23
|
-
Dir[@dir].each do |filepath|
|
24
|
-
|
25
|
-
|
77
|
+
Dir[File.join(@dir, "*.yml")].each do |filepath|
|
78
|
+
load_config filepath
|
79
|
+
end
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
26
83
|
|
84
|
+
##
|
85
|
+
# Load the given config name, or filename.
|
86
|
+
# # Loads @dir/my_config.yml
|
87
|
+
# config.load_config 'my_config'
|
88
|
+
# config['my_config']
|
89
|
+
# #=> data from file
|
90
|
+
#
|
91
|
+
# # Loads the given file if it exists.
|
92
|
+
# config.load_config 'path/to/my_config.yml'
|
93
|
+
# config['my_config']
|
94
|
+
# #=> data from file
|
95
|
+
|
96
|
+
def load_config name
|
97
|
+
name = name.to_s
|
98
|
+
|
99
|
+
if File.file?(name)
|
100
|
+
filepath = name
|
27
101
|
name = File.basename(filepath, ".yml")
|
28
|
-
|
102
|
+
else
|
103
|
+
filepath = filepath_for(name)
|
29
104
|
end
|
30
|
-
|
105
|
+
|
106
|
+
raise Gin::MissingConfig, "No config file at #{filepath}" unless
|
107
|
+
File.file?(filepath)
|
108
|
+
|
109
|
+
@lock.write_sync do
|
110
|
+
@load_times[name] = Time.now
|
111
|
+
|
112
|
+
mtime = File.mtime(filepath)
|
113
|
+
return if mtime == @mtimes[name]
|
114
|
+
|
115
|
+
@mtimes[name] = mtime
|
116
|
+
|
117
|
+
c = YAML.load_file(filepath)
|
118
|
+
c = (c['default'] || {}).merge(c[@environment] || {})
|
119
|
+
|
120
|
+
@data[name] = c
|
121
|
+
end
|
122
|
+
|
123
|
+
rescue Psych::SyntaxError
|
124
|
+
@logger.write "[ERROR] Could not parse config `#{filepath}' as YAML"
|
125
|
+
return nil
|
31
126
|
end
|
32
127
|
|
33
128
|
|
129
|
+
##
|
130
|
+
# Sets a new config name and value. Configs set in this manner do not
|
131
|
+
# qualify for reloading as they don't have a source file.
|
132
|
+
|
34
133
|
def set name, data
|
35
|
-
@data[name] = data
|
36
|
-
define_method(name){ @data[name] } unless respond_to? name
|
134
|
+
@lock.write_sync{ @data[name] = data }
|
37
135
|
end
|
38
136
|
|
39
137
|
|
138
|
+
##
|
139
|
+
# Get a config value from its name.
|
140
|
+
# Setting safe to true will return nil instead of raising errors.
|
141
|
+
# Reloads the config if reloading is enabled and value expired.
|
142
|
+
|
143
|
+
def get name, safe=false
|
144
|
+
return @lock.read_sync{ @data[name] } if
|
145
|
+
current?(name) || safe && !File.file?(filepath_for(name))
|
146
|
+
|
147
|
+
load_config(name) || @lock.read_sync{ @data[name] }
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
##
|
152
|
+
# Checks if the given config is outdated.
|
153
|
+
|
154
|
+
def current? name
|
155
|
+
@lock.read_sync do
|
156
|
+
@ttl == false && @data.has_key?(name) ||
|
157
|
+
!@load_times[name] && @data.has_key?(name) ||
|
158
|
+
@load_times[name] && Time.now - @load_times[name] <= @ttl
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
##
|
164
|
+
# Checks if a config exists in memory or on disk, by its name.
|
165
|
+
# # If foo config isn't loaded, looks for file under @dir/foo.yml
|
166
|
+
# config.has? 'foo'
|
167
|
+
# #=> true
|
168
|
+
|
40
169
|
def has? name
|
41
|
-
@data.has_key?(name)
|
170
|
+
@lock.read_sync{ @data.has_key?(name) } || File.file?(filepath_for(name))
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
##
|
175
|
+
# Non-raising config lookup. The following query the same value:
|
176
|
+
# # Raises an error if the 'user' key is missing.
|
177
|
+
# config.get('remote_shell')['user']['name']
|
178
|
+
#
|
179
|
+
# # Doesn't raise an error if a key is missing.
|
180
|
+
# # Doesn't support configs with '.' in the key names.
|
181
|
+
# config['remote_shell.user.name']
|
182
|
+
|
183
|
+
def [] key
|
184
|
+
chain = key.to_s.split(".")
|
185
|
+
name = chain.shift
|
186
|
+
curr = get(name, true)
|
187
|
+
return unless curr
|
188
|
+
|
189
|
+
chain.each do |k|
|
190
|
+
return unless Array === curr || Hash === curr
|
191
|
+
val = curr[k]
|
192
|
+
val = curr[k.to_i] if val.nil? && k.to_i.to_s == k
|
193
|
+
curr = val
|
194
|
+
end
|
195
|
+
|
196
|
+
curr
|
42
197
|
end
|
43
198
|
|
44
199
|
|
45
200
|
private
|
46
201
|
|
47
|
-
|
48
|
-
|
202
|
+
|
203
|
+
def filepath_for name
|
204
|
+
File.join(@dir, "#{name}.yml")
|
49
205
|
end
|
50
206
|
end
|
data/lib/gin/constants.rb
CHANGED
@@ -38,10 +38,14 @@ module Gin::Constants
|
|
38
38
|
GIN_RELOADED = 'gin.reloaded'.freeze
|
39
39
|
GIN_ERRORS = 'gin.errors'.freeze
|
40
40
|
GIN_TIMESTAMP = 'gin.timestamp'.freeze
|
41
|
+
GIN_TEMPLATES = 'gin.templates'.freeze
|
41
42
|
|
42
43
|
# Environment names
|
43
44
|
ENV_DEV = "development".freeze
|
44
45
|
ENV_TEST = "test".freeze
|
45
46
|
ENV_STAGE = "staging".freeze
|
46
47
|
ENV_PROD = "production".freeze
|
48
|
+
|
49
|
+
# Other
|
50
|
+
SESSION_SECRET = "%064x" % Kernel.rand(2**256-1)
|
47
51
|
end
|
data/lib/gin/controller.rb
CHANGED
@@ -1,3 +1,52 @@
|
|
1
|
+
# Gin controllers follow only a few rules:
|
2
|
+
#
|
3
|
+
# * ALL instance methods are actions (put your helper methods in a module or
|
4
|
+
# parent class not mounted to the app).
|
5
|
+
# * Filters are defined by blocks passed to special class methods.
|
6
|
+
# * Controller-level error handlers are defined by blocks passed to the 'error'
|
7
|
+
# class method.
|
8
|
+
#
|
9
|
+
# Gin controller actions are any instance method defined on the controller
|
10
|
+
# class. The HTTP response body takes the value of the method's return value.
|
11
|
+
#
|
12
|
+
# If the instance method takes arguments, matching params will be assigned to
|
13
|
+
# them. A required argument with a missing param triggers a Gin::BadRequest
|
14
|
+
# error, resulting in a 400 response.
|
15
|
+
#
|
16
|
+
# class UserController < Gin::Controller
|
17
|
+
# # Get params id and email
|
18
|
+
# def show id, email=nil
|
19
|
+
# ...
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# Gin actions also support Ruby 2.0 keyed arguments, which are more flexible
|
24
|
+
# for assigning default values when dealing with missing params.
|
25
|
+
#
|
26
|
+
# class UserController < Gin::Controller
|
27
|
+
# def show(id, email: nil, full: false)
|
28
|
+
# ...
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# Views are rendered by calling the 'view' method from a controller instance.
|
33
|
+
# Views don't halt the action but return a String of the rendered view.
|
34
|
+
# If a layout was set, the rendered view will include the layout.
|
35
|
+
#
|
36
|
+
# class UserController < Gin::Controller
|
37
|
+
# layout :user
|
38
|
+
#
|
39
|
+
# def show id
|
40
|
+
# # Renders <app.views_dir>/show_user.* with the layout <app.layouts_dir>/user.*
|
41
|
+
# view :show_user
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# def tos
|
45
|
+
# # Renders <app.views_dir>/tos.* with no layout
|
46
|
+
# view :tos, :layout => false
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
|
1
50
|
class Gin::Controller
|
2
51
|
extend GinClass
|
3
52
|
include Gin::Constants
|
@@ -17,6 +66,20 @@ class Gin::Controller
|
|
17
66
|
end
|
18
67
|
|
19
68
|
|
69
|
+
def self.inherited subclass
|
70
|
+
subclass.setup
|
71
|
+
super
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
def self.setup # :nodoc:
|
76
|
+
@layout = nil
|
77
|
+
@ctrl_name = Gin.underscore(self.to_s).gsub(/_?controller_?/,'')
|
78
|
+
end
|
79
|
+
|
80
|
+
setup
|
81
|
+
|
82
|
+
|
20
83
|
##
|
21
84
|
# Array of action names for this controller.
|
22
85
|
|
@@ -31,8 +94,9 @@ class Gin::Controller
|
|
31
94
|
# MyApp::FooController.controller_name
|
32
95
|
# #=> "my_app/foo"
|
33
96
|
|
34
|
-
def self.controller_name
|
35
|
-
@ctrl_name
|
97
|
+
def self.controller_name new_name=nil
|
98
|
+
@ctrl_name = new_name if new_name
|
99
|
+
@ctrl_name
|
36
100
|
end
|
37
101
|
|
38
102
|
|
@@ -41,10 +105,10 @@ class Gin::Controller
|
|
41
105
|
# Default value is "text/html". This attribute is inherited.
|
42
106
|
|
43
107
|
def self.content_type new_type=nil
|
44
|
-
|
45
|
-
return @content_type if @content_type
|
46
|
-
self.superclass.respond_to?(:content_type)
|
47
|
-
self.superclass.content_type
|
108
|
+
@content_type = new_type if new_type
|
109
|
+
return @content_type if defined?(@content_type) && @content_type
|
110
|
+
self.superclass.respond_to?(:content_type) &&
|
111
|
+
self.superclass.content_type || "text/html"
|
48
112
|
end
|
49
113
|
|
50
114
|
|
@@ -59,9 +123,36 @@ class Gin::Controller
|
|
59
123
|
end
|
60
124
|
|
61
125
|
|
62
|
-
|
126
|
+
##
|
127
|
+
# Get or set a layout for a given controller.
|
128
|
+
# Value can be a symbol or filepath.
|
129
|
+
# Layout file is expected to be in the Gin::App.layout_dir directory
|
130
|
+
# Defaults to the parent class layout, or Gin::App.layout.
|
131
|
+
|
132
|
+
def self.layout name=nil
|
133
|
+
@layout = name if name
|
134
|
+
return @layout if @layout
|
135
|
+
return self.superclass.layout if self.superclass.respond_to?(:layout)
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
class_rproxy :controller_name, :actions
|
140
|
+
|
141
|
+
# The Gin::App instance used by the controller. The App instance is meant for
|
142
|
+
# read-only use. Writes are not thread-safe and at your own risk.
|
143
|
+
attr_reader :app
|
144
|
+
|
145
|
+
# Gin::Request instance representing the HTTP request.
|
146
|
+
attr_reader :request
|
63
147
|
|
64
|
-
|
148
|
+
# Gin::Response instance representing the HTTP response.
|
149
|
+
attr_reader :response
|
150
|
+
|
151
|
+
# The action that the HTTP request resolved to, based on the App's router.
|
152
|
+
attr_reader :action
|
153
|
+
|
154
|
+
# The Rack env hash.
|
155
|
+
attr_reader :env
|
65
156
|
|
66
157
|
|
67
158
|
def initialize app, env
|
@@ -99,6 +190,14 @@ class Gin::Controller
|
|
99
190
|
end
|
100
191
|
|
101
192
|
|
193
|
+
##
|
194
|
+
# Accessor for @app.config.
|
195
|
+
|
196
|
+
def config
|
197
|
+
@app.config
|
198
|
+
end
|
199
|
+
|
200
|
+
|
102
201
|
##
|
103
202
|
# Get the normalized mime-type matching the given input.
|
104
203
|
|
@@ -109,6 +208,9 @@ class Gin::Controller
|
|
109
208
|
|
110
209
|
##
|
111
210
|
# Get or set the HTTP response Content-Type header.
|
211
|
+
# content_type :json
|
212
|
+
# content_type 'application/json;charset=us-ascii'
|
213
|
+
# content_type :json, charset: 'us-ascii'
|
112
214
|
|
113
215
|
def content_type type=nil, params={}
|
114
216
|
return @response[CNT_TYPE] unless type
|
@@ -296,6 +398,7 @@ class Gin::Controller
|
|
296
398
|
# path_to :show, :id => 123
|
297
399
|
# #=> "/foo/123"
|
298
400
|
#
|
401
|
+
# # Default named route
|
299
402
|
# path_to :show_foo, :id => 123
|
300
403
|
# #=> "/foo/123"
|
301
404
|
|
@@ -516,6 +619,102 @@ class Gin::Controller
|
|
516
619
|
end
|
517
620
|
|
518
621
|
|
622
|
+
##
|
623
|
+
# Value of the layout to use for rendering.
|
624
|
+
# See also Gin::Controller.layout and Gin::App.layout.
|
625
|
+
|
626
|
+
def layout
|
627
|
+
self.class.layout || @app.layout
|
628
|
+
end
|
629
|
+
|
630
|
+
|
631
|
+
##
|
632
|
+
# Returns the path to where the template is expected to be.
|
633
|
+
# template_path :foo
|
634
|
+
# #=> "<views_dir>/foo"
|
635
|
+
#
|
636
|
+
# template_path "sub/foo"
|
637
|
+
# #=> "<views_dir>/sub/foo"
|
638
|
+
#
|
639
|
+
# template_path "sub/foo", :layout
|
640
|
+
# #=> "<layouts_dir>/sub/foo"
|
641
|
+
#
|
642
|
+
# template_path "/other/foo"
|
643
|
+
# #=> "<root_dir>/other/foo"
|
644
|
+
|
645
|
+
def template_path template, is_layout=false
|
646
|
+
dir = if template[0] == ?/
|
647
|
+
@app.root_dir
|
648
|
+
elsif is_layout
|
649
|
+
@app.layouts_dir
|
650
|
+
else
|
651
|
+
@app.views_dir
|
652
|
+
end
|
653
|
+
|
654
|
+
path = File.join(dir, template.to_s)
|
655
|
+
path.gsub!('*', controller_name)
|
656
|
+
File.expand_path(path)
|
657
|
+
end
|
658
|
+
|
659
|
+
|
660
|
+
##
|
661
|
+
# Render a template with the given view template.
|
662
|
+
# Options supported:
|
663
|
+
# :locals:: Hash - local variables used in template
|
664
|
+
# :layout:: Symbol/String - a custom layout to use
|
665
|
+
# :scope:: Object - The scope in which to render the template: default self
|
666
|
+
# :content_type:: Symbol/String - Content-Type header to set
|
667
|
+
# :engine:: String - Tilt rendering engine to use
|
668
|
+
# :layout_engine:: String - Tilt layout rendering engine to use
|
669
|
+
#
|
670
|
+
# The template argument may be a String or a Symbol. By default the
|
671
|
+
# template location will be looked for under Gin::App.views_dir, but
|
672
|
+
# the directory may be specified as any directory under Gin::App.root_dir
|
673
|
+
# by using the '/' prefix:
|
674
|
+
#
|
675
|
+
# view 'foo/template'
|
676
|
+
# #=> Renders file "<views_dir>/foo/template"
|
677
|
+
#
|
678
|
+
# view '/foo/template'
|
679
|
+
# #=> Renders file "<root_dir>/foo/template"
|
680
|
+
#
|
681
|
+
# # Render without layout
|
682
|
+
# view 'foo/template', layout: false
|
683
|
+
|
684
|
+
def view template, opts={}, &block
|
685
|
+
content_type(opts.delete(:content_type)) if opts[:content_type]
|
686
|
+
|
687
|
+
scope = opts[:scope] || self
|
688
|
+
locals = opts[:locals] || {}
|
689
|
+
|
690
|
+
template = template_path(template)
|
691
|
+
v_template = @app.template_for template, opts[:engine]
|
692
|
+
raise Gin::TemplateMissing, "No such template `#{template}'" unless v_template
|
693
|
+
|
694
|
+
if opts[:layout] != false
|
695
|
+
r_layout = template_path((opts[:layout] || layout), true)
|
696
|
+
r_template = @app.template_for r_layout, opts[:layout_engine] if r_layout
|
697
|
+
end
|
698
|
+
|
699
|
+
if !@response[CNT_TYPE]
|
700
|
+
mime_type = v_template.class.default_mime_type ||
|
701
|
+
r_template && r_template.class.default_mime_type
|
702
|
+
content_type(mime_type) if mime_type
|
703
|
+
end
|
704
|
+
|
705
|
+
@env[GIN_TEMPLATES] ||= []
|
706
|
+
|
707
|
+
if r_template
|
708
|
+
@env[GIN_TEMPLATES] << r_template.file << v_template.file
|
709
|
+
r_template.render(scope, locals){
|
710
|
+
v_template.render(scope, locals, &block) }
|
711
|
+
else
|
712
|
+
@env[GIN_TEMPLATES] << v_template.file
|
713
|
+
v_template.render(scope, locals, &block)
|
714
|
+
end
|
715
|
+
end
|
716
|
+
|
717
|
+
|
519
718
|
##
|
520
719
|
# Taken from Sinatra.
|
521
720
|
#
|
@@ -567,12 +766,13 @@ class Gin::Controller
|
|
567
766
|
def html_error_page err, code=nil
|
568
767
|
if @app.development?
|
569
768
|
fulltrace = err.backtrace.join("\n")
|
570
|
-
fulltrace = "<pre>#{fulltrace}</pre>"
|
769
|
+
fulltrace = "<pre>#{h(fulltrace)}</pre>"
|
571
770
|
|
572
771
|
apptrace = Gin.app_trace(err.backtrace).join("\n")
|
573
|
-
apptrace = "<pre>#{apptrace}</pre>" unless apptrace.empty?
|
772
|
+
apptrace = "<pre>#{h(apptrace)}</pre>" unless apptrace.empty?
|
574
773
|
|
575
|
-
DEV_ERROR_HTML %
|
774
|
+
DEV_ERROR_HTML %
|
775
|
+
[h(err.class), h(err.class), h(err.message), apptrace, fulltrace]
|
576
776
|
|
577
777
|
else
|
578
778
|
code ||= status
|
@@ -588,6 +788,14 @@ class Gin::Controller
|
|
588
788
|
end
|
589
789
|
|
590
790
|
|
791
|
+
##
|
792
|
+
# HTML-escape the given String.
|
793
|
+
|
794
|
+
def h obj
|
795
|
+
CGI.escapeHTML obj.to_s
|
796
|
+
end
|
797
|
+
|
798
|
+
|
591
799
|
private
|
592
800
|
|
593
801
|
|