summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Mair <jrmair@gmail.com>2011-02-28 03:40:47 +1300
committerJohn Mair <jrmair@gmail.com>2011-02-28 03:40:47 +1300
commit52ef85631d136a4ff31c0f4c2587b19db3daf512 (patch)
treefcad597e22e22a576fd6ac74b878e89785012d67
parent8f6b36637263a79ae4ac96ec734b1851614a4e19 (diff)
downloadmethod_source-dev.tar.gz
version 0.3.0, some ruby 1.8 supportdev
-rw-r--r--README.markdown17
-rw-r--r--Rakefile4
-rw-r--r--lib/method_source.rb48
-rw-r--r--lib/method_source/source_location.rb56
-rw-r--r--lib/method_source/version.rb2
-rw-r--r--test/test.rb84
-rw-r--r--test/test_helper.rb8
7 files changed, 145 insertions, 74 deletions
diff --git a/README.markdown b/README.markdown
index bc42a82..bb1c83d 100644
--- a/README.markdown
+++ b/README.markdown
@@ -1,11 +1,11 @@
method_source
=============
-(C) John Mair (banisterfiend) 2010
+(C) John Mair (banisterfiend) 2011
_retrieve the sourcecode for a method_
-*NOTE:* This simply utilizes `Method#source_location` in Ruby 1.9; it
+*NOTE:* This simply utilizes `Method#source_location`; it
does not access the live AST.
`method_source` is a utility to return a method's sourcecode as a
@@ -15,6 +15,8 @@ Method comments can also be extracted using the `comment` method.
It is written in pure Ruby (no C).
+* Some Ruby 1.8 support now available.
+
`method_source` provides the `source` and `comment` methods to the `Method` and
`UnboundMethod` and `Proc` classes.
@@ -48,19 +50,14 @@ Example: display method comments
Limitations:
------------
-* Only works with Ruby 1.9+
+* Proc#source not available in Ruby 1.8
+* Occasional strange behaviour in Ruby 1.8
* 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.
-
-
Special Thanks
--------------
[Adam Sanderson](https://github.com/adamsanderson) for `comment` functionality.
+[Dmitry Elastic](https://github.com/dmitryelastic) for the brilliant Ruby 1.8 `source_location` hack.
diff --git a/Rakefile b/Rakefile
index 4edeaac..b24114d 100644
--- a/Rakefile
+++ b/Rakefile
@@ -19,10 +19,12 @@ def apply_spec_defaults(s)
s.email = 'jrmair@gmail.com'
s.description = s.summary
s.require_path = 'lib'
+ s.add_dependency("ruby_parser",">=2.0.5")
+ s.add_development_dependency("bacon",">=1.1.0")
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"]
+ "test/*.rb", "CHANGELOG", "README.markdown", "Rakefile", ".gemtest"]
end
task :test do
diff --git a/lib/method_source.rb b/lib/method_source.rb
index d6a6e29..43651b9 100644
--- a/lib/method_source.rb
+++ b/lib/method_source.rb
@@ -4,19 +4,41 @@
direc = File.dirname(__FILE__)
require "#{direc}/method_source/version"
-
-if RUBY_VERSION =~ /1.9/
- require 'ripper'
-end
+require "#{direc}/method_source/source_location"
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)
- !!Ripper::SexpBuilder.new(code).parse
+ if RUBY_VERSION =~ /1.9/
+ require 'ripper'
+
+ # Determine if a string of code is a valid Ruby expression.
+ # Ruby 1.9 uses Ripper, Ruby 1.8 uses RubyParser.
+ # @param [String] code The code to validate.
+ # @return [Boolean] Whether or not the code is a valid Ruby expression.
+ # @example
+ # valid_expression?("class Hello") #=> false
+ # valid_expression?("class Hello; end") #=> true
+ def self.valid_expression?(code)
+ !!Ripper::SexpBuilder.new(code).parse
+ end
+
+ else
+ require 'ruby_parser'
+
+ # Determine if a string of code is a valid Ruby expression.
+ # Ruby 1.9 uses Ripper, Ruby 1.8 uses RubyParser.
+ # @param [String] code The code to validate.
+ # @return [Boolean] Whether or not the code is a valid Ruby expression.
+ # @example
+ # valid_expression?("class Hello") #=> false
+ # valid_expression?("class Hello; end") #=> true
+ def self.valid_expression?(code)
+ RubyParser.new.parse(code)
+ rescue Racc::ParseError, SyntaxError
+ false
+ else
+ true
+ end
end
# Helper method responsible for extracting method body.
@@ -86,7 +108,7 @@ module MethodSource
raise "Cannot locate source for this method: #{name}" if !source
else
- raise "Method#source not supported by this Ruby version (#{RUBY_VERSION})"
+ raise "#{self.class}#source not supported by this Ruby version (#{RUBY_VERSION})"
end
source
@@ -105,7 +127,7 @@ module MethodSource
raise "Cannot locate source for this method: #{name}" if !comment
else
- raise "Method#comment not supported by this Ruby version (#{RUBY_VERSION})"
+ raise "#{self.class}#comment not supported by this Ruby version (#{RUBY_VERSION})"
end
comment
@@ -114,10 +136,12 @@ module MethodSource
end
class Method
+ include MethodSource::SourceLocation::MethodExtensions
include MethodSource::MethodExtensions
end
class UnboundMethod
+ include MethodSource::SourceLocation::UnboundMethodExtensions
include MethodSource::MethodExtensions
end
diff --git a/lib/method_source/source_location.rb b/lib/method_source/source_location.rb
new file mode 100644
index 0000000..1878b18
--- /dev/null
+++ b/lib/method_source/source_location.rb
@@ -0,0 +1,56 @@
+module MethodSource
+ module SourceLocation
+ module MethodExtensions
+ def trace_func(event, file, line, id, binding, classname)
+ return unless event == 'call'
+ set_trace_func nil
+
+ @file, @line = file, line
+ raise :found
+ end
+
+ # Return the source location of a method for Ruby 1.8.
+ # @return [Array] A two element array. First element is the
+ # file, second element is the line in the file where the
+ # method definition is found.
+ def source_location
+ if @file.nil?
+ args =[*(1..(arity<-1 ? -arity-1 : arity ))]
+
+ set_trace_func method(:trace_func).to_proc
+ call *args rescue nil
+ set_trace_func nil
+ @file = File.expand_path(@file) if @file && File.exist?(File.expand_path(@file))
+ end
+ return [@file, @line] if File.exist?(@file.to_s)
+ end
+ end
+
+ module UnboundMethodExtensions
+
+ # Return the source location of an instance method for Ruby 1.8.
+ # @return [Array] A two element array. First element is the
+ # file, second element is the line in the file where the
+ # method definition is found.
+ def source_location
+ klass = case owner
+ when Class
+ owner
+ when Module
+ Class.new.tap { |v| v.send(:include, owner) }
+ end
+
+ begin
+ klass.allocate.method(name).source_location
+ rescue TypeError
+
+ # Assume we are dealing with a Singleton Class:
+ # 1. Get the instance object
+ # 2. Forward the source_location lookup to the instance
+ instance ||= ObjectSpace.each_object(owner).first
+ instance.method(name).source_location
+ end
+ end
+ end
+ end
+end
diff --git a/lib/method_source/version.rb b/lib/method_source/version.rb
index 2ac241c..6a830a9 100644
--- a/lib/method_source/version.rb
+++ b/lib/method_source/version.rb
@@ -1,3 +1,3 @@
module MethodSource
- VERSION = "0.2.0"
+ VERSION = "0.3.0"
end
diff --git a/test/test.rb b/test/test.rb
index 48f3ed8..8b84538 100644
--- a/test/test.rb
+++ b/test/test.rb
@@ -21,28 +21,21 @@ describe MethodSource do
end
describe "Methods" do
- if RUBY_VERSION =~ /1.9/
- it 'should return source for method' do
- method(:hello).source.should == @hello_source
- end
-
- it 'should return a comment for method' do
- method(:hello).comment.should == @hello_comment
- end
-
- it 'should raise for C methods' do
- lambda { method(:puts).source }.should.raise RuntimeError
- end
+ it 'should return source for method' do
+ method(:hello).source.should == @hello_source
+ end
+
+ it 'should return a comment for method' do
+ method(:hello).comment.should == @hello_comment
+ end
- else
- it 'should raise on #source for 1.8' do
- lambda { method(:hello).source }.should.raise RuntimeError
- end
+ it 'should raise for C methods' do
+ lambda { method(:puts).source }.should.raise RuntimeError
end
end
- describe "Lambdas and Procs" do
- if RUBY_VERSION =~ /1.9/
+ if RUBY_VERSION =~ /1.9/
+ describe "Lambdas and Procs" do
it 'should return source for proc' do
MyProc.source.should == @proc_source
end
@@ -58,42 +51,35 @@ describe MethodSource do
it 'should return comment for lambda' do
MyLambda.comment.should == @lambda_comment
end
- else
- it 'should raise on #source for 1.8' do
- lambda { method(:hello).source }.should.raise RuntimeError
- end
end
end
+ describe "Comment tests" do
+ before do
+ @comment1 = "# a\n# b\n"
+ @comment2 = "# a\n# b\n"
+ @comment3 = "# a\n#\n# b\n"
+ @comment4 = "# a\n# b\n"
+ @comment5 = "# a\n# b\n# c\n# d\n"
+ end
- if RUBY_VERSION =~ /1.9/
- describe "Comment tests" do
- before do
- @comment1 = "# a\n# b\n"
- @comment2 = "# a\n# b\n"
- @comment3 = "# a\n#\n# b\n"
- @comment4 = "# a\n# b\n"
- @comment5 = "# a\n# b\n# c\n# d\n"
- end
-
- it "should correctly extract multi-line comments" do
- method(:comment_test1).comment.should == @comment1
- end
-
- it "should correctly strip leading whitespace before comments" do
- method(:comment_test2).comment.should == @comment2
- end
+ it "should correctly extract multi-line comments" do
+ method(:comment_test1).comment.should == @comment1
+ end
- it "should keep empty comment lines" do
- method(:comment_test3).comment.should == @comment3
- end
-
- it "should ignore blank lines between comments" do
- method(:comment_test4).comment.should == @comment4
- end
+ it "should correctly strip leading whitespace before comments" do
+ method(:comment_test2).comment.should == @comment2
+ end
- it "should align all comments to same indent level" do
- method(:comment_test5).comment.should == @comment5
- end
+ it "should keep empty comment lines" do
+ method(:comment_test3).comment.should == @comment3
end
+
+ it "should ignore blank lines between comments" do
+ method(:comment_test4).comment.should == @comment4
+ end
+
+ it "should align all comments to same indent level" do
+ method(:comment_test5).comment.should == @comment5
+ end
end
end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 78ef513..bb28e29 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -1,4 +1,10 @@
- # A comment for hello
+class String
+ def clear
+ replace("")
+ end
+end
+
+# A comment for hello
# It spans two lines and is indented by 2 spaces
def hello; :hello; end