1 require 'initializer'
2 require 'radiant/admin_ui'
3 require 'radiant/extension_loader'
4
5 module Radiant
6 autoload :Cache, 'radiant/cache'
7
8 class << self
9 # Returns the Radiant::Config eigenclass object, so it can be used wherever you would use Radiant::Config.
10 #
11 # Radiant.config['site.title']
12 # Radiant.config['site.url'] = 'example.com'
13 #
14 # but it will also yield itself to a block:
15 #
16 # Radiant.config do |config|
17 # config.define 'something', default => 'something'
18 # config['other.thing'] = 'nothing'
19 # end
20 #
21 def config # method must be defined before any initializers run
22 yield Radiant::Config if block_given?
23 Radiant::Config
24 end
25
26 # Returns the configuration object with which this application was initialized.
27 # For now it's exactly the same as calling Rails.configuration except that it will also yield itself to a block.
28 #
29 def configuration
30 yield Rails.configuration if block_given?
31 Rails.configuration
32 end
33
34 # Returns the root directory of this radiant installation (which is usually the gem directory).
35 # This is not the same as Rails.root, which is the instance directory and tends to contain only site-delivery material.
36 #
37 def root
38 Pathname.new(RADIANT_ROOT) if defined?(RADIANT_ROOT)
39 end
40 end
41
42 # NB. Radiant::Configuration (aka Radiant.configuration) is our extension-aware subclass of Rails::Configuration
43 # Radiant::Config (aka Radiant.config) is the application-configuration model class.
44
45 class Configuration < Rails::Configuration
46
47 # The Radiant::Configuration class extends Rails::Configuration with three purposes:
48 # * to reset some rails defaults so that files are found in RADIANT_ROOT instead of RAILS_ROOT
49 # * to notice that some gems and plugins are in fact radiant extensions
50 # * to notice that some radiant extensions add load paths (for plugins, controllers, metal, etc)
51
52 attr_accessor :extension_paths, :ignored_extensions
53
54 def initialize #:nodoc:
55 self.extension_paths = default_extension_paths
56 self.ignored_extensions = []
57 super
58 end
59
60 # Sets the locations in which we look for vendored extensions. Normally:
61 # Rails.root/vendor/extensions
62 # Radiant.root/vendor/extensions
63 # There are no vendor/* directories in +RADIANT_ROOT+ any more but the possibility remains for compatibility reasons.
64 # In test mode we also add a fixtures path for testing the extension loader.
65 #
66 def default_extension_paths
67 env = ENV["RAILS_ENV"] || RAILS_ENV
68 paths = [Rails.root + 'vendor/extensions']
69 paths.unshift(Radiant.root + "vendor/extensions") unless Rails.root == Radiant.root
70 paths.unshift(Radiant.root + "test/fixtures/extensions") if env == "test"
71 paths
72 end
73
74 # The list of extensions, expanded and in load order, that results from combining all the extension
75 # configuration directives. These are the extensions that will actually be loaded or migrated,
76 # and for most purposes this is the list you want to refer to.
77 #
78 # Radiant.configuration.enabled_extensions # => [:name, :name, :name, :name]
79 #
80 # Note that an extension enabled is not the same as an extension activated or even loaded: it just means
81 # that the application is configured to load that extension.
82 #
83 def enabled_extensions
84 @enabled_extensions ||= expanded_extension_list - ignored_extensions
85 end
86
87 # The expanded and ordered list of extensions, including any that may later be ignored. This can be configured
88 # (it is here that the :all entry is expanded to mean 'everything else'), or will default to an alphabetical list
89 # of every extension found among gems and vendor/extensions directories.
90 #
91 # Radiant.configuration.expanded_extension_list # => [:name, :name, :name, :name]
92 #
93 # If an extension in the configurted list is not found, a LoadError will be thrown from here.
94 #
95 def expanded_extension_list
96 # NB. it should remain possible to say config.extensions = []
97 @extension_list ||= extensions ? expand_and_check(extensions) : available_extensions
98 end
99
100 def expand_and_check(extension_list) #:nodoc
101 missing_extensions = extension_list - [:all] - available_extensions
102 raise LoadError, "These configured extensions have not been found: #{missing_extensions.to_sentence}" if missing_extensions.any?
103 if m = extension_list.index(:all)
104 extension_list[m] = available_extensions - extension_list
105 end
106 extension_list.flatten
107 end
108
109 # Returns the checked and expanded list of extensions-to-enable. This may be derived from a list passed to
110 # +config.extensions=+ or it may have defaulted to all available extensions.
111
112 # Without such a call, we default to the alphabetical list of all well-formed vendor and gem extensions
113 # returned by +available_extensions+.
114 #
115 # Radiant.configuration.extensions # => [:name, :all, :name]
116 #
117 def extensions
118 @requested_extensions ||= available_extensions
119 end
120
121 # Sets the list of extensions that will be loaded and the order in which to load them.
122 # It can include an :all marker to mean 'everything else' and is typically set in environment.rb:
123 # config.extensions = [:layouts, :taggable, :all]
124 # config.extensions = [:dashboard, :blog, :all]
125 # config.extensions = [:dashboard, :blog, :all, :comments]
126 #
127 # A LoadError is raised if any of the specified extensions can't be found.
128 #
129 def extensions=(extensions)
130 @requested_extensions = extensions
131 end
132
133 # This is a configurable list of extension that should not be loaded.
134 # config.ignore_extensions = [:experimental, :broken]
135 # You can also retrieve the list with +ignored_extensions+:
136 # Radiant.configuration.ignored_extensions # => [:experimental, :broken]
137 # These exclusions are applied regardless of dependencies and extension locations. A configuration that bundles
138 # required extensions then ignores them will not boot and is likely to fail with errors about unitialized constants.
139 #
140 def ignore_extensions(array)
141 self.ignored_extensions |= array
142 end
143
144 # Returns an alphabetical list of every extension found among all the load paths and bundled gems. Any plugin or
145 # gem whose path ends in the form +radiant-something-extension+ is considered to be an extension.
146 #
147 # Radiant.configuration.available_extensions # => [:name, :name, :name, :name]
148 #
149 # This method is always called during initialization, either as a default or to check that specified extensions are
150 # available. One of its side effects is to populate the ExtensionLoader's list of extension root locations, later
151 # used when activating those extensions that have been enabled.
152 #
153 def available_extensions
154 @available_extensions ||= (vendored_extensions + gem_extensions).uniq.sort.map(&:to_sym)
155 end
156
157 # Searches the defined extension_paths for subdirectories and returns a list of names as symbols.
158 #
159 # Radiant.configuration.vendored_extensions # => [:name, :name]
160 #
161 def vendored_extensions
162 extension_paths.each_with_object([]) do |load_path, found|
163 Dir["#{load_path}/*"].each do |path|
164 if File.directory?(path)
165 ep = ExtensionLoader.record_path(path)
166 found << ep.name
167 end
168 end
169 end
170 end
171
172 # Scans the bundled gems for any whose name match the +radiant-something-extension+ format
173 # and returns a list of their names as symbols.
174 #
175 # Radiant.configuration.gem_extensions # => [:name, :name]
176 #
177 def gem_extensions
178 Gem.loaded_specs.each_with_object([]) do |(gemname, gemspec), found|
179 if gemname =~ /radiant-.*-extension$/
180 ep = ExtensionLoader.record_path(gemspec.full_gem_path, gemname)
181 found << ep.name
182 end
183 end
184 end
185
186 # Old extension-dependency mechanism now deprecated
187 #
188 def extension(ext)
189 ::ActiveSupport::Deprecation.warn("Extension dependencies have been deprecated and are no longer supported in radiant 1.0. Extensions with dependencies should be packaged as gems and use the .gemspec to declare them.", caller)
190 end
191
192 # Old gem-invogation method now deprecated
193 #
194 def gem(name, options = {})
195 ::ActiveSupport::Deprecation.warn("Please declare gem dependencies in your Gemfile (or for an extension, in the .gemspec file).", caller)
196 super
197 end
198
199 # Returns the AdminUI singleton, giving get-and-set access to the tabs and partial-sets it defines.
200 # More commonly accessed in the initializer via its call to +configuration.admin+.
201 #
202 def admin
203 AdminUI.instance
204 end
205
206 %w{controller model view metal plugin load eager_load}.each do |type|
207 define_method("add_#{type}_paths".to_sym) do |paths|
208 self.send("#{type}_paths".to_sym).concat(paths)
209 end
210 end
211
212 private
213
214 # Overrides the Rails::Initializer default so that autoload paths for models, controllers etc point to
215 # directories in RADIANT_ROOT rather than in RAILS_ROOT.
216 #
217 def default_autoload_paths
218 paths = ["#{RADIANT_ROOT}/test/mocks/#{environment}"]
219
220 # Add the app's controller directory
221 paths.concat(Dir["#{RADIANT_ROOT}/app/controllers/"])
222
223 # Followed by the standard includes.
224 paths.concat %w(
225 app
226 app/metal
227 app/models
228 app/controllers
229 app/helpers
230 config
231 lib
232 vendor
233 ).map { |dir| "#{RADIANT_ROOT}/#{dir}" }.select { |dir| File.directory?(dir) }
234
235 paths.concat builtin_directories
236 end
237
238 # Overrides the Rails::Initializer default to add plugin paths in RADIANT_ROOT as well as RAILS_ROOT.
239 #
240 def default_plugin_paths
241 super + ["#{RADIANT_ROOT}/lib/plugins", "#{RADIANT_ROOT}/vendor/plugins"]
242 end
243
244 # Overrides the Rails::Initializer default to look for views in RADIANT_ROOT rather than RAILS_ROOT.
245 #
246 def default_view_path
247 File.join(RADIANT_ROOT, 'app', 'views')
248 end
249
250 # Overrides the Rails::Initializer default to look for controllers in RADIANT_ROOT rather than RAILS_ROOT.
251 #
252 def default_controller_paths
253 [File.join(RADIANT_ROOT, 'app', 'controllers')]
254 end
255 end
256
257 class Initializer < Rails::Initializer
258
259 # Rails::Initializer is essentially a list of startup steps and we extend it here by:
260 # * overriding or extending some of those steps so that they use radiant and extension paths
261 # as well as (or instead of) the rails defaults.
262 # * appending some extra steps to set up the admin UI and activate extensions
263
264 def self.run(command = :process, configuration = Configuration.new) #:nodoc
265 Rails.configuration = configuration
266 super
267 end
268
269 # Returns true in the very unusual case where radiant has been deployed as a rails app itself, rather than
270 # loaded as a gem or from vendor/. This is only likely in situations where radiant is customised so heavily
271 # that extensions are not sufficient.
272 #
273 def deployed_as_app?
274 RADIANT_ROOT == RAILS_ROOT
275 end
276
277 # Extends the Rails::Initializer default to add extension paths to the autoload list.
278 # Note that +default_autoload_paths+ is also overridden to point to RADIANT_ROOT.
279 #
280 def set_autoload_paths
281 extension_loader.paths(:load).reverse_each do |path|
282 configuration.autoload_paths.unshift path
283 $LOAD_PATH.unshift path
284 end
285 super
286 end
287
288 # Overrides the Rails initializer to load metal from RADIANT_ROOT and from radiant extensions.
289 #
290 def initialize_metal
291 Rails::Rack::Metal.requested_metals = configuration.metals
292 Rails::Rack::Metal.metal_paths = ["#{RADIANT_ROOT}/app/metal"] # reset Rails default to RADIANT_ROOT
293 Rails::Rack::Metal.metal_paths += plugin_loader.engine_metal_paths
294 Rails::Rack::Metal.metal_paths += extension_loader.paths(:metal)
295
296 configuration.middleware.insert_before(
297 :"ActionController::ParamsParser",
298 Rails::Rack::Metal, :if => Rails::Rack::Metal.metals.any?)
299 end
300
301 # Extends the Rails initializer to add locale paths from RADIANT_ROOT and from radiant extensions.
302 #
303 def initialize_i18n
304 radiant_locale_paths = Dir[File.join(RADIANT_ROOT, 'config', 'locales', '*.{rb,yml}')]
305 configuration.i18n.load_path = radiant_locale_paths + extension_loader.paths(:locale)
306 super
307 end
308
309 # Extends the Rails initializer to add plugin paths in extensions
310 # and makes extension load paths reloadable (eg in development mode)
311 #
312 def add_plugin_load_paths
313 configuration.add_plugin_paths(extension_loader.paths(:plugin))
314 super
315 ActiveSupport::Dependencies.autoload_once_paths -= extension_loader.paths(:load)
316 end
317
318 # Overrides the standard gem-loader to use Bundler instead of config.gem. This is the method normally monkey-patched
319 # into Rails::Initializer from boot.rb if you follow the instructions at http://gembundler.com/rails23.html
320 #
321 def load_gems
322 @bundler_loaded ||= Bundler.require :default, Rails.env
323 end
324
325 # Extends the Rails initializer also to load radiant extensions (which have been excluded from the list of plugins).
326 #
327 def load_plugins
328 super
329 extension_loader.load_extensions
330 end
331
332 # Extends the Rails initializer to run initializers from radiant and from extensions. The load order will be:
333 # 1. RADIANT_ROOT/config/intializers/*.rb
334 # 2. RAILS_ROOT/config/intializers/*.rb
335 # 3. config/initializers/*.rb found in extensions, in extension load order.
336 #
337 # In the now rare case where radiant is deployed as an ordinary rails application, step 1 is skipped
338 # because it is equivalent to step 2.
339 #
340 def load_application_initializers
341 load_radiant_initializers unless deployed_as_app?
342 super
343 extension_loader.load_extension_initalizers
344 end
345
346 # Loads initializers found in RADIANT_ROOT/config/initializers.
347 #
348 def load_radiant_initializers
349 Dir["#{RADIANT_ROOT}/config/initializers/**/*.rb"].sort.each do |initializer|
350 load(initializer)
351 end
352 end
353
354 # Extends the Rails initializer with some extra steps at the end of initialization:
355 # * hook up radiant view paths in controllers and notifiers
356 # * initialize the navigation tabs in the admin interface
357 # * initialize the extendable partial sets that make up the admin interface
358 # * call +activate+ on all radiant extensions
359 # * add extension controller paths
360 # * mark extension app paths for eager loading
361 #
362 def after_initialize
363 super
364 extension_loader.activate_extensions # also calls initialize_views
365 configuration.add_controller_paths(extension_loader.paths(:controller))
366 configuration.add_eager_load_paths(extension_loader.paths(:eager_load))
367 end
368
369 # Initializes all the admin interface elements and views. Separate here so that it can be called
370 # to reset the interface before extension (re)activation.
371 #
372 def initialize_views
373 initialize_default_admin_tabs
374 initialize_framework_views
375 admin.load_default_regions
376 end
377
378 # Initializes the core admin tabs. Separate so that it can be invoked by itself in tests.
379 #
380 def initialize_default_admin_tabs
381 admin.initialize_nav
382 end
383
384 # This adds extension view paths to the standard Rails::Initializer method.
385 # In environments that don't cache templates it reloads the path set on each request,
386 # so that new extension paths are noticed without a restart.
387 #
388 def initialize_framework_views
389 view_paths = extension_loader.paths(:view).push(configuration.view_path)
390 if ActionController::Base.view_paths.blank? || !ActionView::Base.cache_template_loading?
391 ActionController::Base.view_paths = ActionView::Base.process_view_paths(view_paths)
392 end
393 if configuration.frameworks.include?(:action_mailer) && ActionMailer::Base.view_paths.blank? || !ActionView::Base.cache_template_loading?
394 ActionMailer::Base.view_paths = ActionView::Base.process_view_paths(view_paths) if configuration.frameworks.include?(:action_mailer)
395 end
396 end
397
398 # Extends the Rails initializer to make sure that extension controller paths are available when routes
399 # are initialized.
400 #
401 def initialize_routing
402 configuration.add_controller_paths(extension_loader.paths(:controller))
403 configuration.add_eager_load_paths(extension_loader.paths(:eager_load))
404 super
405 end
406
407 # Returns the Radiant::AdminUI singleton so that the initializer can set up the admin interface.
408 #
409 def admin
410 configuration.admin
411 end
412
413 # Returns the ExtensionLoader singleton that will eventually load extensions.
414 #
415 def extension_loader
416 ExtensionLoader.instance {|l| l.initializer = self }
417 end
418
419 end
420 end