annotate vendor/gems/coderay-1.0.0/lib/coderay/helpers/plugin.rb @ 1472:f0b798dad2d6 feature_526

Close obsolete branch feature_526
author Chris Cannam
date Tue, 20 Nov 2012 19:45:51 +0000
parents cbb26bc654de
children
rev   line source
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