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.
- 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
|
-
|