To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

root / .svn / pristine / 5d / 5d94857704d99231f1b0ea1e206666d924acf9ab.svn-base @ 1297:0a574315af3e

History | View | Annotate | Download (6.81 KB)

1
module CodeRay
2
  
3
  # = PluginHost
4
  #
5
  # A simple subclass/subfolder plugin system.
6
  #
7
  # Example:
8
  #  class Generators
9
  #    extend PluginHost
10
  #    plugin_path 'app/generators'
11
  #  end
12
  #  
13
  #  class Generator
14
  #    extend Plugin
15
  #    PLUGIN_HOST = Generators
16
  #  end
17
  #  
18
  #  class FancyGenerator < Generator
19
  #    register_for :fancy
20
  #  end
21
  #  
22
  #  Generators[:fancy]  #-> FancyGenerator
23
  #  # or
24
  #  CodeRay.require_plugin 'Generators/fancy'
25
  #  # or
26
  #  Generators::Fancy
27
  module PluginHost
28
    
29
    # Raised if Encoders::[] fails because:
30
    # * a file could not be found
31
    # * the requested Plugin is not registered
32
    PluginNotFound = Class.new LoadError
33
    HostNotFound = Class.new LoadError
34
    
35
    PLUGIN_HOSTS = []
36
    PLUGIN_HOSTS_BY_ID = {}  # dummy hash
37
    
38
    # Loads all plugins using list and load.
39
    def load_all
40
      for plugin in list
41
        load plugin
42
      end
43
    end
44
    
45
    # Returns the Plugin for +id+.
46
    #
47
    # Example:
48
    #  yaml_plugin = MyPluginHost[:yaml]
49
    def [] id, *args, &blk
50
      plugin = validate_id(id)
51
      begin
52
        plugin = plugin_hash.[] plugin, *args, &blk
53
      end while plugin.is_a? Symbol
54
      plugin
55
    end
56
    
57
    alias load []
58
    
59
    # Tries to +load+ the missing plugin by translating +const+ to the
60
    # underscore form (eg. LinesOfCode becomes lines_of_code).
61
    def const_missing const
62
      id = const.to_s.
63
        gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
64
        gsub(/([a-z\d])([A-Z])/,'\1_\2').
65
        downcase
66
      load id
67
    end
68
    
69
    class << self
70
      
71
      # Adds the module/class to the PLUGIN_HOSTS list.
72
      def extended mod
73
        PLUGIN_HOSTS << mod
74
      end
75
      
76
    end
77
    
78
    # The path where the plugins can be found.
79
    def plugin_path *args
80
      unless args.empty?
81
        @plugin_path = File.expand_path File.join(*args)
82
      end
83
      @plugin_path ||= ''
84
    end
85
    
86
    # Map a plugin_id to another.
87
    #
88
    # Usage: Put this in a file plugin_path/_map.rb.
89
    #
90
    #  class MyColorHost < PluginHost
91
    #    map :navy => :dark_blue,
92
    #      :maroon => :brown,
93
    #      :luna => :moon
94
    #  end
95
    def map hash
96
      for from, to in hash
97
        from = validate_id from
98
        to = validate_id to
99
        plugin_hash[from] = to unless plugin_hash.has_key? from
100
      end
101
    end
102
    
103
    # Define the default plugin to use when no plugin is found
104
    # for a given id, or return the default plugin.
105
    #
106
    # See also map.
107
    #
108
    #  class MyColorHost < PluginHost
109
    #    map :navy => :dark_blue
110
    #    default :gray
111
    #  end
112
    #  
113
    #  MyColorHost.default  # loads and returns the Gray plugin
114
    def default id = nil
115
      if id
116
        id = validate_id id
117
        raise "The default plugin can't be named \"default\"." if id == :default
118
        plugin_hash[:default] = id
119
      else
120
        load :default
121
      end
122
    end
123
    
124
    # Every plugin must register itself for +id+ by calling register_for,
125
    # which calls this method.
126
    #
127
    # See Plugin#register_for.
128
    def register plugin, id
129
      plugin_hash[validate_id(id)] = plugin
130
    end
131
    
132
    # A Hash of plugion_id => Plugin pairs.
133
    def plugin_hash
134
      @plugin_hash ||= make_plugin_hash
135
    end
136
    
137
    # Returns an array of all .rb files in the plugin path.
