method_source 0.2.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
-