reactive-core 0.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.
- 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
|
+
|