138
    #
139
    # The extension .rb is not included.
140
    def list
141
      Dir[path_to('*')].select do |file|
142
        File.basename(file)[/^(?!_)\w+\.rb$/]
143
      end.map do |file|
144
        File.basename(file, '.rb').to_sym
145
      end
146
    end
147
    
148
    # Returns an array of all Plugins.
149
    # 
150
    # Note: This loads all plugins using load_all.
151
    def all_plugins
152
      load_all
153
      plugin_hash.values.grep(Class)
154
    end
155
    
156
    # Loads the map file (see map).
157
    #
158
    # This is done automatically when plugin_path is called.
159
    def load_plugin_map
160
      mapfile = path_to '_map'
161
      @plugin_map_loaded = true
162
      if File.exist? mapfile
163
        require mapfile
164
        true
165
      else
166
        false
167
      end
168
    end
169
    
170
  protected
171
    
172
    # Return a plugin hash that automatically loads plugins.
173
    def make_plugin_hash
174
      @plugin_map_loaded ||= false
175
      Hash.new do |h, plugin_id|
176
        id = validate_id(plugin_id)
177
        path = path_to id
178
        begin
179
          raise LoadError, "#{path} not found" unless File.exist? path
180
          require path
181
        rescue LoadError => boom
182
          if @plugin_map_loaded
183
            if h.has_key?(:default)
184
              warn '%p could not load plugin %p; falling back to %p' % [self, id, h[:default]]
185
              h[:default]
186
            else
187
              raise PluginNotFound, '%p could not load plugin %p: %s' % [self, id, boom]
188
            end
189
          else
190
            load_plugin_map
191
            h[plugin_id]
192
          end
193
        else
194
          # Plugin should have registered by now
195
          if h.has_key? id
196
            h[id]
197
          else
198
            raise PluginNotFound, "No #{self.name} plugin for #{id.inspect} found in #{path}."
199
          end
200
        end
201
      end
202
    end
203
    
204
    # Returns the expected path to the plugin file for the given id.
205
    def path_to plugin_id
206
      File.join plugin_path, "#{plugin_id}.rb"
207
    end
208
    
209
    # Converts +id+ to a Symbol if it is a String,
210
    # or returns +id+ if it already is a Symbol.
211
    #
212
    # Raises +ArgumentError+ for all other objects, or if the
213
    # given String includes non-alphanumeric characters (\W).
214
    def validate_id id
215
      if id.is_a? Symbol or id.nil?
216
        id
217
      elsif id.is_a? String
218
        if id[/\w+/] == id
219
          id.downcase.to_sym
220
        else
221
          raise ArgumentError, "Invalid id given: #{id}"
222
        end
223
      else
224
        raise ArgumentError, "String or Symbol expected, but #{id.class} given."
225
      end
226
    end
227
    
228
  end
229
  
230
  
231
  # = Plugin
232
  #
233
  #  Plugins have to include this module.
234
  #
235
  #  IMPORTANT: Use extend for this module.
236
  #
237
  #  See CodeRay::PluginHost for examples.
238
  module Plugin
239
    
240
    attr_reader :plugin_id
241
    
242
    # Register this class for the given +id+.
243
    # 
244
    # Example:
245
    #   class MyPlugin < PluginHost::BaseClass
246
    #     register_for :my_id
247
    #     ...
248
    #   end
249
    #
250
    # See PluginHost.register.
251
    def register_for id
252
      @plugin_id = id
253
      plugin_host.register self, id
254
    end
255
    
256
    # Returns the title of the plugin, or sets it to the
257
    # optional argument +title+.
258
    def title title = nil
259
      if title
260
        @title = title.to_s
261
      else
262
        @title ||= name[/([^:]+)$/, 1]
263
      end
264
    end
265
    
266
    # The PluginHost for this Plugin class.
267
    def plugin_host host = nil
268
      if host.is_a? PluginHost
269
        const_set :PLUGIN_HOST, host
270
      end
271
      self::PLUGIN_HOST
272
    end
273
    
274
    def aliases
275
      plugin_host.load_plugin_map
276
      plugin_host.plugin_hash.inject [] do |aliases, (key, _)|
277
        aliases << key if plugin_host[key] == self
278
        aliases
279
      end
280
    end
281
    
282
  end
283
  
284
end