gem_plugin.rb

来自「Amarok是一款在LINUX或其他类UNIX操作系统中运行的音频播放器软件。 」· RB 代码 · 共 288 行

RB
288
字号
require 'singleton'require 'rubygems'# Implements a dynamic plugin loading, configuration, and discovery system# based on RubyGems and a simple additional name space that looks like a URI.## A plugin is created and put into a category with the following code:##  class MyThing < GemPlugin::Plugin "/things"#    ...#  end# # What this does is sets up your MyThing in the plugin registry via GemPlugin::Manager.# You can then later get this plugin with GemPlugin::Manager.create("/things/mything")# and can also pass in options as a second parameter.## This isn't such a big deal, but the power is really from the GemPlugin::Manager.load# method.  This method will go through the installed gems and require_gem any# that depend on the gem_plugin RubyGem.  You can arbitrarily include or exclude# gems based on what they also depend on, thus letting you load these gems when appropriate.## Since this system was written originally for the Mongrel project that'll be the# best example of using it.## Imagine you have a neat plugin for Mongrel called snazzy_command that gives the# mongrel_rails a new command snazzy (like:  mongrel_rails snazzy).  You'd like# people to be able to grab this plugin if they want and use it, because it's snazzy.## First thing you do is create a gem of your project and make sure that it depends# on "mongrel" AND "gem_plugin".  This signals to the GemPlugin system that this is# a plugin for mongrel.## Next you put this code into a file like lib/init.rb (can be anything really):##  class Snazzy < GemPlugin::Plugin "/commands"#    ...#  end#  # Then when you create your gem you have the following bits in your Rakefile:##  spec.add_dependency('mongrel', '>= 0.3.9')#  spec.add_dependency('gem_plugin', '>= 0.1')#  spec.autorequire = 'init.rb'## Finally, you just have to now publish this gem for people to install and Mongrel# will "magically" be able to install it.## The "magic" part though is pretty simple and done via the GemPlugin::Manager.load# method.  Read that to see how it is really done.module GemPlugin  EXCLUDE = true  INCLUDE = false  class PluginNotLoaded < StandardError; end    # This class is used by people who use gem plugins (but don't necessarily make them)  # to add plugins to their own systems.  It provides a way to load plugins, list them,  # and create them as needed.  #  # It is a singleton so you use like this:  GemPlugins::Manager.instance.load  class Manager    include Singleton    attr_reader :plugins    attr_reader :gems        def initialize      # plugins that have been loaded      @plugins = {}      # keeps track of gems which have been loaded already by the manager *and*      # where they came from so that they can be referenced later      @gems = {}    end    # Responsible for going through the list of available gems and loading     # any plugins requested.  It keeps track of what it's loaded already    # and won't load them again.    #    # It accepts one parameter which is a hash of gem depends that should include    # or exclude a gem from being loaded.  A gem must depend on gem_plugin to be    # considered, but then each system has to add it's own INCLUDE to make sure    # that only plugins related to it are loaded.    #    # An example again comes from Mongrel.  In order to load all Mongrel plugins:    #    #  GemPlugin::Manager.instance.load "mongrel" => GemPlugin::INCLUDE    #    # Which will load all plugins that depend on mongrel AND gem_plugin.  Now, one    # extra thing we do is we delay loading Rails Mongrel plugins until after rails    # is configured.  Do do this the mongrel_rails script has:    #    # GemPlugin::Manager.instance.load "mongrel" => GemPlugin::INCLUDE, "rails" => GemPlugin::EXCLUDE    # The only thing to remember is that this is saying "include a plugin if it    # depends on gem_plugin, mongrel, but NOT rails".  If a plugin also depends on other    # stuff then it's loaded just fine.  Only gem_plugin, mongrel, and rails are    # ever used to determine if it should be included.    #    # NOTE: Currently RubyGems will run autorequire on gems that get required AND    # on their dependencies.  This really messes with people running edge rails    # since activerecord or other stuff gets loaded for just touching a gem plugin.    # To prevent this load requires the full path to the "init.rb" file, which    # avoids the RubyGems autorequire magic.    def load(needs = {})      sdir = File.join(Gem.dir, "specifications")      gems = Gem::SourceIndex.from_installed_gems(sdir)      needs = needs.merge({"gem_plugin" => INCLUDE})            gems.each do |path, gem|        # don't load gems more than once        next if @gems.has_key? gem.name                check = needs.dup        # rolls through the depends and inverts anything it finds        gem.dependencies.each do |dep|          # this will fail if a gem is depended more than once          if check.has_key? dep.name            check[dep.name] = !check[dep.name]          end        end                # now since excluded gems start as true, inverting them        # makes them false so we'll skip this gem if any excludes are found        if (check.select {|name,test| !test}).length == 0          # looks like no needs were set to false, so it's good          gem_dir = File.join(Gem.dir, "gems", "#{gem.name}-#{gem.version}")          require File.join(gem_dir, "lib", gem.name, "init.rb")          @gems[gem.name] = gem_dir        end      end      return nil    end    # Not necessary for you to call directly, but this is    # how GemPlugin::Base.inherited actually adds a     # plugin to a category.    def register(category, name, klass)      @plugins[category] ||= {}      @plugins[category][name.downcase] = klass    end        # Resolves the given name (should include /category/name) to    # find the plugin class and create an instance.  You can    # pass a second hash option that is then given to the Plugin     # to configure it.    def create(name, options = {})      last_slash = name.rindex("/")      category = name[0 ... last_slash]      plugin = name[last_slash .. -1]      map = @plugins[category]      if not map        raise "Plugin category #{category} does not exist"      elsif not map.has_key? plugin        raise "Plugin #{plugin} does not exist in category #{category}"      else        map[plugin].new(options)      end    end        # Simply says whether the given gem has been loaded yet or not.    def loaded?(gem_name)      @gems.has_key? gem_name    end    # GemPlugins can have a 'resources' directory which is packaged with them    # and can hold any data resources the plugin may need.  The main problem    # is that it's difficult to figure out where these resources are     # actually located on the file system.  The resource method tries to     # locate the real path for a given resource path.    #    # Let's say you have a 'resources/stylesheets/default.css' file in your    # gem distribution, then finding where this file really is involves:    #    #   Manager.instance.resource("mygem", "/stylesheets/default.css")    #    # You either get back the full path to the resource or you get a nil    # if it doesn't exist.    #    # If you request a path for a GemPlugin that hasn't been loaded yet    # then it will throw an PluginNotLoaded exception.  The gem may be    # present on your system in this case, but you just haven't loaded    # it with Manager.instance.load properly.    def resource(gem_name, path)      if not loaded? gem_name        raise PluginNotLoaded.new("Plugin #{gem_name} not loaded when getting resource #{path}")      end            file = File.join(@gems[gem_name], "resources", path)      if File.exist? file        return file      else        return nil      end    end        # While Manager.resource will find arbitrary resources, a special    # case is when you need to load a set of configuration defaults.    # GemPlugin normalizes this to be if you have a file "resources/defaults.yaml"    # then you'll be able to load them via Manager.config.    #    # How you use the method is you get the options the user wants set, pass    # them to Manager.instance.config, and what you get back is a new Hash    # with the user's settings overriding the defaults.    #    #   opts = Manager.instance.config "mygem", :age => 12, :max_load => .9    #    # In the above case, if defaults had {:age => 14} then it would be     # changed to 12.    #    # This loads the .yaml file on the fly every time, so doing it a     # whole lot is very stupid.  If you need to make frequent calls to    # this, call it once with no options (Manager.instance.config) then    # use the returned defaults directly from then on.    def config(gem_name, options={})      config_file = Manager.instance.resource(gem_name, "/defaults.yaml")      if config_file        begin          defaults = YAML.load_file(config_file)          return defaults.merge(options)        rescue          raise "Error loading config #{config_file} for gem #{gem_name}"        end      else        return options      end    end  end  # This base class for plugins really does nothing  # more than wire up the new class into the right category.  # It is not thread-safe yet but will be soon.  class Base        attr_reader :options    # See Mongrel::Plugin for an explanation.    def Base.inherited(klass)      name = "/" + klass.to_s.downcase      Manager.instance.register(@@category, name, klass)      @@category = nil    end        # See Mongrel::Plugin for an explanation.    def Base.category=(category)      @@category = category    end    def initialize(options = {})      @options = options    end  end    # This nifty function works with the GemPlugin::Base to give you  # the syntax:  #  #  class MyThing < GemPlugin::Plugin "/things"  #    ...  #  end  #  # What it does is temporarily sets the GemPlugin::Base.category, and then  # returns GemPlugin::Base.  Since the next immediate thing Ruby does is  # use this returned class to create the new class, GemPlugin::Base.inherited  # gets called.  GemPlugin::Base.inherited then uses the set category, class name,  # and class to register the plugin in the right way.  def GemPlugin::Plugin(c)    Base.category = c    Base  endend

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?