summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Mair <jrmair@gmail.com>2010-12-17 02:58:01 +1300
committerJohn Mair <jrmair@gmail.com>2010-12-17 02:58:01 +1300
commit2895bb245fd216be96da6e4d6b9e361248934450 (patch)
treebd0a0480b25a3eb11f1aba0ef9fc2825a4a16d3b
downloadmethod_source-2895bb245fd216be96da6e4d6b9e361248934450.tar.gz
first commit
-rw-r--r--.yardopts1
-rw-r--r--README.markdown48
-rw-r--r--Rakefile57
-rw-r--r--lib/method_source.rb94
-rw-r--r--lib/method_source/version.rb3
-rw-r--r--test/test.rb32
6 files changed, 235 insertions, 0 deletions
diff --git a/.yardopts b/.yardopts
new file mode 100644
index 0000000..a4e7838
--- /dev/null
+++ b/.yardopts
@@ -0,0 +1 @@
+-m markdown
diff --git a/README.markdown b/README.markdown
new file mode 100644
index 0000000..0d73f38
--- /dev/null
+++ b/README.markdown
@@ -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.
+
+
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..4edeaac
--- /dev/null
+++ b/Rakefile
@@ -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
diff --git a/lib/method_source.rb b/lib/method_source.rb
new file mode 100644
index 0000000..bdb00ed
--- /dev/null
+++ b/lib/method_source.rb
@@ -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
diff --git a/lib/method_source/version.rb b/lib/method_source/version.rb
new file mode 100644
index 0000000..30c0683
--- /dev/null
+++ b/lib/method_source/version.rb
@@ -0,0 +1,3 @@
+module MethodSource
+ VERSION = "0.1.0"
+end
diff --git a/test/test.rb b/test/test.rb
new file mode 100644
index 0000000..8a4beaa
--- /dev/null
+++ b/test/test.rb
@@ -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
+