gin 1.0.4 → 1.1.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/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
|
|