aboutsummaryrefslogtreecommitdiffstats
path: root/railties/lib/rails/plugin.rb
blob: 090ec6e4cba83c33db39c42fa53b7fa70a368876 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
module Rails
  # The Plugin class should be an object which provides the following methods:
  #
  # * +name+       - Used during initialisation to order the plugin (based on name and
  #                  the contents of <tt>config.plugins</tt>).
  # * +valid?+     - Returns true if this plugin can be loaded.
  # * +load_paths+ - Each path within the returned array will be added to the <tt>$LOAD_PATH</tt>.
  # * +load+       - Finally 'load' the plugin.
  #
  # These methods are expected by the Rails::Plugin::Locator and Rails::Plugin::Loader classes.
  # The default implementation returns the <tt>lib</tt> directory as its <tt>load_paths</tt>, 
  # and evaluates <tt>init.rb</tt> when <tt>load</tt> is called.
  #
  # You can also inspect the about.yml data programmatically:
  #
  #   plugin = Rails::Plugin.new(path_to_my_plugin)
  #   plugin.about["author"] # => "James Adam"
  #   plugin.about["url"] # => "http://interblah.net"
  class Plugin
    include Comparable
    include Initializable
    
    attr_reader :directory, :name
    
    def initialize(directory)
      @directory = directory
      @name      = File.basename(@directory) rescue nil
      @loaded    = false
    end
    
    def valid?
      File.directory?(directory) && (has_app_directory? || has_lib_directory? || has_init_file?)
    end
  
    # Returns a list of paths this plugin wishes to make available in <tt>$LOAD_PATH</tt>.
    def load_paths
      report_nonexistant_or_empty_plugin! unless valid?
      
      load_paths = []
      load_paths << lib_path  if has_lib_directory?
      load_paths << app_paths if has_app_directory?
      load_paths.flatten
    end
    
    # Evaluates a plugin's init.rb file.
    def load(initializer)
      return if loaded?
      report_nonexistant_or_empty_plugin! unless valid?
      evaluate_init_rb(initializer)
      @loaded = true
    end
    
    def loaded?
      @loaded
    end
    
    def <=>(other_plugin)
      name <=> other_plugin.name
    end

    def about
      @about ||= load_about_information
    end

    # Engines are plugins with an app/ directory.
    def engine?
      has_app_directory?
    end
    
    # Returns true if the engine ships with a routing file
    def routed?
      File.exist?(routing_file)
    end

    # Returns true if there is any localization file in locale_path
    def localized?
      locale_files.any?
    end

    def view_path
      File.join(directory, 'app', 'views')
    end

    def controller_path
      File.join(directory, 'app', 'controllers')
    end

    def metal_path
      File.join(directory, 'app', 'metal')
    end

    def routing_file
      File.join(directory, 'config', 'routes.rb')
    end

    def locale_path
      File.join(directory, 'config', 'locales')
    end

    def locale_files
      Dir[ File.join(locale_path, '*.{rb,yml}') ]
    end

  private
    def load_about_information
      about_yml_path = File.join(@directory, "about.yml")
      parsed_yml = File.exist?(about_yml_path) ? YAML.load(File.read(about_yml_path)) : {}
      parsed_yml || {}
    rescue Exception
      {}
    end

    def report_nonexistant_or_empty_plugin!
      raise LoadError, "Can not find the plugin named: #{name}"
    end

    def app_paths
      [ File.join(directory, 'app', 'models'), File.join(directory, 'app', 'helpers'), controller_path, metal_path ]
    end

    def lib_path
      File.join(directory, 'lib')
    end

    def classic_init_path
      File.join(directory, 'init.rb')
    end

    def gem_init_path
      File.join(directory, 'rails', 'init.rb')
    end

    def init_path
      File.file?(gem_init_path) ? gem_init_path : classic_init_path
    end

    def has_app_directory?
      File.directory?(File.join(directory, 'app'))
    end

    def has_lib_directory?
      File.directory?(lib_path)
    end

    def has_init_file?
      File.file?(init_path)
    end

    def evaluate_init_rb(initializer)
      if has_init_file?
        require 'active_support/core_ext/kernel/reporting'
        silence_warnings do
          # Allow plugins to reference the current configuration object
          config = initializer.configuration

          eval(IO.read(init_path), binding, init_path)
        end
      end
    end

    class Vendored < Plugin
      initializer :init_rb do |application|
        evaluate_init_rb(application)
      end
    end
  end

  # This Plugin subclass represents a Gem plugin. Although RubyGems has already
  # taken care of $LOAD_PATHs, it exposes its load_paths to add them
  # to Dependencies.load_paths.
  class GemPlugin < Plugin
    # Initialize this plugin from a Gem::Specification.
    def initialize(spec, gem)
      directory = spec.full_gem_path
      super(directory)
      @name = spec.name
    end

    def init_path
      File.join(directory, 'rails', 'init.rb')
    end
  end
end