method_source 0.2.0 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,11 @@
1
1
  method_source
2
2
  =============
3
3
 
4
- (C) John Mair (banisterfiend) 2010
4
+ (C) John Mair (banisterfiend) 2011
5
5
 
6
6
  _retrieve the sourcecode for a method_
7
7
 
8
- *NOTE:* This simply utilizes `Method#source_location` in Ruby 1.9; it
8
+ *NOTE:* This simply utilizes `Method#source_location`; it
9
9
  does not access the live AST.
10
10
 
11
11
  `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.
15
15
 
16
16
  It is written in pure Ruby (no C).
17
17
 
18
+ * Some Ruby 1.8 support now available.
19
+
18
20
  `method_source` provides the `source` and `comment` methods to the `Method` and
19
21
  `UnboundMethod` and `Proc` classes.
20
22
 
@@ -48,19 +50,14 @@ Example: display method comments
48
50
  Limitations:
49
51
  ------------
50
52
 
51
- * Only works with Ruby 1.9+
53
+ * Proc#source not available in Ruby 1.8
54
+ * Occasional strange behaviour in Ruby 1.8
52
55
  * Cannot return source for C methods.
53
56
  * Cannot return source for dynamically defined methods.
54
57
 
55
- Possible Applications:
56
- ----------------------
57
-
58
- * Combine with [RubyParser](https://github.com/seattlerb/ruby_parser)
59
- for extra fun.
60
-
61
-
62
58
  Special Thanks
63
59
  --------------
64
60
 
65
61
  [Adam Sanderson](https://github.com/adamsanderson) for `comment` functionality.
66
62
 
63
+ [Dmitry Elastic](https://github.com/dmitryelastic) for the brilliant Ruby 1.8 `source_location` hack.
data/Rakefile CHANGED
@@ -19,10 +19,12 @@ def apply_spec_defaults(s)
19
19
  s.email = 'jrmair@gmail.com'
20
20
  s.description = s.summary
21
21
  s.require_path = 'lib'
22
+ s.add_dependency("ruby_parser",">=2.0.5")
23
+ s.add_development_dependency("bacon",">=1.1.0")
22
24
  s.homepage = "http://banisterfiend.wordpress.com"
23
25
  s.has_rdoc = 'yard'
24
26
  s.files = Dir["ext/**/extconf.rb", "ext/**/*.h", "ext/**/*.c", "lib/**/*.rb",
25
- "test/*.rb", "CHANGELOG", "README.markdown", "Rakefile"]
27
+ "test/*.rb", "CHANGELOG", "README.markdown", "Rakefile", ".gemtest"]
26
28
  end
27
29
 
28
30
  task :test do
@@ -3,21 +3,42 @@
3
3
 
4
4
  direc = File.dirname(__FILE__)
5
5
 
6
- require 'stringio'
7
6
  require "#{direc}/method_source/version"
8
-
9
- if RUBY_VERSION =~ /1.9/
10
- require 'ripper'
11
- end
7
+ require "#{direc}/method_source/source_location"
12
8
 
13
9
  module MethodSource
14
10
 
15
- # Helper method used to find end of method body
16
- # @param [String] code The string of Ruby code to check for
17
- # correctness
18
- # @return [Boolean]
19
- def self.valid_expression?(code)
20
- !!Ripper::SexpBuilder.new(code).parse
11
+ if RUBY_VERSION =~ /1.9/
12
+ require 'ripper'
13
+
14
+ # Determine if a string of code is a valid Ruby expression.
15
+ # Ruby 1.9 uses Ripper, Ruby 1.8 uses RubyParser.
16
+ # @param [String] code The code to validate.
17
+ # @return [Boolean] Whether or not the code is a valid Ruby expression.
18
+ # @example
19
+ # valid_expression?("class Hello") #=> false
20
+ # valid_expression?("class Hello; end") #=> true
21
+ def self.valid_expression?(code)
22
+ !!Ripper::SexpBuilder.new(code).parse
23
+ end
24
+
25
+ else
26
+ require 'ruby_parser'
27
+
28
+ # Determine if a string of code is a valid Ruby expression.
29
+ # Ruby 1.9 uses Ripper, Ruby 1.8 uses RubyParser.
30
+ # @param [String] code The code to validate.
31
+ # @return [Boolean] Whether or not the code is a valid Ruby expression.
32
+ # @example
33
+ # valid_expression?("class Hello") #=> false
34
+ # valid_expression?("class Hello; end") #=> true
35
+ def self.valid_expression?(code)
36
+ RubyParser.new.parse(code)
37
+ rescue Racc::ParseError, SyntaxError
38
+ false
39
+ else
40
+ true
41
+ end
21
42
  end
22
43
 
23
44
  # Helper method responsible for extracting method body.
@@ -28,19 +49,17 @@ module MethodSource
28
49
  return nil if !source_location.is_a?(Array)
29
50
 
30
51
  file_name, line = source_location
31
- file = File.open(file_name)
32
- (line - 1).times { file.readline }
52
+ File.open(file_name) do |file|
53
+ (line - 1).times { file.readline }
33
54
 
34
- code = ""
35
- loop do
36
- val = file.readline
37
- code << val
38
-
39
- return code if MethodSource.valid_expression?(code)
40
- end
41
-
42
- ensure
43
- file.close if file
55
+ code = ""
56
+ loop do
57
+ val = file.readline
58
+ code << val
59
+
60
+ return code if valid_expression?(code)
61
+ end
62
+ end
44
63
  end
45
64
 
46
65
  # Helper method responsible for opening source file and buffering up
@@ -52,22 +71,21 @@ module MethodSource
52
71
  return nil if !source_location.is_a?(Array)
53
72
 
54
73
  file_name, line = source_location
55
- file = File.open(file_name)
56
- buffer = ""
57
- (line - 1).times do
58
- line = file.readline
59
- # Add any line that is a valid ruby comment,
60
- # but clear as soon as we hit a non comment line.
61
- if (line =~ /^\s*#/) || (line =~ /^\s*$/)
62
- buffer << line.lstrip
63
- else
64
- buffer.clear
74
+ File.open(file_name) do |file|
75
+ buffer = ""
76
+ (line - 1).times do
77
+ line = file.readline
78
+ # Add any line that is a valid ruby comment,
79
+ # but clear as soon as we hit a non comment line.
80
+ if (line =~ /^\s*#/) || (line =~ /^\s*$/)
81
+ buffer << line.lstrip
82
+ else
83
+ buffer.clear
84
+ end
65
85
  end
86
+
87
+ buffer
66
88
  end
67
-
68
- buffer
69
- ensure
70
- file.close if file
71
89
  end
72
90
 
73
91
  # This module is to be included by `Method` and `UnboundMethod` and
@@ -90,7 +108,7 @@ module MethodSource
90
108
 
91
109
  raise "Cannot locate source for this method: #{name}" if !source
92
110
  else
93
- raise "Method#source not supported by this Ruby version (#{RUBY_VERSION})"
111
+ raise "#{self.class}#source not supported by this Ruby version (#{RUBY_VERSION})"
94
112
  end
95
113
 
96
114
  source
@@ -109,7 +127,7 @@ module MethodSource
109
127
 
110
128
  raise "Cannot locate source for this method: #{name}" if !comment
111
129
  else
112
- raise "Method#comment not supported by this Ruby version (#{RUBY_VERSION})"
130
+ raise "#{self.class}#comment not supported by this Ruby version (#{RUBY_VERSION})"
113
131
  end
114
132
 
115
133
  comment
@@ -118,10 +136,12 @@ module MethodSource
118
136
  end
119
137
 
120
138
  class Method
139
+ include MethodSource::SourceLocation::MethodExtensions
121
140
  include MethodSource::MethodExtensions
122
141
  end
123
142
 
124
143
  class UnboundMethod
144
+ include MethodSource::SourceLocation::UnboundMethodExtensions
125
145
  include MethodSource::MethodExtensions
126
146
  end
127
147
 
@@ -0,0 +1,60 @@
1
+ module MethodSource
2
+ module SourceLocation
3
+ module MethodExtensions
4
+
5
+ def trace_func(event, file, line, id, binding, classname)
6
+ return unless event == 'call'
7
+ set_trace_func nil
8
+
9
+ @file, @line = file, line
10
+ raise :found
11
+ end
12
+
13
+ private :trace_func
14
+
15
+ # Return the source location of a method for Ruby 1.8.
16
+ # @return [Array] A two element array. First element is the
17
+ # file, second element is the line in the file where the
18
+ # method definition is found.
19
+ def source_location
20
+ if @file.nil?
21
+ args =[*(1..(arity<-1 ? -arity-1 : arity ))]
22
+
23
+ set_trace_func method(:trace_func).to_proc
24
+ call *args rescue nil
25
+ set_trace_func nil
26
+ @file = File.expand_path(@file) if @file && File.exist?(File.expand_path(@file))
27
+ end
28
+ return [@file, @line] if File.exist?(@file.to_s)
29
+ end
30
+ end
31
+
32
+ module UnboundMethodExtensions
33
+
34
+ # Return the source location of an instance method for Ruby 1.8.
35
+ # @return [Array] A two element array. First element is the
36
+ # file, second element is the line in the file where the
37
+ # method definition is found.
38
+ def source_location
39
+ klass = case owner
40
+ when Class
41
+ owner
42
+ when Module
43
+ method_owner = owner
44
+ Class.new { include(method_owner) }
45
+ end
46
+
47
+ begin
48
+ klass.allocate.method(name).source_location
49
+ rescue TypeError
50
+
51
+ # Assume we are dealing with a Singleton Class:
52
+ # 1. Get the instance object
53
+ # 2. Forward the source_location lookup to the instance
54
+ instance ||= ObjectSpace.each_object(owner).first
55
+ instance.method(name).source_location
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -1,3 +1,3 @@
1
1
  module MethodSource
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.2"
3
3
  end
@@ -7,6 +7,8 @@ require "#{direc}/test_helper"
7
7
  describe MethodSource do
8
8
 
9
9
  before do
10
+ @hello_module_source = " def hello; :hello_module; end\n"
11
+ @hello_singleton_source = "def $o.hello; :hello_singleton; end\n"
10
12
  @hello_source = "def hello; :hello; end\n"
11
13
  @hello_comment = "# A comment for hello\n# It spans two lines and is indented by 2 spaces\n"
12
14
  @lambda_comment = "# This is a comment for MyLambda\n"
@@ -21,28 +23,34 @@ describe MethodSource do
21
23
  end
22
24
 
23
25
  describe "Methods" do
24
- if RUBY_VERSION =~ /1.9/
25
- it 'should return source for method' do
26
- method(:hello).source.should == @hello_source
27
- end
28
-
29
- it 'should return a comment for method' do
30
- method(:hello).comment.should == @hello_comment
31
- end
26
+ it 'should return source for method' do
27
+ method(:hello).source.should == @hello_source
28
+ end
32
29
 
33
- it 'should raise for C methods' do
34
- lambda { method(:puts).source }.should.raise RuntimeError
35
- end
30
+ it 'should return source for a method defined in a module' do
31
+ M.instance_method(:hello).source.should == @hello_module_source
32
+ end
36
33
 
37
- else
38
- it 'should raise on #source for 1.8' do
39
- lambda { method(:hello).source }.should.raise RuntimeError
40
- end
34
+ it 'should return source for a singleton method as an instance method' do
35
+ class << $o; self; end.instance_method(:hello).source.should == @hello_singleton_source
36
+ end
37
+
38
+ it 'should return source for a singleton method' do
39
+ $o.method(:hello).source.should == @hello_singleton_source
40
+ end
41
+
42
+
43
+ it 'should return a comment for method' do
44
+ method(:hello).comment.should == @hello_comment
45
+ end
46
+
47
+ it 'should raise for C methods' do
48
+ lambda { method(:puts).source }.should.raise RuntimeError
41
49
  end
42
50
  end
43
51
 
44
- describe "Lambdas and Procs" do
45
- if RUBY_VERSION =~ /1.9/
52
+ if RUBY_VERSION =~ /1.9/
53
+ describe "Lambdas and Procs" do
46
54
  it 'should return source for proc' do
47
55
  MyProc.source.should == @proc_source
48
56
  end
@@ -58,42 +66,35 @@ describe MethodSource do
58
66
  it 'should return comment for lambda' do
59
67
  MyLambda.comment.should == @lambda_comment
60
68
  end
61
- else
62
- it 'should raise on #source for 1.8' do
63
- lambda { method(:hello).source }.should.raise RuntimeError
64
- end
65
69
  end
66
70
  end
71
+ describe "Comment tests" do
72
+ before do
73
+ @comment1 = "# a\n# b\n"
74
+ @comment2 = "# a\n# b\n"
75
+ @comment3 = "# a\n#\n# b\n"
76
+ @comment4 = "# a\n# b\n"
77
+ @comment5 = "# a\n# b\n# c\n# d\n"
78
+ end
67
79
 
68
- if RUBY_VERSION =~ /1.9/
69
- describe "Comment tests" do
70
- before do
71
- @comment1 = "# a\n# b\n"
72
- @comment2 = "# a\n# b\n"
73
- @comment3 = "# a\n#\n# b\n"
74
- @comment4 = "# a\n# b\n"
75
- @comment5 = "# a\n# b\n# c\n# d\n"
76
- end
77
-
78
- it "should correctly extract multi-line comments" do
79
- method(:comment_test1).comment.should == @comment1
80
- end
81
-
82
- it "should correctly strip leading whitespace before comments" do
83
- method(:comment_test2).comment.should == @comment2
84
- end
80
+ it "should correctly extract multi-line comments" do
81
+ method(:comment_test1).comment.should == @comment1
82
+ end
85
83
 
86
- it "should keep empty comment lines" do
87
- method(:comment_test3).comment.should == @comment3
88
- end
89
-
90
- it "should ignore blank lines between comments" do
91
- method(:comment_test4).comment.should == @comment4
92
- end
84
+ it "should correctly strip leading whitespace before comments" do
85
+ method(:comment_test2).comment.should == @comment2
86
+ end
93
87
 
94
- it "should align all comments to same indent level" do
95
- method(:comment_test5).comment.should == @comment5
96
- end
88
+ it "should keep empty comment lines" do
89
+ method(:comment_test3).comment.should == @comment3
97
90
  end
91
+
92
+ it "should ignore blank lines between comments" do
93
+ method(:comment_test4).comment.should == @comment4
94
+ end
95
+
96
+ it "should align all comments to same indent level" do
97
+ method(:comment_test5).comment.should == @comment5
98
+ end
98
99
  end
99
100
  end
@@ -1,4 +1,17 @@
1
- # A comment for hello
1
+ class String
2
+ def clear
3
+ replace("")
4
+ end
5
+ end
6
+
7
+ module M
8
+ def hello; :hello_module; end
9
+ end
10
+
11
+ $o = Object.new
12
+ def $o.hello; :hello_singleton; end
13
+
14
+ # A comment for hello
2
15
 
3
16
  # It spans two lines and is indented by 2 spaces
4
17
  def hello; :hello; end
metadata CHANGED
@@ -1,69 +1,75 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: method_source
3
- version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 2
8
- - 0
9
- version: 0.2.0
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.2
5
+ prerelease:
10
6
  platform: ruby
11
- authors:
7
+ authors:
12
8
  - John Mair (banisterfiend)
13
9
  autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
-
17
- date: 2010-12-18 00:00:00 +13:00
12
+ date: 2011-02-28 00:00:00.000000000 +13:00
18
13
  default_executable:
19
- dependencies: []
20
-
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: ruby_parser
17
+ requirement: &17134788 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: 2.0.5
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *17134788
26
+ - !ruby/object:Gem::Dependency
27
+ name: bacon
28
+ requirement: &17134464 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 1.1.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *17134464
21
37
  description: retrieve the sourcecode for a method
22
38
  email: jrmair@gmail.com
23
39
  executables: []
24
-
25
40
  extensions: []
26
-
27
41
  extra_rdoc_files: []
28
-
29
- files:
42
+ files:
43
+ - lib/method_source/source_location.rb
30
44
  - lib/method_source/version.rb
31
45
  - lib/method_source.rb
32
46
  - test/test.rb
33
47
  - test/test_helper.rb
34
48
  - README.markdown
35
49
  - Rakefile
36
- has_rdoc: yard
50
+ has_rdoc: true
37
51
  homepage: http://banisterfiend.wordpress.com
38
52
  licenses: []
39
-
40
53
  post_install_message:
41
54
  rdoc_options: []
42
-
43
- require_paths:
55
+ require_paths:
44
56
  - lib
45
- required_ruby_version: !ruby/object:Gem::Requirement
57
+ required_ruby_version: !ruby/object:Gem::Requirement
46
58
  none: false
47
- requirements:
48
- - - ">="
49
- - !ruby/object:Gem::Version
50
- segments:
51
- - 0
52
- version: "0"
53
- required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
64
  none: false
55
- requirements:
56
- - - ">="
57
- - !ruby/object:Gem::Version
58
- segments:
59
- - 0
60
- version: "0"
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
61
69
  requirements: []
62
-
63
70
  rubyforge_project:
64
- rubygems_version: 1.3.7
71
+ rubygems_version: 1.5.2
65
72
  signing_key:
66
73
  specification_version: 3
67
74
  summary: retrieve the sourcecode for a method
68
75
  test_files: []
69
-