padrino-core 0.9.28 → 0.9.29

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,79 +1,24 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../tasks')
1
+ require File.expand_path('../../tasks', __FILE__)
2
2
  require 'rake'
3
+ require 'thor'
3
4
  require 'securerandom' unless defined?(SecureRandom)
4
- Rake.application.instance_variable_set(:@rakefile, __FILE__)
5
5
 
6
6
  module PadrinoTasks
7
- def self.init
8
- Padrino::Tasks.files.flatten.uniq.each { |ext| load(ext) rescue puts "<= Failed load #{ext}" } unless @_init
9
- Rake.application.init
10
- Rake.application.top_level
11
- @_init = true
7
+ def self.init(init=false)
8
+ Padrino::Tasks.files.flatten.uniq.each { |rakefile| Rake.application.add_import(rakefile) rescue puts "<= Failed load #{ext}" }
9
+ if init
10
+ Rake.application.init
11
+ Rake.application.instance_variable_set(:@rakefile, __FILE__)
12
+ load(File.expand_path('../rake_tasks.rb', __FILE__))
13
+ Rake.application.load_imports
14
+ Rake.application.top_level
15
+ else
16
+ load(File.expand_path('../rake_tasks.rb', __FILE__))
17
+ Rake.application.load_imports
18
+ end
12
19
  end
13
20
  end
14
21
 
15
22
  def shell
16
23
  @_shell ||= Thor::Base.shell.new
