gin 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- * User the Ruby Logger instead of $stdout as the default logger
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
@@ -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
- show_help if argv.empty? || argv.delete("-h") || argv.delete("--help")
73
+ options = parse_args(argv)
9
74
 
10
- bare = !!argv.delete("--bare")
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
- path = argv.pop
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
- error("Invalid options #{argv.join(", ")}.") unless argv.empty?
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
- name = File.basename(path)
18
- dir = File.expand_path(path)
19
- parent_ru_path = File.expand_path(File.join(dir, "..", "*.ru"))
20
- standalone = Dir[parent_ru_path].empty?
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.1.2'
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
@@ -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
- @reload_mutex = Mutex.new
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: opts[:config_dir] || self.config_dir,
205
- logger: opts[:logger] || self.logger,
206
- ttl: opts[:config_reload] || self.config_reload
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.reload_mutex # :nodoc:
417
- @reload_mutex
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, :reload_mutex, :autoreload
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: self.class.config_dir,
523
- public_dir: self.class.public_dir,
524
- layouts_dir: self.class.layouts_dir,
525
- views_dir: self.class.views_dir,
526
- config: self.class.config
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] != @options[:config].environment ||
531
- @options[:config_dir] != @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] ||= `#{MD5} #{path}`[0...8]
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
- reload_mutex.synchronize do
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, @options
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
- if @app.route!(env)
698
- @app.call!(env)
845
+ resp =
846
+ if valid_host && @app.route!(env)
847
+ @app.call!(env)
699
848
 
700
- elsif @app.static!(env)
701
- @app.call_static(env)
849
+ elsif valid_host && @app.static!(env)
850
+ @app.call_static(env)
702
851
 
703
- elsif @rack_app
704
- @rack_app.call(env)
852
+ elsif @rack_app
853
+ @rack_app.call(env)
705
854
 
706
- else
707
- @app.call!(env)
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 reload.
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, env[GIN_CTRL], env[GIN_ACTION]
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.controller, gin.action, gin.path_query_hash,
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[GIN_CTRL], env[GIN_ACTION], env[GIN_PATH_PARAMS] =
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
- !!(env[GIN_CTRL] && env[GIN_ACTION])
943
+ !!env[GIN_TARGET]
781
944
  end
782
945
 
783
946
 
784
- STATIC_PATH_CLEANER = %r{\.+/|/\.+} #:nodoc:
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
- # Check if an asset exists.
788
- # Returns the full path to the asset if found, otherwise nil.
789
- # Does not support ./ or ../ for security reasons.
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 asset path
792
- path = path.gsub STATIC_PATH_CLEANER, ""
973
+ def rewrite! env, *args
974
+ new_env = rewrite_env(env, *args)
793
975
 
794
- filepath = File.expand_path(File.join(public_dir, path))
795
- return filepath if File.file? filepath
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
- filepath = File.expand_path(File.join(Gin::PUBLIC_DIR, path))
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, ctrl, action
986
+ def dispatch env
806
987
  raise Gin::NotFound,
807
988
  "No route exists for: #{env[REQ_METHOD]} #{env[PATH_INFO]}" unless
808
- ctrl && action
989
+ env[GIN_TARGET]
809
990
 
810
- env[GIN_CTRL] = ctrl.new(self, env)
811
- env[GIN_CTRL].call_action action
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
- ctrl, action = env[GIN_CTRL].class, env[GIN_ACTION]
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
- not_mounted = ctrl.actions - actions
918
- raise RouterError, "#{ctrl}##{not_mounted[0]} has no route." unless
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}##{extra_mounted[0]} is not a method" unless
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