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