module Boson # Uses method decorators to scrape, process and hand off method attributes as # data to Library objects. # # === Method Decorators # Method decorators refer to methods placed before a command's method in a # library: # class SomeRunner < Boson::Runner # options :verbose=>:boolean # option :count, :numeric # # Something descriptive perhaps # def some_method(opts) # # ... # end # end # # Method decorators serve as configuration for a method's command. All # decorators should only be called once per method except for option. # Available method decorators: # * config: Hash to define any command attributes (see Command.new). # * desc: String to define a command's description for a command. Defaults to # first commented line above a method. # * options: Hash to define an OptionParser object for a command's options. # * option: Option name and value to be merged in with options. See # OptionParser for what an option value can be. class Inspector class << self; attr_reader :enabled; end # Enable scraping by overridding method_added to snoop on a library while # it's loading its methods. def self.enable(options = {}) method_inspector_meth = options[:all_classes] ? :new_method_added : :safe_new_method_added klass = options[:module] || ::Module @enabled = true unless options[:module] body = MethodInspector::ALL_METHODS.map {|e| %[def #{e}(*args) Boson::MethodInspector.#{e}(self, *args) end] }.join("\n") + %[ def new_method_added(method) Boson::MethodInspector.#{method_inspector_meth}(self, method) end alias_method :_old_method_added, :method_added alias_method :method_added, :new_method_added ] klass.module_eval body end # Disable scraping method data. def self.disable ::Module.module_eval %[ Boson::MethodInspector::ALL_METHODS.each {|e| remove_method e } alias_method :method_added, :_old_method_added ] @enabled = false end # Adds method attributes to the library's commands def self.add_method_data_to_library(library) new(library).add_data end def initialize(library) @commands_hash = library.commands_hash @library_file = library.library_file MethodInspector.instance.current_module = library.module @store = MethodInspector.instance.store end module API # Adds scraped data from all inspectors def add_data add_method_scraped_data end end include API private def add_method_scraped_data (MethodInspector::METHODS + [:args]).each do |key| (@store[key] || []).each do |cmd, val| @commands_hash[cmd] ||= {} add_valid_data_to_config(key, val, cmd) end end end def add_valid_data_to_config(key, value, cmd) if valid_attr_value?(key, value) add_scraped_data_to_config(key, value, cmd) else if Boson.debug warn "DEBUG: Command '#{cmd}' has #{key.inspect} attribute with " + "invalid value '#{value.inspect}'" end end end def add_scraped_data_to_config(key, value, cmd) if value.is_a?(Hash) if key == :config @commands_hash[cmd] = Util.recursive_hash_merge value, @commands_hash[cmd] else @commands_hash[cmd][key] = Util.recursive_hash_merge value, @commands_hash[cmd][key] || {} end else @commands_hash[cmd][key] ||= value end end def valid_attr_value?(key, value) return true if (klass = MethodInspector::METHOD_CLASSES[key]).nil? value.is_a?(klass) || value.nil? end end end