17
- end
18
-
19
- # Load rake tasks from common rake task definition locations
20
- Dir["lib/tasks/**/*.rake"].
21
- concat(Dir["tasks/**/*.rake"]).
22
- concat(Dir["{test,spec}/*.rake"]).each { |ext| load(ext) }
23
-
24
- # Loads the Padrino applications mounted within the project
25
- # setting up the required environment for Padrino
26
- task :environment do
27
- Padrino.mounted_apps.each do |app|
28
- app.app_obj.setup_application!
29
- end
30
- end
31
-
32
- desc "Generate a secret key"
33
- task :secret do
34
- shell.say SecureRandom.hex(32)
35
- end
36
-
37
- # lists all routes of a given app
38
- def list_app_routes(app, args)
39
- app_routes = app.named_routes
40
- app_routes.reject! { |r| r.identifier.to_s !~ /#{args.query}/ } if args.query.present?
41
- app_routes.map! { |r| [r.verb, r.name, r.path] }
42
- return if app_routes.empty?
43
- shell.say "\nApplication: #{app.app_class}", :yellow
44
- app_routes.unshift(["REQUEST", "URL", "PATH"])
45
- max_col_1 = app_routes.max { |a, b| a[0].size <=> b[0].size }[0].size
46
- max_col_2 = app_routes.max { |a, b| a[1].size <=> b[1].size }[1].size
47
- app_routes.each_with_index do |row, i|
48
- message = [row[1].ljust(max_col_2+2), row[0].center(max_col_1+2), row[2]]
49
- shell.say(" " + message.join(" "), i==0 ? :bold : nil)
50
- end
51
- end
52
-
53
- desc "Displays a listing of the named routes within a project, optionally only those matched by [query]"
54
- task :routes, :query, :needs => :environment do |t, args|
55
- Padrino.mounted_apps.each do |app|
56
- list_app_routes(app, args)
57
- end
58
- end
59
-
60
- desc "Displays a listing of the named routes a given app [app]"
61
- namespace :routes do
62
- task :app, :app, :needs => :environment do |t, args|
63
- app = Padrino.mounted_apps.find { |app| app.app_class == args.app }
64
- list_app_routes(app, args) if app
65
- end
66
- end
67
-
68
- desc "Generate the Rakefile"
69
- task :gen do
70
- File.open(Padrino.root("Rakefile"), "w") do |file|
71
- file.puts <<-RUBY.gsub(/^ {6}/, '')
72
- require File.dirname(__FILE__) + '/config/boot.rb'
73
- require 'thor'
74
- require 'padrino-core/cli/rake'
75
-
76
- PadrinoTasks.init
77
- RUBY
78
- end
79
24
  end
@@ -0,0 +1,59 @@
1
+ # Load rake tasks from common rake task definition locations
2
+ Dir["lib/tasks/**/*.rake"].
3
+ concat(Dir["tasks/**/*.rake"]).
4
+ concat(Dir["{test,spec}/*.rake"]).each { |rake| load(rake) }
5
+
6
+ # Loads the Padrino applications mounted within the project
7
+ # setting up the required environment for Padrino
8
+ task :environment do
9
+ Padrino.mounted_apps.each do |app|
10
+ app.app_obj.setup_application!
11
+ end
12
+ end
13
+
14
+ desc "Generate a secret key"
15
+ task :secret do
16
+ shell.say SecureRandom.hex(32)
17
+ end
18
+
19
+ # lists all routes of a given app
20
+ def list_app_routes(app, args)
21
+ app_routes = app.named_routes
22
+ app_routes.reject! { |r| r.identifier.to_s !~ /#{args.query}/ } if args.query.present?
23
+ app_routes.map! { |r| [r.verb, r.name, r.path] }
24
+ return if app_routes.empty?
25
+ shell.say "\nApplication: #{app.app_class}", :yellow
26
+ app_routes.unshift(["REQUEST", "URL", "PATH"])
27
+ max_col_1 = app_routes.max { |a, b| a[0].size <=> b[0].size }[0].size
28
+ max_col_2 = app_routes.max { |a, b| a[1].size <=> b[1].size }[1].size
29
+ app_routes.each_with_index do |row, i|
30
+ message = [row[1].ljust(max_col_2+2), row[0].center(max_col_1+2), row[2]]
31
+ shell.say(" " + message.join(" "), i==0 ? :bold : nil)
32
+ end
33
+ end
34
+
35
+ desc "Displays a listing of the named routes within a project, optionally only those matched by [query]"
36
+ task :routes, [:query] => :environment do |t, args|
37
+ Padrino.mounted_apps.each do |app|
38
+ list_app_routes(app, args)
39
+ end
40
+ end
41
+
42
+ desc "Displays a listing of the named routes a given app [app]"
43
+ namespace :routes do
44
+ task :app, [:app] => :environment do |t, args|
45
+ app = Padrino.mounted_apps.find { |app| app.app_class == args.app }
46
+ list_app_routes(app, args) if app
47
+ end
48
+ end
49
+
50
+ desc "Generate the Rakefile"
51
+ task :gen do
52
+ File.open(Padrino.root("Rakefile"), "w") do |file|
53
+ file.puts <<-RUBY.gsub(/^ {6}/, '')
54
+ require File.expand_path('../config/boot.rb', __FILE__)
55
+ require 'padrino-core/cli/rake'
56
+ PadrinoTasks.init
57
+ RUBY
58
+ end
59
+ end
@@ -13,7 +13,7 @@ module Padrino
13
13
  #
14
14
  def before_load(&block)
15
15
  @_before_load ||= []
16
- @_before_load << Proc.new(&block) if block_given?
16
+ @_before_load << block if block_given?
17
17
  @_before_load
18
18
  end
19
19
 
@@ -29,33 +29,58 @@ module Padrino
29
29
  #
30
30
  def after_load(&block)
31
31
  @_after_load ||= []
32
- @_after_load << Proc.new(&block) if block_given?
32
+ @_after_load << block if block_given?
33
33
  @_after_load
34
34
  end
35
35
 
36
+ ##
37
+ # Returns the used $LOAD_PATHS from padrino
38
+ #
39
+ def load_paths
40
+ @_load_paths_was = %w(lib models shared).map { |path| Padrino.root(path) }
41
+ @_load_paths ||= @_load_paths_was
42
+ end
43
+
36
44
  ##
37
45
  # Requires necessary dependencies as well as application files from root lib and models
38
46
  #
39
47
  def load!
40
48
  return false if loaded?
41
49
  @_called_from = first_caller
42
- set_encoding
43
- set_load_paths(*load_paths) # We set the padrino load paths
50
+ Padrino.set_encoding
51
+ Padrino.set_load_paths(*load_paths) # We set the padrino load paths
44
52
  Padrino.logger # Initialize our logger
45
- before_load.each { |bl| bl.call } # Run before hooks
46
- dependency_paths.each { |path| require_dependencies(path) }
47
- Reloader::Stat.run! # We need to fill our Stat::CACHE
48
- after_load.each { |al| al.call } # Run after hooks
53
+ Padrino.require_dependencies("#{root}/config/database.rb", :nodeps => true) # Be sure to don't remove constants from dbs.
54
+ Padrino::Reloader.lock! # Now we can remove constant from here to down
55
+ Padrino.before_load.each(&:call) # Run before hooks
56
+ Padrino.dependency_paths.each { |path| Padrino.require_dependencies(path) }
57
+ Padrino.after_load.each(&:call) # Run after hooks
58
+ Padrino::Reloader.run!
49
59
  Thread.current[:padrino_loaded] = true
50
60
  end
51
61
 
62
+ ##
63
+ # Clear the padrino env
64
+ #
65
+ def clear!
66
+ Padrino.clear_middleware!
67
+ Padrino.mounted_apps.clear
68
+ @_load_paths = nil
69
+ @_dependency_paths = nil
70
+ @_global_configuration = nil
71
+ Padrino.before_load.clear
72
+ Padrino.after_load.clear
73
+ Padrino::Reloader.clear!
74
+ Thread.current[:padrino_loaded] = nil
75
+ end
76
+
52
77
  ##
53
78
  # Method for reloading required applications and their files
54
79
  #
55
80
  def reload!
56
- before_load.each { |bl| bl.call } # Run before hooks
57
- Reloader::Stat.reload! # detects the modified files
58
- after_load.each { |al| al.call } # Run after hooks
81
+ Padrino.before_load.each(&:call) # Run before hooks
82
+ Padrino::Reloader.reload! # detects the modified files
83
+ Padrino.after_load.each(&:call) # Run after hooks
59
84
  end
60
85
 
61
86
  ##
@@ -95,8 +120,10 @@ module Padrino
95
120
  # require_dependencies("#{Padrino.root}/lib/**/*.rb")
96
121
  #
97
122
  def require_dependencies(*paths)
123
+ options = paths.extract_options!
124
+
98
125
  # Extract all files to load
99
- files = paths.map { |path| Dir[path] }.flatten.uniq.sort
126
+ files = paths.flatten.map { |path| Dir[path] }.flatten.uniq.sort
100
127
 
101
128
  while files.present?
102
129
  # List of errors and failed files
@@ -110,7 +137,7 @@ module Padrino
110
137
  # iteration, this prevent problems with rubinus
111
138
  files.dup.each do |file|
112
139
  begin
113
- Reloader::Stat.safe_load(file)
140
+ Padrino::Reloader.safe_load(file, options.dup)
114
141
  files.delete(file)
115
142
  rescue LoadError => e
116
143
  errors << e
@@ -131,43 +158,25 @@ module Padrino
131
158
 
132
159
  ##
133
160
  # Returns default list of path globs to load as dependencies
134
- #
135
- def dependency_paths
136
- # Load db adapter, libs, root models, app configuration
137
- @dependency_paths ||= [
138
- "#{root}/config/database.rb", "#{root}/lib/**/*.rb", "#{root}/shared/lib/**/*.rb",
139
- "#{root}/models/**/*.rb", "#{root}/shared/models/**/*.rb", @custom_dependencies,
140
- "#{root}/config/apps.rb"
141
- ].flatten.compact
142
- end
143
-
144
- ##
145
161
  # Appends custom dependency patterns to the be loaded for Padrino
146
162
  #
147
163
  # ==== Examples
148
- # Padrino.custom_dependencies("#{Padrino.root}/foo/bar/*.rb")
164
+ # Padrino.dependency_paths << "#{Padrino.root}/uploaders/*.rb"
149
165
  #
150
- def custom_dependencies(*globs)
151
- @custom_dependencies ||= []
152
- @custom_dependencies.concat(globs)
153
- end
154
-
155
- ##
156
- # Attempts to load all dependency libs that we need.
157
- # If you use this method we can perform correctly a Padrino.reload!
158
- #
159
- def load_dependencies(*paths)
160
- paths.each do |path|
161
- FileSet.glob(path) { |file| load(file) }
162
- end
166
+ def dependency_paths
167
+ @_dependency_paths_was = [
168
+ "#{root}/config/database.rb", "#{root}/lib/**/*.rb", "#{root}/shared/lib/**/*.rb",
169
+ "#{root}/models/**/*.rb", "#{root}/shared/models/**/*.rb", "#{root}/config/apps.rb"
170
+ ]
171
+ @_dependency_paths ||= @_dependency_paths_was
163
172
  end
164
173
 
165
174
  ##
166
175
  # Concat to $LOAD_PATH the given paths
167
176
  #
168
177
  def set_load_paths(*paths)
169
- $:.concat(paths)
170
- $:.uniq!
178
+ $:.concat(paths); load_paths.concat(paths)
179
+ $:.uniq!; load_paths.uniq!
171
180
  end
172
181
  end # self
173
- end # Padrino
182
+ end # Padrino
@@ -15,6 +15,13 @@ module Padrino
15
15
  Thread.current[:padrino_logger] ||= Padrino::Logger.setup!
16
16
  end
17
17
 
18
+ ##
19
+ # Set the padrino logger
20
+ #
21
+ def self.logger=(value)
22
+ Thread.current[:padrino_logger] = value
23
+ end
24
+
18
25
  ##
19
26
  # Extensions to the built in Ruby logger.
20
27
  #
@@ -42,11 +49,12 @@ module Padrino
42
49
  # :debug:: low-level information for developers
43
50
  #
44
51
  Levels = {
45
- :fatal => 7,
46
- :error => 6,
47
- :warn => 4,
48
- :info => 3,
49
- :debug => 0
52
+ :fatal => 7,
53
+ :error => 6,
54
+ :warn => 4,
55
+ :info => 3,
56
+ :debug => 0,
57
+ :devel => -1,
50
58
  } unless const_defined?(:Levels)
51
59
 
52
60
  @@mutex = {}
@@ -113,7 +121,8 @@ module Padrino
113
121
  :error => [RED],
114
122
  :warn => [YELLOW],
115
123
  :info => [GREEN],
116
- :debug => [CYAN]
124
+ :debug => [CYAN],
125
+ :devel => [MAGENTA]
117
126
  } unless defined?(ColoredLevels)
118
127
 
119
128
  ##
@@ -285,6 +294,7 @@ module Padrino
285
294
 
286
295
  def call(env)
287
296
  env['rack.logger'] = Padrino.logger
297
+ env['rack.errors'] = Padrino.logger.log
288
298
  began_at = Time.now
289
299
  status, header, body = @app.call(env)
290
300
  log(env, status, header, began_at)
@@ -330,4 +340,4 @@ module Kernel #:nodoc:
330
340
  def logger
331
341
  Padrino.logger
332
342
  end
333
- end # Kernel
343
+ end # Kernel
@@ -22,6 +22,7 @@ module Padrino
22
22
  ensure_app_file! || ensure_app_object!
23
23
  @app_root = options[:app_root] || File.dirname(@app_file)
24
24
  @uri_root = "/"
25
+ Padrino::Reloader.exclude_constants << @app_class
25
26
  end
26
27
 
27
28
  ##
@@ -5,57 +5,6 @@ module Padrino
5
5
  # High performance source code reloader middleware
6
6
  #
7
7
  module Reloader
8
- ##
9
- # This class acts as a Rack middleware to be added to the application stack. This middleware performs a
10
- # check and reload for source files at the start of each request, but also respects a specified cool down time
11
- # during which no further action will be taken.
12
- #
13
- class Rack
14
- def initialize(app, cooldown = 1)
15
- @app = app
16
- @cooldown = cooldown
17
- @last = (Time.now - cooldown)
18
- end
19
-
20
- def call(env)
21
- if @cooldown and Time.now > @last + @cooldown
22
- if Thread.list.size > 1
23
- Thread.exclusive { Padrino.reload! }
24
- else
25
- Padrino.reload!
26
- end
27
-
28
- @last = Time.now
29
- end
30
-
31
- @app.call(env)
32
- end
33
- end
34
-
35
- ##
36
- # Specified folders can be excluded from the code reload detection process.
37
- # Default excluded directories at Padrino.root are: test, spec, features, tmp, config, db and public
38
- #
39
- def self.exclude
40
- @_exclude ||= %w(test spec tmp features config public db).map { |path| Padrino.root(path) }
41
- end
42
-
43
- ##
44
- # Specified constants can be excluded from the code unloading process.
45
- # Default excluded constants are: Padrino, Sinatra
46
- #
47
- def self.exclude_constants
48
- @_exclude_constants ||= %w(Padrino::Application Sinatra::Application Sinatra::Base)
49
- end
50
-
51
- ##
52
- # Specified constants can be configured to be reloaded on every request.
53
- # Default included constants are: [none]
54
- #
55
- def self.include_constants
56
- @_include_constants ||= []
57
- end
58
-
59
8
  ##
60
9
  # This reloader is suited for use in a many environments because each file
61
10
  # will only be checked once and only one system call to stat(2) is made.
@@ -63,195 +12,206 @@ module Padrino
63
12
  # Please note that this will not reload files in the background, and does so
64
13
  # only when explicitly invoked.
65
14
  #
66
- module Stat
67
- class << self
68
- CACHE = {}
69
- MTIMES = {}
70
- FILES_LOADED = {}
71
- LOADED_CLASSES = {}
15
+ MTIMES = {}
16
+ FILES_LOADED = {}
17
+ LOADED_CLASSES = {}
18
+
19
+ class << self
20
+ ##
21
+ # Specified folders can be excluded from the code reload detection process.
22
+ # Default excluded directories at Padrino.root are: test, spec, features, tmp, config, db and public
23
+ #
24
+ def exclude
25
+ @_exclude ||= %w(test spec tmp features config public db).map { |path| Padrino.root(path) }
26
+ end
72
27
 
73
- ##
74
- # Reload all files with changes detected.
75
- #
76
- def reload!
77
- # Detect changed files
78
- rotation do |file, mtime|
79
- # Retrive the last modified time
80
- new_file = MTIMES[file].nil?
81
- previous_mtime = MTIMES[file] ||= mtime
82
- logger.debug "Detected a new file #{file}" if new_file
83
- # We skip to next file if it is not new and not modified
84
- next unless new_file || mtime > previous_mtime
85
- # If the file is related to their app (i.e. a controller/mailer/helper)
86
- Padrino.mounted_apps.find_all { |a| file =~ /^#{File.dirname(a.app_file)}/ }.each do |app|
87
- # We need to reload their own app
88
- app.app_obj.reload!
89
- # App reloading will also perform safe_load of itself so we can go next
90
- if File.identical?(app.app_file, file)
91
- MTIMES[file] = mtime # This prevent a loop
92
- next
93
- end
28
+ ##
29
+ # Specified constants can be excluded from the code unloading process.
30
+ #
31
+ def exclude_constants
32
+ @_exclude_constants ||= []
33
+ end
34
+
35
+ ##
36
+ # Specified constants can be configured to be reloaded on every request.
37
+ # Default included constants are: [none]
38
+ #
39
+ def include_constants
40
+ @_include_constants ||= []
41
+ end
42
+ ##
43
+ # Reload all files with changes detected.
44
+ #
45
+ def reload!
46
+ # Detect changed files
47
+ rotation do |file, mtime|
48
+ # Retrive the last modified time
49
+ new_file = MTIMES[file].nil?
50
+ previous_mtime = MTIMES[file] ||= mtime
51
+ logger.devel "Detected a new file #{file}" if new_file
52
+ # We skip to next file if it is not new and not modified
53
+ next unless new_file || mtime > previous_mtime
54
+ # Now we can reload our file
55
+ apps = get_apps(file)
56
+ if apps.present?
57
+ apps.each { |app| app.app_obj.reload! }
58
+ else
59
+ safe_load(file, :force => new_file)
60
+ # Reload also apps
61
+ Padrino.mounted_apps.each do |app|
62
+ app.app_obj.reload! if app.app_obj.dependencies.include?(file)
94
63
  end
95
- # Now we can reload our file
96
- safe_load(file, mtime)
97
64
  end
98
65
  end
66
+ end
99
67
 
100
- ##
101
- # Returns true if any file changes are detected and populates the MTIMES cache
102
- #
103
- def changed?
104
- changed = false
105
- rotation do |file, mtime|
106
- new_file = MTIMES[file].nil?
107
- previous_mtime = MTIMES[file] ||= mtime
108
- changed = true if new_file || mtime > previous_mtime
109
- end
110
- changed
68
+ ##
69
+ # Remove files and classes loaded with stat
70
+ #
71
+ def clear!
72
+ MTIMES.clear
73
+ LOADED_CLASSES.each do |file, klasses|
74
+ klasses.each { |klass| remove_constant(klass) }
75
+ LOADED_CLASSES.delete(file)
111
76
  end
112
- alias :run! :changed?
113
-
114
- ##
115
- # A safe Kernel::load which issues the necessary hooks depending on results
116
- #
117
- def safe_load(file, mtime=nil)
118
- reload = mtime && mtime > MTIMES[file]
119
-
120
- logger.debug "Reloading #{file}" if reload
77
+ FILES_LOADED.each do |file, dependencies|
78
+ dependencies.each { |dependency| $LOADED_FEATURES.delete(dependency) }
79
+ $LOADED_FEATURES.delete(file)
80
+ end
81
+ end
121
82
 
122
- # Removes all classes declared in the specified file
123
- if klasses = LOADED_CLASSES.delete(file)
124
- klasses.each { |klass| remove_constant(klass) }
125
- end
83
+ ##
84
+ # Returns true if any file changes are detected and populates the MTIMES cache
85
+ #
86
+ def changed?
87
+ changed = false
88
+ rotation do |file, mtime|
89
+ new_file = MTIMES[file].nil?
90
+ previous_mtime = MTIMES[file] ||= mtime
91
+ changed = true if new_file || mtime > previous_mtime
92
+ end
93
+ changed
94
+ end
95
+ alias :run! :changed?
96
+
97
+ ##
98
+ # We lock dependencies sets to prevent reloading of protected constants
99
+ #
100
+ def lock!
101
+ klasses = ObjectSpace.classes.map { |klass| klass.to_s.split("::")[0] }.uniq
102
+ klasses = klasses | Padrino.mounted_apps.map { |app| app.app_class }
103
+ Padrino::Reloader.exclude_constants.concat(klasses)
104
+ end
126
105
 
127
- # Keeps track of which constants were loaded and the files
128
- # that have been added so that the constants can be removed
129
- # and the files can be removed from $LOADED_FEAUTRES
130
- if FILES_LOADED[file]
131
- FILES_LOADED[file].each do |fl|
132
- next if fl == file
133
- $LOADED_FEATURES.delete(fl)
134
- end
135
- end
106
+ ##
107
+ # A safe Kernel::require which issues the necessary hooks depending on results
108
+ #
109
+ def safe_load(file, options={})
110
+ force, file = options[:force], figure_path(file)
136
111
 
137
- # Now reload the file ignoring any syntax errors
138
- $LOADED_FEATURES.delete(file)
112
+ reload = MTIMES[file] && File.mtime(file) > MTIMES[file]
113
+ return if !force && !reload && MTIMES[file]
139
114
 
140
- # Duplicate objects and loaded features in the file
141
- klasses = ObjectSpace.classes.dup
142
- files_loaded = $LOADED_FEATURES.dup
115
+ # Removes all classes declared in the specified file
116
+ if klasses = LOADED_CLASSES.delete(file)
117
+ klasses.each { |klass| remove_constant(klass) }
118
+ end
143
119
 
144
- # Start to re-require old dependencies
145
- #
146
- # Why we need to reload the dependencies i.e. of a model?
147
- #
148
- # In some circumstances (i.e. with MongoMapper) reloading a model require:
149
- #
150
- # 1) Clean objectspace
151
- # 2) Reload model dependencies
152
- #
153
- # We need to clean objectspace because for example we don't need to apply two times validations keys etc...
154
- #
155
- # We need to reload MongoMapper dependencies for re-initialize them.
156
- #
157
- # In other cases i.e. in a controller (specially with dependencies that uses autoload) reload stuff like sass
158
- # is not really necessary... but how to distinguish when it is (necessary) since it is not?
159
- #
160
- if FILES_LOADED[file]
161
- FILES_LOADED[file].each do |fl|
162
- next if fl == file
163
- # Swich off for a while warnings expecially "already initialized constant" stuff
164
- begin
165
- verbosity = $-v
166
- $-v = nil
167
- require(fl)
168
- ensure
169
- $-v = verbosity
170
- end
171
- end
172
- end
120
+ # Duplicate objects and loaded features in the file
121
+ klasses = ObjectSpace.classes.dup
173
122
 
174
- # And finally reload the specified file
175
- begin
176
- require(file)
177
- rescue SyntaxError => ex
178
- logger.error "Cannot require #{file} because of syntax error: #{ex.message}"
179
- ensure
180
- MTIMES[file] = mtime if mtime
181
- end
123
+ # And finally reload the specified file
124
+ begin
125
+ logger.devel "Loading #{file}#{' with force' if force}" if !reload
126
+ logger.debug "Reloading #{file}" if reload
127
+ $LOADED_FEATURES.delete(file)
128
+ require(file)
129
+ MTIMES[file] = File.mtime(file)
130
+ rescue SyntaxError => ex
131
+ logger.error "Cannot require #{file} because of syntax error: #{ex.message}"
132
+ end
182
133
 
183
- # Store the file details after successful loading
184
- LOADED_CLASSES[file] = ObjectSpace.classes - klasses
185
- FILES_LOADED[file] = $LOADED_FEATURES - files_loaded
134
+ # Store the file details after successful loading
135
+ LOADED_CLASSES[file] ||= (ObjectSpace.classes - klasses).uniq
136
+ end
186
137
 
187
- nil
138
+ ##
139
+ # Returns true if the file is defined in our padrino root
140
+ #
141
+ def figure_path(file)
142
+ return file if Pathname.new(file).absolute?
143
+ $:.each do |path|
144
+ found = File.join(path, file)
145
+ return File.expand_path(found) if File.exist?(found)
188
146
  end
147
+ file
148
+ end
189
149
 
190
- ##
191
- # Removes the specified class and constant.
192
- #
193
- def remove_constant(const)
194
- return if Padrino::Reloader.exclude_constants.any? { |base| (const.to_s =~ /^#{base}/ || const.superclass.to_s =~ /^#{base}/) } &&
195
- !Padrino::Reloader.include_constants.any? { |base| (const.to_s =~ /^#{base}/ || const.superclass.to_s =~ /^#{base}/) }
196
-
197
- parts = const.to_s.split("::")
198
- base = parts.size == 1 ? Object : Object.full_const_get(parts[0..-2].join("::"))
150
+ ##
151
+ # Removes the specified class and constant.
152
+ #
153
+ def remove_constant(const)
154
+ return if Padrino::Reloader.exclude_constants.any? { |base| (const.to_s =~ %r{^#{base}}) } &&
155
+ !Padrino::Reloader.include_constants.any? { |base| (const.to_s =~ %r{^#{base}}) }
156
+ begin
157
+ parts = const.to_s.split("::")
158
+ base = parts.size == 1 ? Object : parts[0..-2].join("::").constantize
199
159
  object = parts[-1].to_s
200
- begin
201
- base.send(:remove_const, object)
202
- rescue NameError
203
- end
160
+ logger.devel "Remove constant: #{const}"
161
+ base.send(:remove_const, object)
162
+ rescue NameError; end
163
+ end
204
164
 
205
- nil
165
+ private
166
+ ##
167
+ # Return the mounted_app providing the app location
168
+ #
169
+ def get_apps(file)
170
+ file = figure_path(file)
171
+ Padrino.mounted_apps.find_all { |app| File.identical?(file, app.app_file) }
206
172
  end
207
173
 
208
174
  ##
209
- # Searches Ruby files in your +Padrino.root+ and monitors them for any changes.
175
+ # Searches Ruby files in your +Padrino.load_paths+ , Padrino::Application.load_paths
176
+ # and monitors them for any changes.
210
177
  #
211
178
  def rotation
212
- paths = Dir[Padrino.root("*")].unshift(Padrino.root).
213
- reject { |path| Padrino::Reloader.exclude.include?(path) || !File.directory?(path) }
214
- files = paths.map { |path| Dir["#{path}/**/*.rb"] }.flatten.uniq
215
-
216
- files.map { |file|
217
- next if Padrino::Reloader.exclude.any? { |base| file =~ /^#{Regexp.escape(base)}/ }
218
-
219
- found, stat = figure_path(file, paths)
220
- next unless found && stat && mtime = stat.mtime
221
-
222
- CACHE[file] = found
223
-
224
- yield(found, mtime)
179
+ files = Padrino.load_paths.map { |path| Dir["#{path}/**/*.rb"] }.flatten
180
+ files = files | Padrino.mounted_apps.map { |app| app.app_file }
181
+ files = files | Padrino.mounted_apps.map { |app| app.app_obj.dependencies }.flatten
182
+ files.uniq.map { |file|
183
+ file = File.expand_path(file)
184
+ next if Padrino::Reloader.exclude.any? { |base| file =~ %r{^#{base}} } || !File.exist?(file)
185
+ yield(file, File.mtime(file))
225
186
  }.compact
226
187
  end
188
+ end # self
227
189
 
228
- ##
229
- # Takes a relative or absolute +file+ name and a couple possible +paths+ that
230
- # the +file+ might reside in. Returns the full path and File::Stat for that path.
231
- #
232
- def figure_path(file, paths)
233
- found = CACHE[file]
234
- found = file if !found and Pathname.new(file).absolute?
235
- found, stat = safe_stat(found)
236
- return found, stat if found
190
+ ##
191
+ # This class acts as a Rack middleware to be added to the application stack. This middleware performs a
192
+ # check and reload for source files at the start of each request, but also respects a specified cool down time
193
+ # during which no further action will be taken.
194
+ #
195
+ class Rack
196
+ def initialize(app, cooldown = 1)
197
+ @app = app
198
+ @cooldown = cooldown
199
+ @last = (Time.now - cooldown)
200
+ end
237
201
 
238
- paths.find do |possible_path|
239
- path = ::File.join(possible_path, file)
240
- found, stat = safe_stat(path)
241
- return ::File.expand_path(found), stat if found
202
+ def call(env)
203
+ if @cooldown and Time.now > @last + @cooldown
204
+ if Thread.list.size > 1
205
+ Thread.exclusive { Padrino.reload! }
206
+ else
207
+ Padrino.reload!
242
208
  end
243
209
 
244
- return false, false
210
+ @last = Time.now
245
211
  end
246
212
 
247
- def safe_stat(file)
248
- return unless file
249
- stat = ::File.stat(file)
250
- return file, stat if stat.file?
251
- rescue Errno::ENOENT, Errno::ENOTDIR
252
- CACHE.delete(file) and false
253
- end
254
- end # self
255
- end # Stat
213
+ @app.call(env)
214
+ end
215
+ end
256
216
  end # Reloader
257
217
  end # Padrino