first commit
This commit is contained in:
commit
2895bb245f
|
@ -0,0 +1,48 @@
|
|||
method_source
|
||||
=============
|
||||
|
||||
(C) John Mair (banisterfiend) 2010
|
||||
|
||||
_retrieve the sourcecode for a method_
|
||||
|
||||
`method_source` is a utility to return a method's sourcecode as a
|
||||
Ruby string.
|
||||
|
||||
It is written in pure Ruby (no C).
|
||||
|
||||
`method_source` provides the `source` method to the `Method` and
|
||||
`UnboundMethod` classes.
|
||||
|
||||
* Install the [gem](https://rubygems.org/gems/method_source): `gem install method_source`
|
||||
* Read the [documentation](http://rdoc.info/github/banister/method_source/master/file/README.markdown)
|
||||
* See the [source code](http://github.com/banister/method_source)
|
||||
|
||||
example:
|
||||
---------
|
||||
|
||||
Set.instance_method(:merge).source.display
|
||||
# =>
|
||||
def merge(enum)
|
||||
if enum.instance_of?(self.class)
|
||||
@hash.update(enum.instance_variable_get(:@hash))
|
||||
else
|
||||
do_with_enum(enum) { |o| add(o) }
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
Limitations:
|
||||
------------
|
||||
|
||||
* Only works with Ruby 1.9+
|
||||
* Cannot return source for C methods.
|
||||
* Cannot return source for dynamically defined methods.
|
||||
|
||||
Possible Applications:
|
||||
----------------------
|
||||
|
||||
* Combine with [RubyParser](https://github.com/seattlerb/ruby_parser)
|
||||
for extra fun.
|
||||
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
dlext = Config::CONFIG['DLEXT']
|
||||
direc = File.dirname(__FILE__)
|
||||
|
||||
require 'rake/clean'
|
||||
require 'rake/gempackagetask'
|
||||
require "#{direc}/lib/method_source/version"
|
||||
|
||||
CLOBBER.include("**/*.#{dlext}", "**/*~", "**/*#*", "**/*.log", "**/*.o")
|
||||
CLEAN.include("ext/**/*.#{dlext}", "ext/**/*.log", "ext/**/*.o",
|
||||
"ext/**/*~", "ext/**/*#*", "ext/**/*.obj",
|
||||
"ext/**/*.def", "ext/**/*.pdb", "**/*_flymake*.*", "**/*_flymake")
|
||||
|
||||
def apply_spec_defaults(s)
|
||||
s.name = "method_source"
|
||||
s.summary = "retrieve the sourcecode for a method"
|
||||
s.version = MethodSource::VERSION
|
||||
s.date = Time.now.strftime '%Y-%m-%d'
|
||||
s.author = "John Mair (banisterfiend)"
|
||||
s.email = 'jrmair@gmail.com'
|
||||
s.description = s.summary
|
||||
s.require_path = 'lib'
|
||||
s.homepage = "http://banisterfiend.wordpress.com"
|
||||
s.has_rdoc = 'yard'
|
||||
s.files = Dir["ext/**/extconf.rb", "ext/**/*.h", "ext/**/*.c", "lib/**/*.rb",
|
||||
"test/*.rb", "CHANGELOG", "README.markdown", "Rakefile"]
|
||||
end
|
||||
|
||||
task :test do
|
||||
sh "bacon -k #{direc}/test/test.rb"
|
||||
end
|
||||
|
||||
namespace :ruby do
|
||||
spec = Gem::Specification.new do |s|
|
||||
apply_spec_defaults(s)
|
||||
s.platform = Gem::Platform::RUBY
|
||||
end
|
||||
|
||||
Rake::GemPackageTask.new(spec) do |pkg|
|
||||
pkg.need_zip = false
|
||||
pkg.need_tar = false
|
||||
end
|
||||
end
|
||||
|
||||
desc "build all platform gems at once"
|
||||
task :gems => [:rmgems, "ruby:gem"]
|
||||
|
||||
desc "remove all platform gems"
|
||||
task :rmgems => ["ruby:clobber_package"]
|
||||
|
||||
desc "build and push latest gems"
|
||||
task :pushgems => :gems do
|
||||
chdir("#{direc}/pkg") do
|
||||
Dir["*.gem"].each do |gemfile|
|
||||
sh "gem push #{gemfile}"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,94 @@
|
|||
# (C) John Mair (banisterfiend) 2010
|
||||
# MIT License
|
||||
|
||||
direc = File.dirname(__FILE__)
|
||||
|
||||
require 'stringio'
|
||||
require "#{direc}/method_source/version"
|
||||
|
||||
module MethodSource
|
||||
|
||||
# Helper method used to find end of method body
|
||||
# @param [String] code The string of Ruby code to check for
|
||||
# correctness
|
||||
# @return [Boolean]
|
||||
def self.valid_expression?(code)
|
||||
suppress_stderr do
|
||||
RubyVM::InstructionSequence.new(code)
|
||||
end
|
||||
rescue Exception
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
|
||||
# Helper method used to suppress stderr output by the
|
||||
# `RubyVM::InstructionSequence` method
|
||||
# @yield The block where stderr is suppressed
|
||||
def self.suppress_stderr
|
||||
real_stderr, $stderr = $stderr, StringIO.new
|
||||
yield
|
||||
ensure
|
||||
$stderr = real_stderr
|
||||
end
|
||||
|
||||
# Helper method responsible for opening source file and advancing to
|
||||
# the correct linenumber. Defined here to avoid polluting `Method`
|
||||
# class.
|
||||
# @param [Array] source_location The array returned by Method#source_location
|
||||
# @return [File] The opened source file
|
||||
def self.source_helper(source_location)
|
||||
return nil if !source_location.is_a?(Array)
|
||||
|
||||
file_name, line = source_location
|
||||
file = File.open(file_name)
|
||||
(line - 1).times { file.readline }
|
||||
file
|
||||
end
|
||||
|
||||
# This module is to be included by `Method` and `UnboundMethod` and
|
||||
# provides the `#source` functionality
|
||||
module MethodExtensions
|
||||
|
||||
# Return the sourcecode for the method as a string
|
||||
# (This functionality is only supported in Ruby 1.9 and above)
|
||||
# @return [String] The method sourcecode as a string
|
||||
# @example
|
||||
# Set.instance_method(:clear).source.display
|
||||
# =>
|
||||
# def clear
|
||||
# @hash.clear
|
||||
# self
|
||||
# end
|
||||
def source
|
||||
file = nil
|
||||
|
||||
if respond_to?(:source_location)
|
||||
file = MethodSource.source_helper(source_location)
|
||||
|
||||
raise "Cannot locate source for this method: #{name}" if !file
|
||||
else
|
||||
raise "Method#source not supported by this Ruby version (#{RUBY_VERSION})"
|
||||
end
|
||||
|
||||
code = ""
|
||||
loop do
|
||||
val = file.readline
|
||||
code += val
|
||||
|
||||
return code if MethodSource.valid_expression?(code)
|
||||
end
|
||||
|
||||
ensure
|
||||
file.close if file
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Method
|
||||
include MethodSource::MethodExtensions
|
||||
end
|
||||
|
||||
class UnboundMethod
|
||||
include MethodSource::MethodExtensions
|
||||
end
|
|
@ -0,0 +1,3 @@
|
|||
module MethodSource
|
||||
VERSION = "0.1.0"
|
||||
end
|
|
@ -0,0 +1,32 @@
|
|||
direc = File.dirname(__FILE__)
|
||||
|
||||
require 'bacon'
|
||||
require "#{direc}/../lib/method_source"
|
||||
|
||||
hello_source = "def hello; :hello; end\n"
|
||||
|
||||
def hello; :hello; end
|
||||
|
||||
describe MethodSource do
|
||||
|
||||
it 'should define methods on both Method and UnboundMethod' do
|
||||
Method.method_defined?(:source).should == true
|
||||
UnboundMethod.method_defined?(:source).should == true
|
||||
end
|
||||
|
||||
if RUBY_VERSION =~ /1.9/
|
||||
it 'should return source for method' do
|
||||
method(:hello).source.should == hello_source
|
||||
end
|
||||
|
||||
it 'should raise for C methods' do
|
||||
lambda { method(:puts).source }.should.raise RuntimeError
|
||||
end
|
||||
|
||||
else
|
||||
it 'should raise on #source' do
|
||||
lambda { method(:hello).source }.should.raise RuntimeError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue