# (C) John Mair (banisterfiend) 2011 # MIT License direc = File.dirname(__FILE__) require "#{direc}/method_source/version" require "#{direc}/method_source/source_location" require "#{direc}/method_source/code_helpers" module MethodSource extend MethodSource::CodeHelpers # An Exception to mark errors that were raised trying to find the source from # a given source_location. # class SourceNotFoundError < StandardError; end # Helper method responsible for extracting method body. # Defined here to avoid polluting `Method` class. # @param [Array] source_location The array returned by Method#source_location # @param [String] method_name # @return [String] The method body def self.source_helper(source_location, name=nil) raise SourceNotFoundError, "Could not locate source for #{name}!" unless source_location file, line = *source_location expression_at(lines_for(file), line) rescue SyntaxError => e raise SourceNotFoundError, "Could not parse source for #{name}: #{e.message}" end # Helper method responsible for opening source file and buffering up # the comments for a specified method. Defined here to avoid polluting # `Method` class. # @param [Array] source_location The array returned by Method#source_location # @param [String] method_name # @return [String] The comments up to the point of the method. def self.comment_helper(source_location, name=nil) raise SourceNotFoundError, "Could not locate source for #{name}!" unless source_location file, line = *source_location comment_describing(lines_for(file), line) end # Load a memoized copy of the lines in a file. # # @param [String] file_name # @param [String] method_name # @return [Array] the contents of the file # @raise [SourceNotFoundError] def self.lines_for(file_name, name=nil) @lines_for_file ||= {} @lines_for_file[file_name] ||= File.readlines(file_name) rescue Errno::ENOENT => e raise SourceNotFoundError, "Could not load source for #{name}: #{e.message}" end # Clear cache. def self.clear_cache @lines_for_file = {} end # @deprecated — use MethodSource::CodeHelpers#complete_expression? def self.valid_expression?(str) complete_expression?(str) rescue SyntaxError false end # @deprecated — use MethodSource::CodeHelpers#expression_at def self.extract_code(source_location) source_helper(source_location) end # This module is to be included by `Method` and `UnboundMethod` and # provides the `#source` functionality module MethodExtensions # We use the included hook to patch Method#source on rubinius. # We need to use the included hook as Rubinius defines a `source` # on Method so including a module will have no effect (as it's # higher up the MRO). # @param [Class] klass The class that includes the module. def self.included(klass) if klass.method_defined?(:source) && Object.const_defined?(:RUBY_ENGINE) && RUBY_ENGINE =~ /rbx/ klass.class_eval do orig_source = instance_method(:source) define_method(:source) do begin super rescue orig_source.bind(self).call end end end end end # Return the sourcecode for the method as a string # @return [String] The method sourcecode as a string # @raise SourceNotFoundException # # @example # Set.instance_method(:clear).source.display # => # def clear # @hash.clear # self # end def source MethodSource.source_helper(source_location, defined?(name) ? name : inspect) end # Return the comments associated with the method as a string. # @return [String] The method's comments as a string # @raise SourceNotFoundException # # @example # Set.instance_method(:clear).comment.display # => # # Removes all elements and returns self. def comment MethodSource.comment_helper(source_location, defined?(name) ? name : inspect) end # Return the comments associated with the method class/module. # @return [String] The method's comments as a string # @raise SourceNotFoundException # # @example # MethodSource::MethodExtensions.method(:included).module_comment # => # # This module is to be included by `Method` and `UnboundMethod` and # # provides the `#source` functionality def class_comment if self.respond_to?(:receiver) class_inst_or_module = self.receiver elsif self.respond_to?(:owner) class_inst_or_module = self.owner else return comment end if class_inst_or_module.respond_to?(:name) const_name = class_inst_or_module.name else const_name = class_inst_or_module.class.name class_inst_or_module = class_inst_or_module.class end location = class_inst_or_module.const_source_location(const_name) MethodSource.comment_helper(location, defined?(name) ? name : inspect) end alias module_comment class_comment end end class Method include MethodSource::SourceLocation::MethodExtensions include MethodSource::MethodExtensions end class UnboundMethod include MethodSource::SourceLocation::UnboundMethodExtensions include MethodSource::MethodExtensions end class Proc include MethodSource::SourceLocation::ProcExtensions include MethodSource::MethodExtensions end