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.
- data/lib/padrino-core.rb +3 -14
- data/lib/padrino-core/application.rb +45 -38
- data/lib/padrino-core/application/rendering.rb +16 -9
- data/lib/padrino-core/application/routing.rb +41 -57
- data/lib/padrino-core/cli/adapter.rb +9 -32
- data/lib/padrino-core/cli/base.rb +13 -14
- data/lib/padrino-core/cli/rake.rb +14 -69
- data/lib/padrino-core/cli/rake_tasks.rb +59 -0
- data/lib/padrino-core/loader.rb +50 -41
- data/lib/padrino-core/logger.rb +17 -7
- data/lib/padrino-core/mounter.rb +1 -0
- data/lib/padrino-core/reloader.rb +170 -210
- data/lib/padrino-core/server.rb +41 -45
- data/lib/padrino-core/support_lite.rb +24 -76
- data/lib/padrino-core/version.rb +1 -1
- data/padrino-core.gemspec +6 -2
- data/test/fixtures/apps/simple.rb +1 -1
- data/test/test_application.rb +1 -1
- data/test/test_core.rb +18 -4
- data/test/test_mounter.rb +1 -1
- data/test/test_reloader_complex.rb +2 -2
- data/test/test_reloader_simple.rb +2 -2
- data/test/test_rendering.rb +30 -0
- data/test/test_router.rb +4 -2
- data/test/test_routing.rb +9 -0
- metadata +5 -58
- data/test/test_server.rb +0 -37
@@ -1,79 +1,24 @@
|
|
1
|
-
require File.expand_path(
|
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 { |
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
data/lib/padrino-core/loader.rb
CHANGED
@@ -13,7 +13,7 @@ module Padrino
|
|
13
13
|
#
|
14
14
|
def before_load(&block)
|
15
15
|
@_before_load ||= []
|
16
|
-
@_before_load <<
|
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 <<
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
57
|
-
Reloader
|
58
|
-
after_load.each
|
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
|
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.
|
164
|
+
# Padrino.dependency_paths << "#{Padrino.root}/uploaders/*.rb"
|
149
165
|
#
|
150
|
-
def
|
151
|
-
@
|
152
|
-
|
153
|
-
|
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
|
data/lib/padrino-core/logger.rb
CHANGED
@@ -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 =>
|
46
|
-
:error =>
|
47
|
-
:warn =>
|
48
|
-
:info =>
|
49
|
-
:debug =>
|
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
|
data/lib/padrino-core/mounter.rb
CHANGED
@@ -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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
138
|
-
|
112
|
+
reload = MTIMES[file] && File.mtime(file) > MTIMES[file]
|
113
|
+
return if !force && !reload && MTIMES[file]
|
139
114
|
|
140
|
-
|
141
|
-
|
142
|
-
|
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
|
-
|
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
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
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
|
-
|
184
|
-
|
185
|
-
|
134
|
+
# Store the file details after successful loading
|
135
|
+
LOADED_CLASSES[file] ||= (ObjectSpace.classes - klasses).uniq
|
136
|
+
end
|
186
137
|
|
187
|
-
|
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
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
parts
|
198
|
-
base
|
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
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
160
|
+
logger.devel "Remove constant: #{const}"
|
161
|
+
base.send(:remove_const, object)
|
162
|
+
rescue NameError; end
|
163
|
+
end
|
204
164
|
|
205
|
-
|
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.
|
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
|
-
|
213
|
-
|
214
|
-
files =
|
215
|
-
|
216
|
-
|
217
|
-
next if Padrino::Reloader.exclude.any? { |base| file =~
|
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
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
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
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
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
|
-
|
210
|
+
@last = Time.now
|
245
211
|
end
|
246
212
|
|
247
|
-
|
248
|
-
|
249
|
-
|
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
|