gin 1.1.2 → 1.2.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 +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
|