reactive-core 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/Manifest +30 -0
- data/README +25 -0
- data/Rakefile +19 -0
- data/lib/reactive-core.rb +8 -0
- data/lib/reactive-core/console_app.rb +9 -0
- data/lib/reactive-core/core_ext.rb +3 -0
- data/lib/reactive-core/dispatcher.rb +63 -0
- data/lib/reactive-core/errors.rb +99 -0
- data/lib/reactive-core/ext/memoizable.rb +6 -0
- data/lib/reactive-core/ext/object.rb +14 -0
- data/lib/reactive-core/ext/object_instance.rb +9 -0
- data/lib/reactive-core/ext/ordered_options.rb +1 -0
- data/lib/reactive-core/ext/rubygems_activate_patch.rb +140 -0
- data/lib/reactive-core/gem_dependency.rb +231 -0
- data/lib/reactive-core/helper_module.rb +14 -0
- data/lib/reactive-core/initializer.rb +488 -0
- data/lib/reactive-core/meta_model.rb +92 -0
- data/lib/reactive-core/output_handler.rb +143 -0
- data/lib/reactive-core/request.rb +27 -0
- data/lib/reactive-core/response.rb +14 -0
- data/lib/reactive-core/tasks/app.rake +29 -0
- data/lib/reactive-core/tasks/gems.rake +43 -0
- data/lib/reactive-core/tasks/log.rake +9 -0
- data/lib/reactive-core/tasks/misc.rake +5 -0
- data/lib/reactive-core/tasks/reactive.rb +18 -0
- data/lib/reactive-core/updater/base.rb +24 -0
- data/lib/reactive-core/updater/cli.rb +54 -0
- data/lib/reactive-core/updater/gui.rb +13 -0
- data/lib/reactive-core/version.rb +9 -0
- metadata +109 -0
@@ -0,0 +1,231 @@
|
|
1
|
+
module Reactive
|
2
|
+
class GemDependency #:nodoc:
|
3
|
+
attr_accessor :name, :requirement, :version, :lib, :source
|
4
|
+
|
5
|
+
def initialize(name, options = {})
|
6
|
+
require 'rubygems' unless Object.const_defined?(:Gem)
|
7
|
+
|
8
|
+
options = {:version => options} if options.is_a? String
|
9
|
+
|
10
|
+
if options[:requirement]
|
11
|
+
@requirement = options[:requirement]
|
12
|
+
elsif options[:version]
|
13
|
+
@requirement = Gem::Requirement.create(options[:version])
|
14
|
+
end
|
15
|
+
|
16
|
+
@version = @requirement.instance_variable_get("@requirements").first.last if @requirement
|
17
|
+
@name = name.to_s
|
18
|
+
@lib = options[:lib]
|
19
|
+
@source = options[:source]
|
20
|
+
@init = options[:init]
|
21
|
+
@loaded = false
|
22
|
+
end
|
23
|
+
|
24
|
+
def dependencies
|
25
|
+
if spec = specification
|
26
|
+
all_dependencies = spec.dependencies.map do |dependency|
|
27
|
+
GemDependency.new(dependency.name, :requirement => dependency.version_requirements)
|
28
|
+
end
|
29
|
+
all_dependencies += all_dependencies.map(&:dependencies).flatten
|
30
|
+
all_dependencies.uniq
|
31
|
+
else
|
32
|
+
[]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def load
|
37
|
+
return if @loaded
|
38
|
+
args = [@name]
|
39
|
+
args << @requirement.to_s if @requirement
|
40
|
+
gem *args
|
41
|
+
# Now, run its reactive init code
|
42
|
+
init_plugin
|
43
|
+
@loaded = true
|
44
|
+
end
|
45
|
+
|
46
|
+
def init_plugin
|
47
|
+
if @init == :rails
|
48
|
+
require(@lib) if @lib
|
49
|
+
else
|
50
|
+
require(@lib || @name) unless @lib == false
|
51
|
+
end
|
52
|
+
gem_path = File.expand_path(specification.full_gem_path)
|
53
|
+
specification.require_paths.map {|path| File.join(gem_path, path) }.each do |path|
|
54
|
+
begin
|
55
|
+
require File.join(path, 'reactive', 'init.rb')
|
56
|
+
return
|
57
|
+
rescue LoadError
|
58
|
+
# No reactive init code, let's try a Rails init code
|
59
|
+
if @init == :rails
|
60
|
+
return if load_rails_init(path)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def embedded?
|
67
|
+
return true if @embedded
|
68
|
+
return false unless specification
|
69
|
+
File.expand_path(specification.installation_path) == File.expand_path(Reactive.path_for('gems'))
|
70
|
+
end
|
71
|
+
|
72
|
+
def installed?
|
73
|
+
loaded? || specification
|
74
|
+
end
|
75
|
+
|
76
|
+
def loaded?
|
77
|
+
@loaded
|
78
|
+
end
|
79
|
+
|
80
|
+
def loaded_spec
|
81
|
+
Gem.loaded_specs[@name]
|
82
|
+
end
|
83
|
+
|
84
|
+
def install
|
85
|
+
cmd = "#{gem_command} #{install_command.join(' ')}"
|
86
|
+
[cmd, %x(#{cmd})]
|
87
|
+
end
|
88
|
+
|
89
|
+
def embed
|
90
|
+
return "#{name} #{requirement} is already embedded." if embedded?
|
91
|
+
# If the gem is already installed, use Gem::Installer to embed because we only need gem files and the spec
|
92
|
+
if installed?
|
93
|
+
require 'rubygems/installer'
|
94
|
+
path = get_gem_path(name, requirement)
|
95
|
+
basename = File.basename(path).sub(/\.gem$/, '')
|
96
|
+
installer = Gem::Installer.new(path, :install_dir => Reactive.path_for('gems'))
|
97
|
+
installer.unpack Reactive.path_for('gems', 'gems', basename)
|
98
|
+
installer.write_spec
|
99
|
+
@embedded = true
|
100
|
+
"Successfully embedded #{name} #{installer.spec.version}"
|
101
|
+
else
|
102
|
+
cmd = "#{gem_command} #{install_command.join(' ')} -i #{Reactive.path_for('gems')} --ignore-dependencies"
|
103
|
+
[cmd, %x(#{cmd})]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def ==(other)
|
108
|
+
self.name == other.name && self.requirement == other.requirement
|
109
|
+
end
|
110
|
+
alias :eql? :==
|
111
|
+
def hash
|
112
|
+
"#{name}#{requirement}".hash
|
113
|
+
end
|
114
|
+
|
115
|
+
protected
|
116
|
+
|
117
|
+
def specification
|
118
|
+
@spec ||= Gem.source_index.search(Gem::Dependency.new(@name, @requirement)).sort_by { |s| s.version }.last
|
119
|
+
end
|
120
|
+
|
121
|
+
def gem_command
|
122
|
+
RUBY_PLATFORM =~ /win32/ ? 'gem.bat' : 'gem'
|
123
|
+
end
|
124
|
+
|
125
|
+
def install_command
|
126
|
+
cmd = %w(install) << @name
|
127
|
+
cmd << "--version" << %("#{@requirement.to_s}") if @requirement
|
128
|
+
cmd << "--source" << @source if @source
|
129
|
+
cmd
|
130
|
+
end
|
131
|
+
|
132
|
+
# >> taken from rubygems/commands/unpack_command.rb <<
|
133
|
+
# Return the full path to the cached gem file matching the given
|
134
|
+
# name and version requirement. Returns 'nil' if no match.
|
135
|
+
#
|
136
|
+
# Example:
|
137
|
+
#
|
138
|
+
# get_path('rake', '> 0.4') # -> '/usr/lib/ruby/gems/1.8/cache/rake-0.4.2.gem'
|
139
|
+
# get_path('rake', '< 0.1') # -> nil
|
140
|
+
# get_path('rak') # -> nil (exact name required)
|
141
|
+
#--
|
142
|
+
# TODO: This should be refactored so that it's a general service. I don't
|
143
|
+
# think any of our existing classes are the right place though. Just maybe
|
144
|
+
# 'Cache'?
|
145
|
+
#
|
146
|
+
# TODO: It just uses Gem.dir for now. What's an easy way to get the list of
|
147
|
+
# source directories?
|
148
|
+
def get_gem_path(gemname, version_req)
|
149
|
+
return gemname if gemname =~ /\.gem$/i
|
150
|
+
|
151
|
+
specs = Gem::source_index.search(/\A#{gemname}\z/, version_req)
|
152
|
+
selected = specs.sort_by { |s| s.version }.last
|
153
|
+
return nil if selected.nil?
|
154
|
+
|
155
|
+
# We expect to find (basename).gem in the 'cache' directory.
|
156
|
+
# Furthermore, the name match must be exact (ignoring case).
|
157
|
+
if gemname =~ /^#{selected.name}$/i
|
158
|
+
filename = selected.full_name + '.gem'
|
159
|
+
path = nil
|
160
|
+
|
161
|
+
Gem.path.find do |gem_dir|
|
162
|
+
path = File.join gem_dir, 'cache', filename
|
163
|
+
File.exist? path
|
164
|
+
end
|
165
|
+
|
166
|
+
path
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def load_rails_init(path)
|
171
|
+
# define_rails_equivalence
|
172
|
+
if init_file = [File.join(path, 'rails', 'init.rb'), File.join(specification.full_gem_path, 'init.rb')].find {|file| File.file? file}
|
173
|
+
Reactive::Initializer.register("init_rails_plugin_#{specification.name}", :init_rails_plugin) do
|
174
|
+
Kernel.load(init_file)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
rescue LoadError
|
178
|
+
# Don't care, not having init code is legal (at least in reactive).
|
179
|
+
false
|
180
|
+
end
|
181
|
+
|
182
|
+
=begin
|
183
|
+
@@rails_equivalence_set = false
|
184
|
+
def define_rails_equivalence
|
185
|
+
return if @@rails_equivalence_set
|
186
|
+
@@rails_equivalence_set = true
|
187
|
+
puts "defininf rials qeui"
|
188
|
+
Reactive::Initializer.configure :rails_equivalence do
|
189
|
+
puts "rails equi"
|
190
|
+
self.class.module_eval <<-EOC
|
191
|
+
module ::Rails
|
192
|
+
Generator = RubiGen
|
193
|
+
end
|
194
|
+
EOC
|
195
|
+
end
|
196
|
+
end
|
197
|
+
=end
|
198
|
+
|
199
|
+
public
|
200
|
+
|
201
|
+
def self.report_gems_state(with_dependencies = true)
|
202
|
+
Reactive.configuration.gems.inject('') do |report, gem|
|
203
|
+
installed_dependency_count = 0
|
204
|
+
dependencies_status = gem.dependencies.collect do |dependency|
|
205
|
+
if dependency.installed?
|
206
|
+
code = dependency.embedded? ? "E" : "I"
|
207
|
+
installed_dependency_count += 1
|
208
|
+
else
|
209
|
+
code = " "
|
210
|
+
end
|
211
|
+
" [#{code}] #{dependency.name} #{dependency.requirement.to_s}\n"
|
212
|
+
end.uniq
|
213
|
+
|
214
|
+
code = gem.installed? ? (gem.embedded? ? "E" : "I") : " "
|
215
|
+
code.downcase! if installed_dependency_count != gem.dependencies.size
|
216
|
+
report << "[#{code}] #{gem.name} #{gem.requirement.to_s}\n"
|
217
|
+
report << dependencies_status.join('') if with_dependencies
|
218
|
+
report
|
219
|
+
end << "I = Installed, i = Installed but missing dependencies\nE = Embedded, e = Embedded but missing dependencies"
|
220
|
+
end
|
221
|
+
|
222
|
+
# Returns an Array of missing gems
|
223
|
+
def self.missing_gems
|
224
|
+
Reactive.configuration.gems.inject([]) do |missing, gem|
|
225
|
+
missing << gem unless gem.installed?
|
226
|
+
gem.dependencies.inject(missing) {|missing, dependency| dependency.installed? ? missing : missing << dependency}
|
227
|
+
end.uniq
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
231
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class HelperModule < Module
|
2
|
+
def initialize(names, code)
|
3
|
+
self.class.send(:define_method, :helpers) do
|
4
|
+
# reverse because this array will be passed to #extend which put precedence in the first passed module
|
5
|
+
helpers = names.collect {|name| const_get(name.split('.').first.classify) }.reverse
|
6
|
+
#constants doesn't preserve the order in which they are declared! #helpers = constants.collect{|name| const_get(name)}.select{|const| const.is_a? Module}
|
7
|
+
# We provide ourself (which is an empty module) if no nested modules are defined because calling #extend with no argument isn't legal.
|
8
|
+
helpers.empty? ? self : helpers
|
9
|
+
end
|
10
|
+
@name = names.join('_')
|
11
|
+
class_eval(code)
|
12
|
+
end
|
13
|
+
attr_reader :name
|
14
|
+
end
|
@@ -0,0 +1,488 @@
|
|
1
|
+
require 'activesupport'
|
2
|
+
require 'singleton'
|
3
|
+
require 'reactive-core/core_ext'
|
4
|
+
require 'reactive-core/version'
|
5
|
+
require 'reactive-core/gem_dependency'
|
6
|
+
|
7
|
+
module Reactive
|
8
|
+
class << self
|
9
|
+
# The reactive configuration used for the application.
|
10
|
+
attr_accessor :configuration
|
11
|
+
|
12
|
+
# The logger instance used by the framework. Plugins may copy this reference,
|
13
|
+
# thus changing it lately (after init time) may not have the desired effect.
|
14
|
+
attr_accessor :logger
|
15
|
+
|
16
|
+
# Returns the Reactive version as a String
|
17
|
+
def version
|
18
|
+
VERSION::STRING
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the directory for the type passed.
|
22
|
+
# Returns nil if no directory is configured for the passed type.
|
23
|
+
#
|
24
|
+
# Examples:
|
25
|
+
# dir_for(:config) => "/home/lambda/mysales/config"
|
26
|
+
# dir_for(:views) => "/home/lambda/mysales/app/views"
|
27
|
+
def dir_for(type)
|
28
|
+
dirs_for(type).first
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns an array of directories for the type passed.
|
32
|
+
# Returns an empty array if no directory is configured for the passed type.
|
33
|
+
#
|
34
|
+
# Examples:
|
35
|
+
# dirs_for(:config) => ["/home/lambda/mysales/config"]
|
36
|
+
# dirs_for(:views) => ["/home/lambda/mysales/app/views", "/home/lambda/mysales/app/special_views"]
|
37
|
+
def dirs_for(type)
|
38
|
+
[configuration.paths[type]].flatten.compact
|
39
|
+
end
|
40
|
+
|
41
|
+
# Constructs an absolute path with parts relative to the application root or relative
|
42
|
+
# to a typed directory.
|
43
|
+
#
|
44
|
+
# Raises an ArgumentError if no directory is configured for the passed type.
|
45
|
+
#
|
46
|
+
# Examples:
|
47
|
+
# path_for("log", "production.log") # => "/home/lambda/mysales/log/production.log"
|
48
|
+
# path_for(:views, "main", "run.rb") # => /home/lambda/mysales/app/views/main/run.rb"
|
49
|
+
def path_for(*parts)
|
50
|
+
root_dir = parts.first.is_a?(Symbol) ? dir_for(path_type = parts.shift) : configuration.root_dir
|
51
|
+
raise ArgumentError, defined?(path_type) ? "No defined path for #{path_type}" : "No root dir defined!" unless root_dir
|
52
|
+
File.join(root_dir, *parts)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the complete pathname for a given file (the last argument) in a given path (other parts) specified with a similar form than #path_for.
|
56
|
+
# You may also pass wildcards as defined in Dir#glob, when this is the case, the first matching file is returned.
|
57
|
+
def file_for(*parts)
|
58
|
+
files_for(*parts).first
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the complete pathnames for a given glob path relative to the application root, see #path_for for more information.
|
62
|
+
def files_for(*parts)
|
63
|
+
root_dirs = parts.first.is_a?(Symbol) ? dirs_for(path_type = parts.shift) : [configuration.root_dir]
|
64
|
+
raise ArgumentError, defined?(path_type) ? "No defined path for #{path_type}" : "No root dir defined!" if root_dirs.empty?
|
65
|
+
root_dirs.each do |dir|
|
66
|
+
pathname = File.join(dir, *parts)
|
67
|
+
filenames = Dir.glob(pathname).select {|item| File.file? item}
|
68
|
+
return filenames unless filenames.empty?
|
69
|
+
end
|
70
|
+
[]
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns the complete pathname for a given file (the last argument) in a given path (other parts) specified with a similar form than #path_for.
|
74
|
+
# Raises an Errno::ENOENT exception if none matched.
|
75
|
+
def file_for!(*parts)
|
76
|
+
file_for(*parts) or raise Errno::ENOENT
|
77
|
+
end
|
78
|
+
|
79
|
+
# Constructs a relative path (to the application root) with parts relative to the application root or relative
|
80
|
+
# to a typed directory.
|
81
|
+
#
|
82
|
+
# Examples:
|
83
|
+
# relative_path_for("log", "production.log") # => "log/production.log"
|
84
|
+
# relative_path_for(:views, "main", "run.rb") # => "app/views/main/run.rb"
|
85
|
+
def relative_path_for(*parts)
|
86
|
+
path_for(*parts).sub("#{configuration.root_dir}/", '')
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
self.logger = ActiveSupport::BufferedLogger.new(STDERR)
|
91
|
+
self.logger.level = (ENV['DEBUG'] || $DEBUG) ? ActiveSupport::BufferedLogger::DEBUG : ActiveSupport::BufferedLogger::WARN
|
92
|
+
|
93
|
+
#
|
94
|
+
# Holds the configuration of the framework and plugins.
|
95
|
+
#
|
96
|
+
# The configuration object is a hash-like object with access to values with
|
97
|
+
# accessors. So these are equivalent:
|
98
|
+
# config[:use_donuts] = true
|
99
|
+
# config.use_donuts = true
|
100
|
+
#
|
101
|
+
#
|
102
|
+
# It is populated by the framework itself, plugins and also the application through
|
103
|
+
# the config/config.rb file and its specialized config/environments/*.rb forms.
|
104
|
+
class Configuration < OrderedOptions
|
105
|
+
def initialize(*args)
|
106
|
+
super()
|
107
|
+
self[:paths] = {}
|
108
|
+
self[:gems] = []
|
109
|
+
parse(args.first) if args.first
|
110
|
+
end
|
111
|
+
|
112
|
+
# Merges the passed options with the configuration.
|
113
|
+
# Pass a hash-like object for the options. You may either pass a block which
|
114
|
+
# will be yielded with a fresh configuration object that will be merged after
|
115
|
+
# the end of the block.
|
116
|
+
def merge(options = nil, &block)
|
117
|
+
raise ArgumentError, "Pass either an options object or a block, not both!" unless options.nil? ^ block.nil?
|
118
|
+
block.call(options = Configuration.new) if block
|
119
|
+
options.each {|key, value| self[key] = value }
|
120
|
+
|
121
|
+
# special handling for paths and gems
|
122
|
+
(options.paths || {}).each {|type, path| self[:paths][type] = path }
|
123
|
+
(options.gems || []).each {|gem| self[:gems] << gem }
|
124
|
+
end
|
125
|
+
|
126
|
+
# Reverse merge the passed options with the configuration. See #merge for details about the arguments.
|
127
|
+
# A reverse merge is a non destructive merge, that is if a value already exists in the current configuration
|
128
|
+
# it has precedence over the one you pass in the arguments.
|
129
|
+
def reverse_merge(options = nil, &block)
|
130
|
+
raise ArgumentError, "Pass either an options object or a block, not both!" unless options.nil? ^ block.nil?
|
131
|
+
block.call(options = Configuration.new) if block
|
132
|
+
the_keys = self.keys
|
133
|
+
options.each {|key, value| self[key] = value unless the_keys.include?(key) }
|
134
|
+
|
135
|
+
# special handling for paths and gems
|
136
|
+
(options.paths || {}).each {|type, path| self[:paths][type] = path unless self[:paths][type] }
|
137
|
+
(options.gems || []).each {|gem| self[:gems] << gem }
|
138
|
+
end
|
139
|
+
|
140
|
+
# Adds a single Gem dependency to the reactive application.
|
141
|
+
# You may pass :init => :rails to specifiy that the plugin should
|
142
|
+
# be initialized through the rails init code. Note that it is not
|
143
|
+
# assured to work.
|
144
|
+
#
|
145
|
+
# config.gem 'aws-s3', :lib => 'aws/s3', :version => '>= 0.4.0'
|
146
|
+
# config.gem 'acts_as_tree', :init => :rails
|
147
|
+
#
|
148
|
+
def gem(name, options = {})
|
149
|
+
self[:gems] << Reactive::GemDependency.new(name, options)
|
150
|
+
|
151
|
+
name = name.gsub(/\W/, '_').squeeze('_')
|
152
|
+
self[name] = OrderedOptions.new
|
153
|
+
end
|
154
|
+
|
155
|
+
def app_gem_spec(spec = nil, &block) # :nodoc:
|
156
|
+
@app_gem_spec = block if block
|
157
|
+
if @app_gem_spec
|
158
|
+
if spec
|
159
|
+
@app_gem_spec.call(spec)
|
160
|
+
else
|
161
|
+
options = OrderedOptions.new
|
162
|
+
@app_gem_spec.call(options)
|
163
|
+
options
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Defines the application root. Normally the boot process will take care of it.
|
169
|
+
def root_dir=(value) # :nodoc:
|
170
|
+
self[:root_dir] = File.expand_path(value)
|
171
|
+
end
|
172
|
+
|
173
|
+
protected
|
174
|
+
|
175
|
+
def parse(argv)
|
176
|
+
if index = argv.rindex(argv.find {|arg| (arg == '-e') || (arg == '--environment')})
|
177
|
+
self.environment = argv[index+1]
|
178
|
+
end
|
179
|
+
if index = argv.rindex(argv.find {|arg| arg == '--root'})
|
180
|
+
self.root_dir = argv[index+1]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
# Handles Reactive initialization process.
|
187
|
+
#
|
188
|
+
# The initialization process is sliced in short parts named stages.
|
189
|
+
# Each stage is responsible for a specific initialization. Plugins will also
|
190
|
+
# register stages in this system.
|
191
|
+
#
|
192
|
+
# Running the application is a three steps process:
|
193
|
+
# 1. Booting: loads the reactive-core gem (see config/boot.rb)
|
194
|
+
# 2. Initialization (the #run method is called)
|
195
|
+
# 3. Dispatching the initial request (as configured in config/config.rb)
|
196
|
+
#
|
197
|
+
# The application developer may also register initialization stages by example
|
198
|
+
# in the files under config/initializers/*.rb like so:
|
199
|
+
# Reactive::Initializer.init :donuts do
|
200
|
+
# require 'donuts'
|
201
|
+
# Donuts.setup_factory
|
202
|
+
# end
|
203
|
+
#
|
204
|
+
# Registering stages is done with the general #register method or the more conveniant
|
205
|
+
# methods: #configure, #before_init, #init and #after_init.
|
206
|
+
#
|
207
|
+
# The application or any plugin may observe the initilization process by registering an
|
208
|
+
# observer with #add_observer. It acts like a callback triggered before processing each
|
209
|
+
# stage.
|
210
|
+
#
|
211
|
+
# The stages are left visible with the #stages accessor, but be careful with it.
|
212
|
+
class Initializer
|
213
|
+
STAGE_LEVELS = {:configure => -400, :before_init => -200, :init => 0, :init_rails_plugin => 100, :after_init => 200}
|
214
|
+
|
215
|
+
include Singleton
|
216
|
+
|
217
|
+
class << self
|
218
|
+
# Registers an initialization stage. Pass a _name_, then either a level or an option to specify
|
219
|
+
# the relation against another stage.
|
220
|
+
# register(:mvc_configure, -100) { some_code }
|
221
|
+
# register(:mvc_configure, :before => :init_logger) { some_code }
|
222
|
+
# register(:mvc_configure, :after => :wx_configure, :proc => a_proc)
|
223
|
+
def register(name, options = {}, &block)
|
224
|
+
Initializer.instance.register(name, options, &block)
|
225
|
+
end
|
226
|
+
|
227
|
+
# convenience aliases for standard priorities
|
228
|
+
def configure(name, options = {}, &block)
|
229
|
+
name = "configure_#{name}" unless name.to_s =~ /^configure_/
|
230
|
+
register(name, :configure, &block)
|
231
|
+
end
|
232
|
+
|
233
|
+
def before_init(name, options = {}, &block)
|
234
|
+
name = "before_init_#{name}" unless name.to_s =~ /^before_init_/
|
235
|
+
register(name, :before_init, &block)
|
236
|
+
end
|
237
|
+
|
238
|
+
def init(name, options = {}, &block)
|
239
|
+
name = "init_#{name}" unless name.to_s =~ /^init_/
|
240
|
+
register(name, :init, &block)
|
241
|
+
end
|
242
|
+
|
243
|
+
def after_init(name, options = {}, &block)
|
244
|
+
name = "after_init_#{name}" unless name.to_s =~ /^after_init_/
|
245
|
+
register(name, :after_init, &block)
|
246
|
+
end
|
247
|
+
|
248
|
+
# Runs the initialization process.
|
249
|
+
#
|
250
|
+
# _stage_ is either a level which will also be processed or
|
251
|
+
# a stage name that will also be processed.
|
252
|
+
def run(stage = :final, configuration = Reactive.configuration || Configuration.new) # :yields: configuration
|
253
|
+
yield configuration if block_given?
|
254
|
+
Reactive.configuration = configuration
|
255
|
+
Initializer.instance.run_stages(stage)
|
256
|
+
end
|
257
|
+
|
258
|
+
def add_observer(proc = nil, &block)
|
259
|
+
raise ArgumentError, "Pass either a proc or a block, not both!" unless proc.nil? ^ block.nil?
|
260
|
+
raise ArgumentError, "Passed object is not callable!" if proc && !proc.respond_to?(:call)
|
261
|
+
Initializer.instance.observers << (proc || block)
|
262
|
+
end
|
263
|
+
|
264
|
+
def stages
|
265
|
+
Initializer.instance.stages
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
269
|
+
|
270
|
+
class Stage
|
271
|
+
attr_accessor :name, :level, :proc
|
272
|
+
def initialize(name, level, proc)
|
273
|
+
@name, @level, @proc = name, level, proc
|
274
|
+
end
|
275
|
+
# Checks equivalence based on #name.
|
276
|
+
def ==(other)
|
277
|
+
other.is_a?(Stage) ? @name == other.name : @name == other
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
attr_reader :stages, :observers # :nodoc:
|
282
|
+
|
283
|
+
def initialize # :nodoc:
|
284
|
+
@stages = []
|
285
|
+
@finished = []
|
286
|
+
@observers = []
|
287
|
+
end
|
288
|
+
|
289
|
+
def register(name, options, &block) # :nodoc:
|
290
|
+
options = options.is_a?(Hash) ? options : {:level => STAGE_LEVELS[options] || options}
|
291
|
+
proc = options[:proc]
|
292
|
+
raise ArgumentError, "Pass either a proc or a block, not both!" unless proc.nil? ^ block.nil?
|
293
|
+
raise ArgumentError, "Proc object is not callable!" if proc && !proc.respond_to?(:call)
|
294
|
+
block ||= proc
|
295
|
+
# raise ArgumentError, "register options are mutually exclusive!" if options[:level]
|
296
|
+
case
|
297
|
+
when level = options[:level]
|
298
|
+
insert_index = stages.index(stages.find {|item| item.level > level}) || -1
|
299
|
+
when before = options[:before]
|
300
|
+
insert_index = stages.index(stages.find{|item| item.name == before.to_sym})
|
301
|
+
raise ArgumentError, "Can't insert before '#{before}', no stage of that name!" unless insert_index
|
302
|
+
items = [insert_index-1 < 0 ? nil : insert_index-1, insert_index].compact
|
303
|
+
level = stages.values_at(*items).inject {|sum, stage| sum + stage.level} / items.size
|
304
|
+
when after = options[:after]
|
305
|
+
insert_index = stages.index(stages.find{|item| item.name == after.to_sym})
|
306
|
+
raise ArgumentError, "Can't insert after '#{after}', no stage of that name!" unless insert_index
|
307
|
+
items = stages.values_at([index, index+1]).compact
|
308
|
+
level = items.inject {|sum, stage| sum + stage.level} / items.size
|
309
|
+
end
|
310
|
+
|
311
|
+
stages.insert(insert_index, Stage.new(name, level, block))
|
312
|
+
end
|
313
|
+
|
314
|
+
# last_stage is either a level which WILL also be processed or
|
315
|
+
# a stage name that WILL also be processed.
|
316
|
+
def run_stages(last_stage) # :nodoc:
|
317
|
+
last_stage = STAGE_LEVELS[last_stage] || last_stage
|
318
|
+
stage = stages.first
|
319
|
+
while stage
|
320
|
+
break if last_stage.is_a?(Integer) && stage.level > last_stage
|
321
|
+
unless @finished.include? stage
|
322
|
+
observers.each {|block| block.call(stage) }
|
323
|
+
benchlog("Stage: #{stage.name}") { stage.proc.call }
|
324
|
+
@finished << stage
|
325
|
+
break if stage.name == last_stage
|
326
|
+
end
|
327
|
+
stage = stages[stages.index(stage).succ]
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
protected
|
332
|
+
|
333
|
+
def benchlog(caption) # :nodoc:
|
334
|
+
@bench_level ||= 0
|
335
|
+
time = Time.now.to_f
|
336
|
+
Reactive.logger.debug "#{' '*@bench_level}#{caption}" if @bench_level > 0
|
337
|
+
@bench_level += 2
|
338
|
+
yield if block_given?
|
339
|
+
seconds = Time.now.to_f - time
|
340
|
+
@bench_level -= 2
|
341
|
+
if @bench_level > 0
|
342
|
+
Reactive.logger.debug "#{' '*@bench_level}(#{'%.1f' % (seconds * 1000)}ms)"
|
343
|
+
else
|
344
|
+
Reactive.logger.debug "#{caption} (#{'%.1f' % (seconds * 1000)}ms)"
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
# Here is the list of all initializers setup up by the framework. Listed in level order.
|
349
|
+
# *patch_base_gems*:: -1000,
|
350
|
+
# *default_environment*:: -950,
|
351
|
+
# *default_config*:: -760,
|
352
|
+
# *load_config*:: -750
|
353
|
+
# *load_initializers*:: -740
|
354
|
+
# *require_plugins*:: -550
|
355
|
+
# *require_framework*:: -360
|
356
|
+
# *check_plugins*:: -350
|
357
|
+
# *init_logger*:: -160
|
358
|
+
# *init_depedency_mechanism*:: -150
|
359
|
+
# *init_framwork*:: -140
|
360
|
+
# *finish*:: +1000
|
361
|
+
class Default < Initializer
|
362
|
+
register :patch_base_gems, -1000 do
|
363
|
+
# Patch missing activesupport features
|
364
|
+
ActiveSupport.const_set(:Dependencies, ::Dependencies) unless defined? ActiveSupport::Dependencies
|
365
|
+
require 'reactive-core/ext/object_instance' unless Object.respond_to? :instance_variable_names
|
366
|
+
require 'reactive-core/ext/memoizable' unless defined? ActiveSupport::Memoizable
|
367
|
+
end
|
368
|
+
|
369
|
+
register :default_environment, -950 do
|
370
|
+
unless Reactive.configuration.environment
|
371
|
+
Reactive.configuration.environment = Gem.source_index.find_name('reactive-dev').empty? ? 'production' : 'development'
|
372
|
+
end
|
373
|
+
if Reactive.configuration.environment == 'development'
|
374
|
+
# gem 'reactive-dev', core-gem-version
|
375
|
+
require 'reactive-dev'
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
register :default_config, -760 do
|
380
|
+
Reactive.configuration.reverse_merge do |config|
|
381
|
+
config.paths = {
|
382
|
+
:config => Reactive.path_for("config"),
|
383
|
+
:log => Reactive.path_for("log"),
|
384
|
+
:assets => [Reactive.path_for("assets", "default")]
|
385
|
+
}
|
386
|
+
end
|
387
|
+
Reactive.configuration.reverse_merge do |config|
|
388
|
+
config.global_configfile = Reactive.path_for(:config, "config.rb")
|
389
|
+
config.environment_configfile = Reactive.path_for(:config, "environments", "#{Reactive.configuration.environment}.rb")
|
390
|
+
|
391
|
+
config.log_path = Reactive.path_for(:log, "#{Reactive.configuration.environment}.log")
|
392
|
+
config.log_level = Reactive.configuration.environment == 'production' ? :warn : :info
|
393
|
+
|
394
|
+
config.dependency_mechanism = :load
|
395
|
+
config.cache_classes = false
|
396
|
+
|
397
|
+
# stubs for setting options on the framework base classes
|
398
|
+
config.dispatcher = OrderedOptions.new
|
399
|
+
config.output_handler = OrderedOptions.new
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
# Loads the environment configuration specified by Configuration#global_configfile and #environment_configfile
|
404
|
+
# which is typically one of development, test, or production.
|
405
|
+
register :load_config, -750 do
|
406
|
+
load(Reactive.configuration.global_configfile) if File.exists?(Reactive.configuration.global_configfile)
|
407
|
+
load(Reactive.configuration.environment_configfile) if File.exists?(Reactive.configuration.environment_configfile)
|
408
|
+
end
|
409
|
+
|
410
|
+
register :load_initializers, -740 do
|
411
|
+
Dir[Reactive.path_for(:config, "initializers/**/*.rb")].sort.each do |initializer|
|
412
|
+
load(initializer)
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
# load all plugins gems listed in configurations.
|
417
|
+
register :require_plugins, -550 do
|
418
|
+
Reactive.configuration.gems.each do |gem|
|
419
|
+
begin
|
420
|
+
Reactive.logger.info "Loading gem #{gem.name} #{gem.requirement}"
|
421
|
+
gem.load
|
422
|
+
rescue LoadError
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
register :require_framework, -360 do
|
428
|
+
require 'reactive-core/errors'
|
429
|
+
require 'reactive-core/request'
|
430
|
+
require 'reactive-core/response'
|
431
|
+
require 'reactive-core/dispatcher'
|
432
|
+
require 'reactive-core/output_handler'
|
433
|
+
require 'reactive-core/meta_model'
|
434
|
+
end
|
435
|
+
|
436
|
+
# Abort execution if any of the configured plugins isn't loaded
|
437
|
+
register :check_plugins, -350 do
|
438
|
+
unloaded_gems = Reactive.configuration.gems.reject {|gem| gem.loaded? }
|
439
|
+
unless unloaded_gems.empty?
|
440
|
+
message = <<-EOS
|
441
|
+
Missing these required gems:
|
442
|
+
#{unloaded_gems.map {|gem| "#{gem.name} #{gem.requirement}"}.join("\n ")}
|
443
|
+
Run `rake gems:install` to install the missing gems.
|
444
|
+
EOS
|
445
|
+
Reactive.logger.fatal message
|
446
|
+
raise LoadError, message
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
register :init_logger, -160 do
|
451
|
+
begin
|
452
|
+
unless logger = Reactive.configuration.logger
|
453
|
+
Reactive.logger.info "Further logs should go to #{Reactive.configuration.log_path}"
|
454
|
+
logger = ActiveSupport::BufferedLogger.new(Reactive.configuration.log_path)
|
455
|
+
logger.level = Reactive.configuration.log_level.is_a?(Integer) ? Reactive.configuration.log_level : ActiveSupport::BufferedLogger.const_get(Reactive.configuration.log_level.to_s.upcase)
|
456
|
+
logger.auto_flushing = false if Reactive.configuration.environment == "production"
|
457
|
+
end
|
458
|
+
Reactive.logger = logger
|
459
|
+
rescue StandardError => e
|
460
|
+
Reactive.logger.warn "Reactive Error: Unable to access log file. Please ensure that #{Reactive.configuration.log_path} exists and is chmod 0666."
|
461
|
+
ensure
|
462
|
+
Reactive.logger.unknown "\n\nApplication initialized at #{Time.now.to_s(:db)}"
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
register :init_depedency_mechanism, -150 do
|
467
|
+
ActiveSupport::Dependencies.mechanism = Reactive.configuration.cache_classes ? :require : :load
|
468
|
+
end
|
469
|
+
|
470
|
+
register :init_framework, -140 do
|
471
|
+
[:dispatcher, :output_handler].each do |framework|
|
472
|
+
base_class = Reactive.const_get(framework.to_s.classify)::Base
|
473
|
+
base_class.logger = Reactive.logger
|
474
|
+
Reactive.configuration.send(framework).each do |setting, value|
|
475
|
+
base_class.send("#{setting}=", value)
|
476
|
+
end
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
register :finish, +1000 do
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
end
|
485
|
+
|
486
|
+
|
487
|
+
end
|
488
|
+
|