gin 1.1.2 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.rdoc +22 -1
- data/Manifest.txt +7 -0
- data/TODO.rdoc +45 -0
- data/bin/gin +105 -35
- data/lib/gin.rb +7 -1
- data/lib/gin/app.rb +328 -59
- data/lib/gin/asset_manifest.rb +178 -0
- data/lib/gin/asset_pipeline.rb +235 -0
- data/lib/gin/cache.rb +36 -0
- data/lib/gin/config.rb +3 -1
- data/lib/gin/constants.rb +6 -1
- data/lib/gin/controller.rb +180 -17
- data/lib/gin/core_ext/float.rb +10 -0
- data/lib/gin/core_ext/time.rb +41 -0
- data/lib/gin/filterable.rb +5 -5
- data/lib/gin/mountable.rb +100 -0
- data/lib/gin/request.rb +4 -12
- data/lib/gin/response.rb +4 -2
- data/lib/gin/router.rb +110 -37
- data/lib/gin/strict_hash.rb +33 -0
- data/lib/gin/test.rb +8 -4
- data/lib/gin/worker.rb +49 -0
- data/test/mock_app.rb +7 -7
- data/test/test_app.rb +266 -17
- data/test/test_cache.rb +73 -5
- data/test/test_config.rb +4 -4
- data/test/test_controller.rb +158 -32
- data/test/test_filterable.rb +16 -1
- data/test/test_gin.rb +7 -6
- data/test/test_request.rb +6 -1
- data/test/test_response.rb +1 -1
- data/test/test_router.rb +156 -34
- data/test/test_test.rb +51 -45
- metadata +42 -14
- checksums.yaml +0 -7
data/History.rdoc
CHANGED
@@ -1,7 +1,28 @@
|
|
1
|
+
=== 1.2.0 / 2013-11-18
|
2
|
+
|
3
|
+
* Major Enhancements
|
4
|
+
* Rubinius officially supported
|
5
|
+
* Support for rewriting or rerouting requests from the controller
|
6
|
+
* Support generic App-mountable controller-like objects
|
7
|
+
|
8
|
+
* Minor Enhancements
|
9
|
+
* Allow enforcing server name for routing
|
10
|
+
* Cache#increase and Cache#decrease for numeric values
|
11
|
+
* More robust indifferent access hash implementation
|
12
|
+
* Removed support for passing default verb to Mount#default
|
13
|
+
|
14
|
+
* Bugfixes
|
15
|
+
* Fix for ignored exclusions when adding filter to filter chain
|
16
|
+
* Fix for default controller create action request path mapping
|
17
|
+
* Reload bugfix which didn't recognize Controller classes in routes
|
18
|
+
* Fix to remove slashes from namespaced controller route names
|
19
|
+
* Don't convert numeric params that start with 0 to Integers
|
20
|
+
* Support negative numbers when converting param types
|
21
|
+
|
1
22
|
=== 1.1.2 / 2013-07-19
|
2
23
|
|
3
24
|
* Minor Enhancements
|
4
|
-
*
|
25
|
+
* Use the Ruby Logger instead of $stdout as the default logger
|
5
26
|
|
6
27
|
* Bugfixes
|
7
28
|
* Fixes to the RWLock which made reads blocking in some instances
|
data/Manifest.txt
CHANGED
@@ -5,22 +5,29 @@ Rakefile
|
|
5
5
|
bin/gin
|
6
6
|
lib/gin.rb
|
7
7
|
lib/gin/app.rb
|
8
|
+
lib/gin/asset_manifest.rb
|
9
|
+
lib/gin/asset_pipeline.rb
|
8
10
|
lib/gin/cache.rb
|
9
11
|
lib/gin/config.rb
|
10
12
|
lib/gin/constants.rb
|
11
13
|
lib/gin/controller.rb
|
12
14
|
lib/gin/core_ext/cgi.rb
|
15
|
+
lib/gin/core_ext/float.rb
|
13
16
|
lib/gin/core_ext/gin_class.rb
|
14
17
|
lib/gin/core_ext/rack_commonlogger.rb
|
18
|
+
lib/gin/core_ext/time.rb
|
15
19
|
lib/gin/errorable.rb
|
16
20
|
lib/gin/filterable.rb
|
21
|
+
lib/gin/mountable.rb
|
17
22
|
lib/gin/reloadable.rb
|
18
23
|
lib/gin/request.rb
|
19
24
|
lib/gin/response.rb
|
20
25
|
lib/gin/router.rb
|
21
26
|
lib/gin/rw_lock.rb
|
22
27
|
lib/gin/stream.rb
|
28
|
+
lib/gin/strict_hash.rb
|
23
29
|
lib/gin/test.rb
|
30
|
+
lib/gin/worker.rb
|
24
31
|
public/400.html
|
25
32
|
public/404.html
|
26
33
|
public/500.html
|
data/TODO.rdoc
CHANGED
@@ -1,4 +1,49 @@
|
|
1
|
+
== Asset Pipeline TODO
|
2
|
+
- Write docs
|
3
|
+
- Write tests
|
4
|
+
|
5
|
+
- Consider app reload on file change
|
6
|
+
- Consider auto-reload of browser page on app reload
|
7
|
+
|
8
|
+
|
9
|
+
- Investigate image sprite generation
|
10
|
+
- Investigate HTML helpers
|
11
|
+
|
12
|
+
- Extend App.mount support:
|
13
|
+
mount "/path/to/dir", "/public"
|
14
|
+
mount MyResource, "/"
|
15
|
+
|
16
|
+
- Static actions cache
|
17
|
+
- Perhaps with the implementation of a 'static' method on Router
|
18
|
+
- Cache and ETag page based on params => could lead to attack to bloat memory
|
19
|
+
|
20
|
+
mount HomeController, '/' do
|
21
|
+
get :index, '/', :cache => true
|
22
|
+
get :index, '/', :cache => 300 # 5 minutes
|
23
|
+
end
|
24
|
+
|
25
|
+
- API description/resource support (maybe on mount)
|
26
|
+
- Example:
|
27
|
+
mount UserResource #=> includes Gin::RestResource
|
28
|
+
- Renders a static file to check for API changes on test
|
29
|
+
|
30
|
+
|
31
|
+
= Done
|
32
|
+
|
33
|
+
- Get multiple Gin apps to render assets to the same dir
|
34
|
+
|
35
|
+
- Single asset-checking process that rebuilds assets
|
36
|
+
- Only updates changed assets
|
37
|
+
- Dev mode checks if asset generation process is done (maybe always happens on boot)
|
38
|
+
|
39
|
+
|
1
40
|
= Maybe
|
2
41
|
- Find a better default non-locking logger
|
42
|
+
|
3
43
|
- Bundler-runtime-free
|
4
44
|
- script/server equivalent
|
45
|
+
|
46
|
+
- Gin common background or startup process for offsetting tasks such as asset generation?
|
47
|
+
- Sync mechanism for having a task be done by a unique process/thread
|
48
|
+
- Any number of processes/threads can do the asset file updating with flock
|
49
|
+
- Let user choose number of processes to do startup tasks.
|
data/bin/gin
CHANGED
@@ -1,23 +1,119 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
$: << File.join(File.dirname(__FILE__), "../lib") if $0 == "bin/gin"
|
4
|
+
require 'rubygems'
|
4
5
|
require 'gin'
|
6
|
+
require 'optparse'
|
5
7
|
|
6
8
|
class Gin::Cmd
|
9
|
+
|
10
|
+
def self.parse_args argv
|
11
|
+
options = {}
|
12
|
+
|
13
|
+
opts = OptionParser.new do |opt|
|
14
|
+
opt.program_name = File.basename $0
|
15
|
+
opt.version = Gin::VERSION
|
16
|
+
opt.release = nil
|
17
|
+
|
18
|
+
opt.banner = <<-STR
|
19
|
+
|
20
|
+
#{opt.program_name}-#{opt.version}
|
21
|
+
|
22
|
+
Create and control Gin applications.
|
23
|
+
|
24
|
+
Usage:
|
25
|
+
#{opt.program_name} --help
|
26
|
+
#{opt.program_name} --version
|
27
|
+
|
28
|
+
#{opt.program_name} <app-path> [options]
|
29
|
+
|
30
|
+
Options:
|
31
|
+
STR
|
32
|
+
|
33
|
+
opt.on('-b', '--bare', 'Create lightweight Gin app') do
|
34
|
+
options[:bare] = true
|
35
|
+
end
|
36
|
+
|
37
|
+
opt.on('-m', '--middleware', 'Create a Gin app as middleware') do
|
38
|
+
options[:middleware] = true
|
39
|
+
end
|
40
|
+
|
41
|
+
opt.on('-A', '--assets', 'Render assets for the given Gin app') do
|
42
|
+
options[:action] = :render_assets
|
43
|
+
end
|
44
|
+
|
45
|
+
opt.separator nil
|
46
|
+
|
47
|
+
opt.on('-h', '--help', 'Print this help screen') do
|
48
|
+
puts opt
|
49
|
+
exit
|
50
|
+
end
|
51
|
+
|
52
|
+
opt.on('-v', '--version', 'Output Gin version and exit') do
|
53
|
+
puts Gin::VERSION
|
54
|
+
exit
|
55
|
+
end
|
56
|
+
|
57
|
+
opt.separator nil
|
58
|
+
end
|
59
|
+
|
60
|
+
opts.parse! argv
|
61
|
+
|
62
|
+
options[:app_path] = argv.shift
|
63
|
+
error("Missing app-path argument. Use gin -h for help.") if
|
64
|
+
!options[:app_path]
|
65
|
+
|
66
|
+
error("Invalid arguments #{argv.join(", ")}.") unless argv.empty?
|
67
|
+
|
68
|
+
options
|
69
|
+
end
|
70
|
+
|
71
|
+
|
7
72
|
def self.run argv=ARGV
|
8
|
-
|
73
|
+
options = parse_args(argv)
|
9
74
|
|
10
|
-
|
75
|
+
case options[:action]
|
76
|
+
when :render_assets
|
77
|
+
render_assets(options)
|
78
|
+
else
|
79
|
+
create_app(options)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
def self.render_assets options
|
85
|
+
app_path = File.expand_path(options[:app_path])
|
86
|
+
app_filepath = nil
|
87
|
+
|
88
|
+
if File.file?(app_path)
|
89
|
+
app_filepath = app_path
|
90
|
+
|
91
|
+
elsif File.directory?(app_path)
|
92
|
+
["#{File.basename(app_path)}.rb", "app.rb", "application.rb"].each do |name|
|
93
|
+
filepath = File.join(app_path, name)
|
94
|
+
app_filepath = filepath and break if File.file?(filepath)
|
95
|
+
end
|
96
|
+
end
|
11
97
|
|
12
|
-
|
13
|
-
error("Missing app_name argument. Use gin -h for help.") if !path
|
98
|
+
error("No such file or directory: #{options[:app_path]}") if !app_filepath
|
14
99
|
|
15
|
-
|
100
|
+
$: << File.dirname(app_filepath)
|
101
|
+
require app_filepath
|
102
|
+
|
103
|
+
Gin::App.each do |app_klass|
|
104
|
+
puts "Generating assets for #{app_klass.name}..."
|
105
|
+
app_klass.new(:force_asset_pipeline => false).send(:create_asset_pipeline)
|
106
|
+
end
|
107
|
+
end
|
16
108
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
109
|
+
|
110
|
+
def self.create_app options
|
111
|
+
bare = options[:bare]
|
112
|
+
path = options[:app_path].sub(/\.\w+$/, '')
|
113
|
+
standalone = !options[:middleware]
|
114
|
+
|
115
|
+
name = File.basename(path)
|
116
|
+
dir = File.expand_path(path)
|
21
117
|
|
22
118
|
make_dirs(dir, bare)
|
23
119
|
|
@@ -38,32 +134,6 @@ class Gin::Cmd
|
|
38
134
|
end
|
39
135
|
|
40
136
|
|
41
|
-
def self.show_help
|
42
|
-
puts <<-STR
|
43
|
-
|
44
|
-
gin #{Gin::VERSION}
|
45
|
-
|
46
|
-
Create a new Gin application.
|
47
|
-
|
48
|
-
gin <path/to/app_name>
|
49
|
-
|
50
|
-
Examples:
|
51
|
-
|
52
|
-
$ gin my_website
|
53
|
-
$ gin my_api --bare
|
54
|
-
|
55
|
-
Options:
|
56
|
-
--bare Don't create view-related files/dirs
|
57
|
-
-h --help Show this screen
|
58
|
-
|
59
|
-
Gin applications created in a directory containing
|
60
|
-
a *.ru file will not generate its own config.ru.
|
61
|
-
|
62
|
-
STR
|
63
|
-
true
|
64
|
-
end
|
65
|
-
|
66
|
-
|
67
137
|
def self.make_dirs dir, bare=false
|
68
138
|
error("Can't create directory: #{dir} already exists") if File.directory?(dir)
|
69
139
|
|
data/lib/gin.rb
CHANGED
@@ -3,7 +3,7 @@ require 'rack'
|
|
3
3
|
require 'tilt'
|
4
4
|
|
5
5
|
class Gin
|
6
|
-
VERSION = '1.
|
6
|
+
VERSION = '1.2.0'
|
7
7
|
|
8
8
|
LIB_DIR = File.expand_path("..", __FILE__) #:nodoc:
|
9
9
|
PUBLIC_DIR = File.expand_path("../../public/", __FILE__) #:nodoc:
|
@@ -22,6 +22,8 @@ class Gin
|
|
22
22
|
|
23
23
|
class TemplateMissing < Error; end
|
24
24
|
|
25
|
+
class RouterError < Error; end
|
26
|
+
|
25
27
|
|
26
28
|
##
|
27
29
|
# Change string to underscored version.
|
@@ -122,9 +124,12 @@ class Gin
|
|
122
124
|
|
123
125
|
|
124
126
|
require 'gin/core_ext/cgi'
|
127
|
+
require 'gin/core_ext/time'
|
128
|
+
require 'gin/core_ext/float'
|
125
129
|
require 'gin/core_ext/gin_class'
|
126
130
|
require 'gin/core_ext/rack_commonlogger'
|
127
131
|
|
132
|
+
require 'gin/strict_hash'
|
128
133
|
require 'gin/constants'
|
129
134
|
require 'gin/cache'
|
130
135
|
require 'gin/router'
|
@@ -135,6 +140,7 @@ class Gin
|
|
135
140
|
require 'gin/stream'
|
136
141
|
require 'gin/errorable'
|
137
142
|
require 'gin/filterable'
|
143
|
+
require 'gin/mountable'
|
138
144
|
require 'gin/controller'
|
139
145
|
require 'gin/app'
|
140
146
|
end
|
data/lib/gin/app.rb
CHANGED
@@ -15,8 +15,6 @@ class Gin::App
|
|
15
15
|
extend GinClass
|
16
16
|
include Gin::Constants
|
17
17
|
|
18
|
-
class RouterError < Gin::Error; end
|
19
|
-
|
20
18
|
CALLERS_TO_IGNORE = [ # :nodoc:
|
21
19
|
/lib\/gin(\/(.*?))?\.rb/, # all gin code
|
22
20
|
/lib\/tilt.*\.rb/, # all tilt code
|
@@ -28,6 +26,13 @@ class Gin::App
|
|
28
26
|
/src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files
|
29
27
|
]
|
30
28
|
|
29
|
+
@@app_klasses = {} #:nodoc:
|
30
|
+
|
31
|
+
|
32
|
+
def self.each &block #:nodoc:
|
33
|
+
@@app_klasses.values.each(&block)
|
34
|
+
end
|
35
|
+
|
31
36
|
|
32
37
|
def self.inherited subclass #:nodoc:
|
33
38
|
subclass.setup
|
@@ -39,11 +44,14 @@ class Gin::App
|
|
39
44
|
filepath = File.expand_path(caller_line.split(/:\d+:in `/).first)
|
40
45
|
dir = File.dirname(filepath)
|
41
46
|
|
47
|
+
@@app_klasses[self.name] = self unless self.name == "Gin::App"
|
48
|
+
|
42
49
|
@source_file = filepath
|
50
|
+
@app_name = Gin.underscore(self.name).gsub("/", ".")
|
43
51
|
@source_class = self.to_s
|
44
52
|
@templates = Gin::Cache.new
|
45
53
|
@md5s = Gin::Cache.new
|
46
|
-
@
|
54
|
+
@reload_lock = Gin::RWLock.new
|
47
55
|
@autoreload = nil
|
48
56
|
@instance = nil
|
49
57
|
|
@@ -81,6 +89,15 @@ class Gin::App
|
|
81
89
|
end
|
82
90
|
|
83
91
|
|
92
|
+
##
|
93
|
+
# Get or set the app name.
|
94
|
+
|
95
|
+
def self.app_name name=nil
|
96
|
+
@app_name = name if name
|
97
|
+
@app_name
|
98
|
+
end
|
99
|
+
|
100
|
+
|
84
101
|
##
|
85
102
|
# Returns the source file of the current app.
|
86
103
|
|
@@ -188,6 +205,79 @@ class Gin::App
|
|
188
205
|
end
|
189
206
|
|
190
207
|
|
208
|
+
##
|
209
|
+
# Turn asset pipelining on or off. Defaults to turned on if any asset paths
|
210
|
+
# are present at boot time.
|
211
|
+
# asset_pipeline true # Force ON
|
212
|
+
# asset_pipeline false # Force OFF
|
213
|
+
#
|
214
|
+
# If you would rather handle asset generation manually through the `gin -A`
|
215
|
+
# command for production purposes, you can set asset_pipieline to false. The
|
216
|
+
# app will still know where to look for rendered assets.
|
217
|
+
# asset_pipeline ENV['RACK_ENV'] == 'development'
|
218
|
+
#
|
219
|
+
# Passing a block will allow for configuration for Sprockets asset pipelining.
|
220
|
+
# Passed block yields a Sprockets::Environment instance.
|
221
|
+
# asset_pipeline do |sprockets|
|
222
|
+
# sprockets.append_path 'foo/assets'
|
223
|
+
# end
|
224
|
+
#
|
225
|
+
# To keep memory usage low, Gin avoids loading Sprockets during the App
|
226
|
+
# runtime. If you however need Sprockets at runtime for your app,
|
227
|
+
# you may also pass a Sprockets::Environment instance to the
|
228
|
+
# asset_pipeline method:
|
229
|
+
# sprockets = Sprockets::Environment.new
|
230
|
+
# asset_pipeline sprockets
|
231
|
+
|
232
|
+
def self.asset_pipeline val=nil, &block
|
233
|
+
@options[:force_asset_pipeline] = val if !val.nil?
|
234
|
+
@options[:asset_config] = block if block_given?
|
235
|
+
@options[:force_asset_pipeline]
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
##
|
240
|
+
# Get or set the globs to find asset files.
|
241
|
+
# asset_paths "/path/to/other/shared/assets/*/"
|
242
|
+
#
|
243
|
+
# These globs will be expanded and added to the asset_pipeline Sprockets
|
244
|
+
# instance.
|
245
|
+
#
|
246
|
+
# Default globs are:
|
247
|
+
# <root_dir>/assets/
|
248
|
+
# <root_dir>/assets/*/
|
249
|
+
# <root_dir>/lib/**/assets/
|
250
|
+
# <root_dir>/lib/**/assets/*/
|
251
|
+
# <root_dir>/vendor/**/assets/
|
252
|
+
# <root_dir>/vendor/**/assets/*/
|
253
|
+
|
254
|
+
def self.asset_paths *paths
|
255
|
+
@options[:asset_paths] ||=
|
256
|
+
DEFAULT_ASSET_PATHS.map{|pa| File.join(root_dir, *pa) }
|
257
|
+
@options[:asset_paths].concat(paths) if !paths.empty?
|
258
|
+
@options[:asset_paths]
|
259
|
+
end
|
260
|
+
|
261
|
+
DEFAULT_ASSET_PATHS = [ #:nodoc:
|
262
|
+
%w{assets */},
|
263
|
+
%w{lib ** assets */},
|
264
|
+
%w{vendor ** assets */},
|
265
|
+
%w{assets/},
|
266
|
+
%w{lib ** assets/},
|
267
|
+
%w{vendor ** assets/}
|
268
|
+
]
|
269
|
+
|
270
|
+
|
271
|
+
##
|
272
|
+
# Directory where the asset pipeline should render static assets to.
|
273
|
+
# Defaults to <public_dir>/assets
|
274
|
+
|
275
|
+
def self.assets_dir dir=nil
|
276
|
+
@options[:assets_dir] = dir if dir
|
277
|
+
@options[:assets_dir] || File.join(public_dir, "assets")
|
278
|
+
end
|
279
|
+
|
280
|
+
|
191
281
|
##
|
192
282
|
# Get or set the CDN asset host (and path).
|
193
283
|
# If block is given, evaluates the block on every read.
|
@@ -201,9 +291,9 @@ class Gin::App
|
|
201
291
|
|
202
292
|
def self.make_config opts={} # :nodoc:
|
203
293
|
Gin::Config.new opts[:environment] || self.environment,
|
204
|
-
dir
|
205
|
-
logger
|
206
|
-
ttl
|
294
|
+
:dir => opts[:config_dir] || self.config_dir,
|
295
|
+
:logger => opts[:logger] || self.logger,
|
296
|
+
:ttl => opts[:config_reload] || self.config_reload
|
207
297
|
end
|
208
298
|
|
209
299
|
|
@@ -318,6 +408,23 @@ class Gin::App
|
|
318
408
|
end
|
319
409
|
|
320
410
|
|
411
|
+
##
|
412
|
+
# Get or set the Host header on responses if not set by the called action.
|
413
|
+
# If the enforce options is given, the app will only respond to requests that
|
414
|
+
# explicitly match the specified host, or regexp.
|
415
|
+
# This is useful for running multiple apps on the same middleware stack that
|
416
|
+
# might have conflicting routes, or need explicit per-host routing (such
|
417
|
+
# as an admin app).
|
418
|
+
# hostname 'host.com'
|
419
|
+
# hostname 'admin.host.com:443', :enforce => true
|
420
|
+
# hostname 'admin.host.com', :enforce => /^admin\.(localhost|host\.com)/
|
421
|
+
|
422
|
+
def self.hostname host=nil, opts={}
|
423
|
+
@options[:host] = {:name => host}.merge(opts) if host
|
424
|
+
@options[:host][:name] if @options[:host]
|
425
|
+
end
|
426
|
+
|
427
|
+
|
321
428
|
##
|
322
429
|
# Get or set the layout name. Layout file location is assumed to be in
|
323
430
|
# the views_dir. If the views dir has a controller wildcard '*', the layout
|
@@ -413,8 +520,8 @@ class Gin::App
|
|
413
520
|
end
|
414
521
|
|
415
522
|
|
416
|
-
def self.
|
417
|
-
@
|
523
|
+
def self.reload_lock # :nodoc:
|
524
|
+
@reload_lock
|
418
525
|
end
|
419
526
|
|
420
527
|
|
@@ -493,8 +600,10 @@ class Gin::App
|
|
493
600
|
opt_reader :error_delegate, :router, :logger
|
494
601
|
opt_reader :layout, :layouts_dir, :views_dir, :template_engines
|
495
602
|
opt_reader :root_dir, :public_dir, :environment
|
603
|
+
opt_reader :asset_paths, :assets_dir, :asset_config, :force_asset_pipeline
|
496
604
|
|
497
|
-
class_proxy :mime_type, :md5s, :templates, :
|
605
|
+
class_proxy :mime_type, :md5s, :templates, :reload_lock, :autoreload
|
606
|
+
class_proxy :app_name
|
498
607
|
|
499
608
|
# App to fallback on if Gin::App is used as middleware and no route is found.
|
500
609
|
attr_reader :rack_app
|
@@ -519,22 +628,28 @@ class Gin::App
|
|
519
628
|
end
|
520
629
|
|
521
630
|
@options = {
|
522
|
-
config_dir
|
523
|
-
public_dir
|
524
|
-
|
525
|
-
|
526
|
-
|
631
|
+
:config_dir => self.class.config_dir,
|
632
|
+
:public_dir => self.class.public_dir,
|
633
|
+
:assets_dir => self.class.assets_dir,
|
634
|
+
:asset_paths => self.class.asset_paths,
|
635
|
+
:layouts_dir => self.class.layouts_dir,
|
636
|
+
:views_dir => self.class.views_dir,
|
637
|
+
:config => self.class.config
|
527
638
|
}.merge(self.class.options).merge(options)
|
528
639
|
|
529
640
|
@options[:config] = self.class.make_config(@options) if
|
530
|
-
@options[:environment]
|
531
|
-
@options[:config_dir]
|
641
|
+
@options[:environment] != @options[:config].environment ||
|
642
|
+
@options[:config_dir] != @options[:config].dir ||
|
532
643
|
@options[:config_reload] != @options[:config].ttl
|
533
644
|
|
645
|
+
@override_options = options if autoreload
|
646
|
+
|
534
647
|
validate_all_controllers!
|
535
648
|
|
536
649
|
@app = self
|
537
650
|
@stack = build_app Rack::Builder.new
|
651
|
+
|
652
|
+
setup_asset_pipeline
|
538
653
|
end
|
539
654
|
|
540
655
|
|
@@ -590,6 +705,32 @@ class Gin::App
|
|
590
705
|
end
|
591
706
|
|
592
707
|
|
708
|
+
STATIC_PATH_CLEANER = %r{\.+/|/\.+} #:nodoc:
|
709
|
+
|
710
|
+
##
|
711
|
+
# Check if an asset exists.
|
712
|
+
# Returns the full path to the asset if found, otherwise nil.
|
713
|
+
# Does not support ./ or ../ for security reasons.
|
714
|
+
|
715
|
+
def asset path
|
716
|
+
path = path.gsub STATIC_PATH_CLEANER, ""
|
717
|
+
|
718
|
+
pub_filepath = File.expand_path(File.join(public_dir, path))
|
719
|
+
return pub_filepath if File.file? pub_filepath
|
720
|
+
|
721
|
+
ast_filepath = File.expand_path(File.join(assets_dir, path))
|
722
|
+
return ast_filepath if File.file? ast_filepath
|
723
|
+
|
724
|
+
pub_filepath.sub!(/(\.\w+)$/, '-*\1')
|
725
|
+
ast_filepath.sub!(/(\.\w+)$/, '-*\1')
|
726
|
+
filepath = Dir[pub_filepath, ast_filepath].first
|
727
|
+
return filepath if filepath
|
728
|
+
|
729
|
+
gin_filepath = File.expand_path(File.join(Gin::PUBLIC_DIR, path))
|
730
|
+
return gin_filepath if File.file? gin_filepath
|
731
|
+
end
|
732
|
+
|
733
|
+
|
593
734
|
##
|
594
735
|
# Returns the asset host for a given asset name. This is useful when assigning
|
595
736
|
# a block for the asset_host. The asset_name argument is passed to the block.
|
@@ -618,15 +759,21 @@ class Gin::App
|
|
618
759
|
end
|
619
760
|
|
620
761
|
|
621
|
-
MD5 = RUBY_PLATFORM =~ /darwin/ ? 'md5 -q' : 'md5sum' #:nodoc:
|
622
|
-
|
623
762
|
##
|
624
763
|
# Returns the first 8 characters of a file's MD5 hash.
|
625
764
|
# Values are cached for future reference.
|
626
765
|
|
627
766
|
def md5 path
|
628
767
|
return unless File.file?(path)
|
629
|
-
self.md5s[path] ||=
|
768
|
+
self.md5s[path] ||= Digest::MD5.file(path).hexdigest[0...8]
|
769
|
+
end
|
770
|
+
|
771
|
+
|
772
|
+
##
|
773
|
+
# The name defined by App.hostname.
|
774
|
+
|
775
|
+
def hostname
|
776
|
+
@options[:host][:name] if @options[:host]
|
630
777
|
end
|
631
778
|
|
632
779
|
|
@@ -673,7 +820,7 @@ class Gin::App
|
|
673
820
|
# If you use this in production, you're gonna have a bad time.
|
674
821
|
|
675
822
|
def reload!
|
676
|
-
|
823
|
+
reload_lock.write_sync do
|
677
824
|
self.class.erase_dependencies!
|
678
825
|
|
679
826
|
if File.extname(self.class.source_file) != ".ru"
|
@@ -683,7 +830,7 @@ class Gin::App
|
|
683
830
|
require self.class.source_file
|
684
831
|
end
|
685
832
|
|
686
|
-
@app = self.class.source_class.new @rack_app, @
|
833
|
+
@app = self.class.source_class.new @rack_app, @override_options
|
687
834
|
end
|
688
835
|
end
|
689
836
|
|
@@ -692,30 +839,47 @@ class Gin::App
|
|
692
839
|
# Default Rack call method.
|
693
840
|
|
694
841
|
def call env
|
695
|
-
try_autoreload(env)
|
842
|
+
try_autoreload(env) do
|
843
|
+
valid_host = valid_host?(env)
|
696
844
|
|
697
|
-
|
698
|
-
|
845
|
+
resp =
|
846
|
+
if valid_host && @app.route!(env)
|
847
|
+
@app.call!(env)
|
699
848
|
|
700
|
-
|
701
|
-
|
849
|
+
elsif valid_host && @app.static!(env)
|
850
|
+
@app.call_static(env)
|
702
851
|
|
703
|
-
|
704
|
-
|
852
|
+
elsif @rack_app
|
853
|
+
@rack_app.call(env)
|
705
854
|
|
706
|
-
|
707
|
-
|
855
|
+
elsif !valid_host
|
856
|
+
bt = caller
|
857
|
+
msg = "No route for host '%s:%s'" % [env[SERVER_NAME], env[SERVER_PORT]]
|
858
|
+
err = Gin::BadRequest.new(msg)
|
859
|
+
err.set_backtrace(bt)
|
860
|
+
handle_error(err, env)
|
861
|
+
|
862
|
+
else
|
863
|
+
@app.call!(env)
|
864
|
+
end
|
865
|
+
|
866
|
+
resp[1][HOST_NAME] ||=
|
867
|
+
(hostname || env[SERVER_NAME]).sub(/(:[0-9]+)?$/, ":#{env[SERVER_PORT]}")
|
868
|
+
|
869
|
+
resp
|
708
870
|
end
|
709
871
|
end
|
710
872
|
|
711
873
|
|
712
874
|
##
|
713
|
-
# Check if autoreload is needed and
|
875
|
+
# Check if autoreload is needed, reload, and/or run block in a
|
876
|
+
# thread-safe manner.
|
714
877
|
|
715
|
-
def try_autoreload env
|
716
|
-
return if env[GIN_RELOADED] || !autoreload
|
878
|
+
def try_autoreload env, &block
|
879
|
+
return block.call if env[GIN_RELOADED] || !autoreload
|
717
880
|
env[GIN_RELOADED] = true
|
718
881
|
reload!
|
882
|
+
reload_lock.read_sync{ block.call }
|
719
883
|
end
|
720
884
|
|
721
885
|
|
@@ -724,7 +888,7 @@ class Gin::App
|
|
724
888
|
|
725
889
|
def call! env
|
726
890
|
if env[GIN_STACK]
|
727
|
-
dispatch env
|
891
|
+
dispatch env
|
728
892
|
|
729
893
|
else
|
730
894
|
env[GIN_STACK] = true
|
@@ -764,51 +928,68 @@ class Gin::App
|
|
764
928
|
|
765
929
|
##
|
766
930
|
# Check if the request routes to a controller and action and set
|
767
|
-
# gin.
|
768
|
-
# and gin.http_route env variables.
|
931
|
+
# gin.target, gin.path_query_hash, and gin.http_route env variables.
|
769
932
|
# Returns true if a route is found, otherwise false.
|
770
933
|
|
771
934
|
def route! env
|
772
935
|
http_route = "#{env[REQ_METHOD]} #{env[PATH_INFO]}"
|
773
936
|
return true if env[GIN_ROUTE] == http_route
|
774
937
|
|
775
|
-
env[
|
938
|
+
env[GIN_TARGET], env[GIN_PATH_PARAMS] =
|
776
939
|
router.resources_for env[REQ_METHOD], env[PATH_INFO]
|
777
940
|
|
778
941
|
env[GIN_ROUTE] = http_route
|
779
942
|
|
780
|
-
!!
|
943
|
+
!!env[GIN_TARGET]
|
781
944
|
end
|
782
945
|
|
783
946
|
|
784
|
-
|
947
|
+
def rewrite_env env, *args # :nodoc:
|
948
|
+
headers = args.pop if Hash === args.last && Hash === args[-2] && args[-2] != args[-1]
|
949
|
+
params = args.pop if Hash === args.last
|
950
|
+
|
951
|
+
route = if String === args.first
|
952
|
+
verb = (headers && headers[REQ_METHOD] || 'GET').upcase
|
953
|
+
Gin::Router::Route.new(verb, args[0])
|
954
|
+
else
|
955
|
+
@app.router.route_to(*args)
|
956
|
+
end
|
957
|
+
|
958
|
+
new_env = env.dup
|
959
|
+
new_env.delete_if{|k, v| k.start_with?('gin.') }
|
960
|
+
new_env[GIN_RELOADED] = env[GIN_RELOADED]
|
961
|
+
new_env[GIN_TIMESTAMP] = env[GIN_TIMESTAMP]
|
962
|
+
|
963
|
+
new_env.merge!(headers) if headers
|
964
|
+
route.to_env(params, new_env)
|
965
|
+
end
|
966
|
+
|
785
967
|
|
786
968
|
##
|
787
|
-
#
|
788
|
-
#
|
789
|
-
#
|
969
|
+
# Rewrites the given Rack env and processes the new request.
|
970
|
+
# You're probably looking for Gin::Controller#rewrite or
|
971
|
+
# Gin::Controller#reroute.
|
790
972
|
|
791
|
-
def
|
792
|
-
|
973
|
+
def rewrite! env, *args
|
974
|
+
new_env = rewrite_env(env, *args)
|
793
975
|
|
794
|
-
|
795
|
-
|
976
|
+
logger << "[REWRITE] %s %s -> %s %s\n" %
|
977
|
+
[env[REQ_METHOD], env[PATH_INFO], new_env[REQ_METHOD], new_env[PATH_INFO]]
|
796
978
|
|
797
|
-
|
798
|
-
return filepath if File.file? filepath
|
979
|
+
call(new_env)
|
799
980
|
end
|
800
981
|
|
801
982
|
|
802
983
|
##
|
803
984
|
# Dispatch the Rack env to the given controller and action.
|
804
985
|
|
805
|
-
def dispatch env
|
986
|
+
def dispatch env
|
806
987
|
raise Gin::NotFound,
|
807
988
|
"No route exists for: #{env[REQ_METHOD]} #{env[PATH_INFO]}" unless
|
808
|
-
|
989
|
+
env[GIN_TARGET]
|
809
990
|
|
810
|
-
env[
|
811
|
-
env[
|
991
|
+
env[GIN_APP] = self
|
992
|
+
env[GIN_TARGET][0].call(env)
|
812
993
|
|
813
994
|
rescue ::Exception => err
|
814
995
|
handle_error(err, env)
|
@@ -841,10 +1022,7 @@ class Gin::App
|
|
841
1022
|
now = Time.now
|
842
1023
|
time = now - env[GIN_TIMESTAMP] if env[GIN_TIMESTAMP]
|
843
1024
|
|
844
|
-
|
845
|
-
target = "#{ctrl}##{action}" if ctrl && action
|
846
|
-
target = resp[2].path if !target && resp[2].respond_to?(:path)
|
847
|
-
target = "<stream>" if !target && (!(Array === resp[2]) || !resp[2].empty?)
|
1025
|
+
target = request_target_name(env, resp)
|
848
1026
|
|
849
1027
|
logger << ( LOG_FORMAT % [
|
850
1028
|
env[FWD_FOR] || env[REMOTE_ADDR] || "-",
|
@@ -861,6 +1039,21 @@ class Gin::App
|
|
861
1039
|
end
|
862
1040
|
|
863
1041
|
|
1042
|
+
def request_target_name env, resp
|
1043
|
+
if env[GIN_TARGET]
|
1044
|
+
if Gin::Mountable === env[GIN_TARGET][0]
|
1045
|
+
env[GIN_TARGET][0].display_name(env[GIN_TARGET][1])
|
1046
|
+
else
|
1047
|
+
"#{env[GIN_TARGET][0].inspect}->#{env[GIN_TARGET][1].inspect}"
|
1048
|
+
end
|
1049
|
+
elsif resp[2].respond_to?(:path)
|
1050
|
+
resp[2].path
|
1051
|
+
elsif !(Array === resp[2]) || !resp[2].empty?
|
1052
|
+
"<stream>"
|
1053
|
+
end
|
1054
|
+
end
|
1055
|
+
|
1056
|
+
|
864
1057
|
def with_log_request env
|
865
1058
|
env[GIN_TIMESTAMP] ||= Time.now
|
866
1059
|
resp = yield
|
@@ -903,6 +1096,64 @@ class Gin::App
|
|
903
1096
|
end
|
904
1097
|
|
905
1098
|
|
1099
|
+
def asset_pipeline
|
1100
|
+
Thread.main[:"#{self.app_name}_asset_pipeline"]
|
1101
|
+
end
|
1102
|
+
|
1103
|
+
|
1104
|
+
def set_asset_pipeline pipe
|
1105
|
+
pipe_key = :"#{self.app_name}_asset_pipeline"
|
1106
|
+
|
1107
|
+
old_pipe = Thread.main[pipe_key]
|
1108
|
+
old_pipe.stop if old_pipe
|
1109
|
+
|
1110
|
+
Thread.main[pipe_key] = pipe
|
1111
|
+
end
|
1112
|
+
|
1113
|
+
|
1114
|
+
def use_asset_pipeline?
|
1115
|
+
return !!force_asset_pipeline unless force_asset_pipeline.nil?
|
1116
|
+
return false unless asset_paths
|
1117
|
+
return @use_asset_pipeline if defined? @use_asset_pipeline
|
1118
|
+
@use_asset_pipeline = !!Dir.glob(asset_paths).first
|
1119
|
+
end
|
1120
|
+
|
1121
|
+
|
1122
|
+
def setup_asset_pipeline
|
1123
|
+
return unless use_asset_pipeline?
|
1124
|
+
|
1125
|
+
if development?
|
1126
|
+
pipe = create_asset_pipeline
|
1127
|
+
pipe.listen
|
1128
|
+
set_asset_pipeline pipe
|
1129
|
+
|
1130
|
+
else
|
1131
|
+
require 'gin/worker'
|
1132
|
+
pidfile = File.join(self.root_dir, "#{self.app_name}.asset_pipeline.pid")
|
1133
|
+
w = Gin::Worker.new(pidfile){ create_asset_pipeline }
|
1134
|
+
w.run
|
1135
|
+
w.wait
|
1136
|
+
end
|
1137
|
+
end
|
1138
|
+
|
1139
|
+
|
1140
|
+
def asset_manifest_path
|
1141
|
+
File.join(self.root_dir, "#{self.app_name}.asset_manifest")
|
1142
|
+
end
|
1143
|
+
|
1144
|
+
|
1145
|
+
def create_asset_pipeline
|
1146
|
+
require 'gin/asset_pipeline'
|
1147
|
+
|
1148
|
+
pipe = Gin::AssetPipeline.new(asset_manifest_path,
|
1149
|
+
assets_dir, asset_paths, force_asset_pipeline, &asset_config)
|
1150
|
+
|
1151
|
+
pipe.logger = self.logger
|
1152
|
+
pipe.render_all if pipe.outdated?
|
1153
|
+
pipe
|
1154
|
+
end
|
1155
|
+
|
1156
|
+
|
906
1157
|
##
|
907
1158
|
# Make sure all controller actions have a route, or raise a RouterError.
|
908
1159
|
|
@@ -914,16 +1165,34 @@ class Gin::App
|
|
914
1165
|
end
|
915
1166
|
|
916
1167
|
actions_map.each do |ctrl, actions|
|
917
|
-
|
918
|
-
|
1168
|
+
next unless Gin::Mountable === ctrl
|
1169
|
+
ctrl.verify_mount!
|
1170
|
+
|
1171
|
+
not_mounted = ctrl.actions - actions
|
1172
|
+
raise Gin::RouterError, "#{ctrl.display_name(not_mounted[0])} has no route" unless
|
919
1173
|
not_mounted.empty?
|
920
1174
|
|
921
1175
|
extra_mounted = actions - ctrl.actions
|
922
|
-
raise RouterError, "#{ctrl
|
1176
|
+
raise Gin::RouterError, "#{ctrl.display_name(extra_mounted[0])} is not an action" unless
|
923
1177
|
extra_mounted.empty?
|
924
1178
|
end
|
925
1179
|
end
|
926
1180
|
|
927
1181
|
|
1182
|
+
def valid_host? env
|
1183
|
+
return true unless @options[:host] && @options[:host][:enforce]
|
1184
|
+
|
1185
|
+
name, port = @options[:host][:name].split(":", 2)
|
1186
|
+
|
1187
|
+
if @options[:host][:enforce] == true
|
1188
|
+
name == env[SERVER_NAME] && (port.nil? || port == env[SERVER_PORT])
|
1189
|
+
else
|
1190
|
+
@options[:host][:enforce] === "#{env[SERVER_NAME]}:#{env[SERVER_PORT]}" ||
|
1191
|
+
@options[:host][:enforce] === env[SERVER_NAME] &&
|
1192
|
+
(port.nil? || port == env[SERVER_PORT])
|
1193
|
+
end
|
1194
|
+
end
|
1195
|
+
|
1196
|
+
|
928
1197
|
setup
|
929
1198
|
end
|