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.
@@ -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