1 require 'radiant/extension'
2 require 'radiant/extension_path'
3 require 'method_observer'
4
5 module Radiant
6 class ExtensionLoader
7 # The ExtensionLoader is reponsible for the loading, activation and reactivation of extensions.
8 # The noticing of important subdirectories is now handled by the ExtensionPath class.
9
10 class DependenciesObserver < MethodObserver
11 # Extends the reload mechanism in ActiveSupport so that extensions are deactivated and reactivated
12 # when model classes are reloaded (in development mode, usually).
13
14 attr_accessor :config
15
16 def initialize(rails_config) #:nodoc
17 @config = rails_config
18 end
19
20 def before_clear(*args) #:nodoc
21 ExtensionLoader.deactivate_extensions
22 end
23
24 def after_clear(*args) #:nodoc
25 ExtensionLoader.load_extensions
26 ExtensionLoader.activate_extensions
27 end
28 end
29
30 include Simpleton
31
32 attr_accessor :initializer, :extensions
33
34 def initialize #:nodoc
35 self.extensions = []
36 end
37
38 # Returns a list of paths to all the extensions that are enabled in the configuration of this application.
39 #
40 def enabled_extension_paths
41 ExtensionPath.enabled.map(&:to_s)
42 end
43
44 # Returns a list of all the paths discovered within extension roots of the specified type.
45 # (by calling the corresponding class method on ExtensionPath).
46 #
47 # extension_loader.paths(:metal) #=> ['extension/app/metal', 'extension/app/metal']
48 # extension_loader.paths(:controller) #=> ['extension/app/controllers', 'extension/app/controllers']
49 # extension_loader.paths(:eager_load) #=> ['extension/app/controllers', 'extension/app/models', 'extension/app/helpers']
50 #
51 # For compatibility with the old loader, there are corresponding +type_paths+ methods.
52 # There are also (deprecated) +add_type_paths+ methods.
53 #
54 def paths(type)
55 ExtensionPath.send("#{type}_paths".to_sym)
56 end
57
58 # Loads but does not activate all the extensions that have been enabled, in the configured order
59 # (which defaults to alphabetically). If an extension fails to load an error will be logged
60 # but application startup will continue. If an extension doesn't exist, a LoadError will be raised
61 # and startup will halt.
62 #
63 def load_extensions
64 configuration = initializer.configuration
65 @observer ||= DependenciesObserver.new(configuration).observe(::ActiveSupport::Dependencies)
66 self.extensions = configuration.enabled_extensions.map { |ext| load_extension(ext) }.compact
67 end
68
69 # Loads the specified extension.
70 #
71 def load_extension(name)
72 extension_path = ExtensionPath.find(name)
73 begin
74 constant = "#{name}_extension".camelize
75 extension = constant.constantize
76 extension.unloadable
77 extension.path = extension_path
78 extension
79 rescue LoadError, NameError => e
80 $stderr.puts "Could not load extension: #{name}.\n#{e.inspect}"
81 nil
82 end
83 end
84
85 # Loads all the initializers defined in enabled extensions, in the configured order.
86 #
87 def load_extension_initalizers
88 extensions.each &:load_initializers
89 end
90
91 # Deactivates all enabled extensions.
92 #
93 def deactivate_extensions
94 extensions.each &:deactivate
95 end
96
97 # Activates all enabled extensions and makes sure that any newly declared subclasses of Page are recognised.
98 # The admin UI and views have to be reinitialized each time to pick up changes and avoid duplicates.
99 #
100 def activate_extensions
101 initializer.initialize_views
102 extensions.each &:activate
103 Page.load_subclasses
104 end
105 alias :reactivate :activate_extensions
106
107 class << self
108 # Builds an ExtensionPath object from the supplied path, working out the name of the extension on the way.
109 # The ExtensionPath object will later be used to scan and load the extension.
110 # An extension name can be supplied in addition to the path. It will be processed in the usual way to
111 # remove radiant- and -extension and any verion numbering.
112 #
113 def record_path(path, name=nil)
114 ExtensionPath.from_path(path, name)
115 end
116
117 # For compatibility with old calls probably still to be found in some extensions.
118 #
119 %w{controller model view metal plugin load locale}.each do |type|
120 define_method("#{type}_paths".to_sym) do
121 paths(type)
122 end
123 define_method("add_#{type}_paths".to_sym) do |additional_paths|
124 ::ActiveSupport::Deprecation.warn("ExtensionLoader.add_#{type}_paths is has been moved and is deprecated. Please use Radiant.configuration.add_#{type}_paths", caller)
125 initializer.configuration.send("add_#{type}_paths".to_sym, additional_paths)
126 end
127 end
128 end
129 end
130 end