first commit

This commit is contained in:
John Mair 2010-12-17 02:58:01 +13:00
commit 2895bb245f
6 changed files with 235 additions and 0 deletions

1
.yardopts Normal file
View File

@ -0,0 +1 @@
-m markdown

48
README.markdown Normal file
View File

@ -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.

57
Rakefile Normal file
View File

@ -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

94
lib/method_source.rb Normal file
View File

@ -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

View File

@ -0,0 +1,3 @@
module MethodSource
VERSION = "0.1.0"
end

32
test/test.rb Normal file
View File

@ -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