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.
- data/README.markdown +7 -10
- data/Rakefile +3 -1
- data/lib/method_source.rb +59 -39
- data/lib/method_source/source_location.rb +60 -0
- data/lib/method_source/version.rb +1 -1
- data/test/test.rb +49 -48
- data/test/test_helper.rb +14 -1
- metadata +44 -38
data/README.markdown
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
method_source
|
2
2
|
=============
|
3
3
|
|
4
|
-
(C) John Mair (banisterfiend)
|
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
|
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
|
-
*
|
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
|
data/lib/method_source.rb
CHANGED
@@ -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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
32
|
-
|
52
|
+
File.open(file_name) do |file|
|
53
|
+
(line - 1).times { file.readline }
|
33
54
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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 "
|
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 "
|
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
|
data/test/test.rb
CHANGED
@@ -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
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
45
|
-
|
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
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
95
|
-
|
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
|
data/test/test_helper.rb
CHANGED
@@ -1,4 +1,17 @@
|
|
1
|
-
|
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
|
-
|
5
|
-
|
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
|
-
|
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:
|
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
|
-
|
51
|
-
|
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
|
-
|
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.
|
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
|
-
|