method_source 0.7.1 → 0.8.pre.1

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/Rakefile CHANGED
@@ -41,6 +41,9 @@ end
41
41
  desc "Set up and run tests"
42
42
  task :default => [:test]
43
43
 
44
+ desc "Build the gemspec file"
45
+ task :gemspec => "ruby:gemspec"
46
+
44
47
  namespace :ruby do
45
48
  spec = Gem::Specification.new do |s|
46
49
  apply_spec_defaults(s)
data/lib/method_source.rb CHANGED
@@ -5,74 +5,66 @@ direc = File.dirname(__FILE__)
5
5
 
6
6
  require "#{direc}/method_source/version"
7
7
  require "#{direc}/method_source/source_location"
8
+ require "#{direc}/method_source/code_helpers"
8
9
 
9
10
  module MethodSource
10
- # Determine if a string of code is a valid Ruby expression.
11
- # @param [String] code The code to validate.
12
- # @return [Boolean] Whether or not the code is a valid Ruby expression.
13
- # @example
14
- # valid_expression?("class Hello") #=> false
15
- # valid_expression?("class Hello; end") #=> true
16
- def self.valid_expression?(str)
17
- if defined?(Rubinius::Melbourne19) && RUBY_VERSION =~ /^1\.9/
18
- Rubinius::Melbourne19.parse_string(str)
19
- elsif defined?(Rubinius::Melbourne)
20
- Rubinius::Melbourne.parse_string(str)
21
- else
22
- catch(:valid) {
23
- eval("BEGIN{throw :valid}\n#{str}")
24
- }
25
- end
26
- true
27
- rescue SyntaxError
28
- false
29
- end
11
+ extend MethodSource::CodeHelpers
12
+
13
+ # An Exception to mark errors that were raised trying to find the source from
14
+ # a given source_location.
15
+ #
16
+ class SourceNotFoundError < StandardError; end
30
17
 
31
18
  # Helper method responsible for extracting method body.
32
19
  # Defined here to avoid polluting `Method` class.
33
20
  # @param [Array] source_location The array returned by Method#source_location
34
- # @return [File] The opened source file
35
- def self.source_helper(source_location)
36
- return nil if !source_location.is_a?(Array)
37
-
38
- file_name, line = source_location
39
- File.open(file_name) do |file|
40
- (line - 1).times { file.readline }
41
-
42
- code = ""
43
- loop do
44
- val = file.readline
45
- code << val
46
-
47
- return code if valid_expression?(code)
48
- end
49
- end
21
+ # @param [String] method_name
22
+ # @return [String] The method body
23
+ def self.source_helper(source_location, name=nil)
24
+ raise SourceNotFoundError, "Could not locate source for #{name}!" unless source_location
25
+ file, line = *source_location
26
+
27
+ expression_at(lines_for(file), line)
28
+ rescue SyntaxError => e
29
+ raise SourceNotFoundError, "Could not parse source for #{name}: #{e.message}"
50
30
  end
51
31
 
52
32
  # Helper method responsible for opening source file and buffering up
53
33
  # the comments for a specified method. Defined here to avoid polluting
54
34
  # `Method` class.
55
35
  # @param [Array] source_location The array returned by Method#source_location
36
+ # @param [String] method_name
56
37
  # @return [String] The comments up to the point of the method.
57
- def self.comment_helper(source_location)
58
- return nil if !source_location.is_a?(Array)
59
-
60
- file_name, line = source_location
61
- File.open(file_name) do |file|
62
- buffer = ""
63
- (line - 1).times do
64
- line = file.readline
65
- # Add any line that is a valid ruby comment,
66
- # but clear as soon as we hit a non comment line.
67
- if (line =~ /^\s*#/) || (line =~ /^\s*$/)
68
- buffer << line.lstrip
69
- else
70
- buffer.replace("")
71
- end
72
- end
38
+ def self.comment_helper(source_location, name=nil)
39
+ raise SourceNotFoundError, "Could not locate source for #{name}!" unless source_location
40
+ file, line = *source_location
73
41
 
74
- buffer
75
- end
42
+ comment_describing(lines_for(file), line)
43
+ end
44
+
45
+ # Load a memoized copy of the lines in a file.
46
+ #
47
+ # @param [String] file_name
48
+ # @param [String] method_name
49
+ # @return [Array<String>] the contents of the file
50
+ # @raise [SourceNotFoundError]
51
+ def self.lines_for(file_name, name=nil)
52
+ @lines_for_file ||= {}
53
+ @lines_for_file[file_name] ||= File.readlines(file_name)
54
+ rescue Errno::ENOENT => e
55
+ raise SourceNotFoundError, "Could not load source for #{name}: #{e.message}"
56
+ end
57
+
58
+ # @deprecated — use MethodSource::CodeHelpers#complete_expression?
59
+ def self.valid_expression?(str)
60
+ complete_expression?(str)
61
+ rescue SyntaxError
62
+ false
63
+ end
64
+
65
+ # @deprecated — use MethodSource::CodeHelpers#expression_at
66
+ def self.extract_code(source_location)
67
+ source_helper(source_location)
76
68
  end
77
69
 
78
70
  # This module is to be included by `Method` and `UnboundMethod` and
@@ -104,8 +96,9 @@ module MethodSource
104
96
  end
105
97
 
106
98
  # Return the sourcecode for the method as a string
107
- # (This functionality is only supported in Ruby 1.9 and above)
108
99
  # @return [String] The method sourcecode as a string
100
+ # @raise SourceNotFoundException
101
+ #
109
102
  # @example
110
103
  # Set.instance_method(:clear).source.display
111
104
  # =>
@@ -114,34 +107,19 @@ module MethodSource
114
107
  # self
115
108
  # end
116
109
  def source
117
- if respond_to?(:source_location)
118
- source = MethodSource.source_helper(source_location)
119
-
120
- raise "Cannot locate source for this method: #{name}" if !source
121
- else
122
- raise "#{self.class}#source not supported by this Ruby version (#{RUBY_VERSION})"
123
- end
124
-
125
- source
110
+ MethodSource.source_helper(source_location, name)
126
111
  end
127
112
 
128
113
  # Return the comments associated with the method as a string.
129
- # (This functionality is only supported in Ruby 1.9 and above)
130
114
  # @return [String] The method's comments as a string
115
+ # @raise SourceNotFoundException
116
+ #
131
117
  # @example
132
118
  # Set.instance_method(:clear).comment.display
133
119
  # =>
134
120
  # # Removes all elements and returns self.
135
121
  def comment
136
- if respond_to?(:source_location)
137
- comment = MethodSource.comment_helper(source_location)
138
-
139
- raise "Cannot locate source for this method: #{name}" if !comment
140
- else
141
- raise "#{self.class}#comment not supported by this Ruby version (#{RUBY_VERSION})"
142
- end
143
-
144
- comment
122
+ MethodSource.comment_helper(source_location, name)
145
123
  end
146
124
  end
147
125
  end
@@ -0,0 +1,125 @@
1
+ module MethodSource
2
+
3
+ module CodeHelpers
4
+ # Retrieve the first expression starting on the given line of the given file.
5
+ #
6
+ # This is useful to get module or method source code.
7
+ #
8
+ # @param [Array<String>, File, String] file The file to parse, either as a File or as
9
+ # @param [Fixnum] line_number The line number at which to look.
10
+ # NOTE: The first line in a file is line 1!
11
+ # @param [Boolean] strict If set to true, then only completely valid expressions are
12
+ # returned. Otherwise heuristics are used to extract
13
+ # expressions that may have been valid inside an eval.
14
+ # @return [String] The first complete expression
15
+ # @raise [SyntaxError] If the first complete expression can't be identified
16
+ def expression_at(file, line_number, strict=false)
17
+ lines = file.is_a?(Array) ? file : file.each_line.to_a
18
+
19
+ relevant_lines = lines[(line_number - 1)..-1] || []
20
+
21
+ extract_first_expression(relevant_lines)
22
+ rescue SyntaxError => e
23
+ raise if strict
24
+
25
+ begin
26
+ extract_first_expression(relevant_lines) do |code|
27
+ code.gsub(/\#\{.*?\}/, "temp")
28
+ end
29
+ rescue SyntaxError => e2
30
+ raise e
31
+ end
32
+ end
33
+
34
+ # Retrieve the comment describing the expression on the given line of the given file.
35
+ #
36
+ # This is useful to get module or method documentation.
37
+ #
38
+ # @param [Array<String>, File, String] file The file to parse, either as a File or as
39
+ # a String or an Array of lines.
40
+ # @param [Fixnum] line_number The line number at which to look.
41
+ # NOTE: The first line in a file is line 1!
42
+ # @return [String] The comment
43
+ def comment_describing(file, line_number)
44
+ lines = file.is_a?(Array) ? file : file.each_line.to_a
45
+
46
+ extract_last_comment(lines[0..(line_number - 2)])
47
+ end
48
+
49
+ # Determine if a string of code is a complete Ruby expression.
50
+ # @param [String] code The code to validate.
51
+ # @return [Boolean] Whether or not the code is a complete Ruby expression.
52
+ # @raise [SyntaxError] Any SyntaxError that does not represent incompleteness.
53
+ # @example
54
+ # complete_expression?("class Hello") #=> false
55
+ # complete_expression?("class Hello; end") #=> true
56
+ # complete_expression?("class 123") #=> SyntaxError: unexpected tINTEGER
57
+ def complete_expression?(str)
58
+ old_verbose = $VERBOSE
59
+ $VERBOSE = nil
60
+
61
+ catch(:valid) do
62
+ eval("BEGIN{throw :valid}\n#{str}")
63
+ end
64
+
65
+ # Assert that a line which ends with a , or \ is incomplete.
66
+ str !~ /[,\\]\s*\z/
67
+ rescue IncompleteExpression
68
+ false
69
+ ensure
70
+ $VERBOSE = old_verbose
71
+ end
72
+
73
+ private
74
+
75
+ # Get the first expression from the input.
76
+ #
77
+ # @param [Array<String>] lines
78
+ # @param [&Block] a clean-up function to run before checking for complete_expression
79
+ # @return [String] a valid ruby expression
80
+ # @raise [SyntaxError]
81
+ def extract_first_expression(lines, &block)
82
+ code = ""
83
+ lines.each do |v|
84
+ code << v
85
+ return code if complete_expression?(block ? block.call(code) : code)
86
+ end
87
+ raise SyntaxError, "unexpected $end"
88
+ end
89
+
90
+ # Get the last comment from the input.
91
+ #
92
+ # @param [Array<String>] lines
93
+ # @return [String]
94
+ def extract_last_comment(lines)
95
+ buffer = ""
96
+
97
+ lines.each do |line|
98
+ # Add any line that is a valid ruby comment,
99
+ # but clear as soon as we hit a non comment line.
100
+ if (line =~ /^\s*#/) || (line =~ /^\s*$/)
101
+ buffer << line.lstrip
102
+ else
103
+ buffer.replace("")
104
+ end
105
+ end
106
+
107
+ buffer
108
+ end
109
+
110
+ # An exception matcher that matches only subsets of SyntaxErrors that can be
111
+ # fixed by adding more input to the buffer.
112
+ module IncompleteExpression
113
+ def self.===(ex)
114
+ case ex.message
115
+ when /unexpected (\$end|end-of-file|END_OF_FILE)/, # mri, jruby, ironruby
116
+ /unterminated (quoted string|string|regexp) meets end of file/, # "quoted string" is ironruby
117
+ /missing 'end' for/, /: expecting '[})\]]'$/, /can't find string ".*" anywhere before EOF/, /: expecting keyword_end/, /expecting kWHEN/ # rbx
118
+ true
119
+ else
120
+ false
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -46,7 +46,7 @@ module MethodSource
46
46
  set_trace_func nil
47
47
  @file = File.expand_path(@file) if @file && File.exist?(File.expand_path(@file))
48
48
  end
49
- return [@file, @line] if File.exist?(@file.to_s)
49
+ [@file, @line] if @file
50
50
  end
51
51
  end
52
52
  end
@@ -1,3 +1,3 @@
1
1
  module MethodSource
2
- VERSION = "0.7.1"
2
+ VERSION = "0.8.pre.1"
3
3
  end
@@ -2,17 +2,17 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "method_source"
5
- s.version = "0.7.0"
5
+ s.version = "0.8.pre.1"
6
6
 
7
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
7
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["John Mair (banisterfiend)"]
9
- s.date = "2012-01-01"
9
+ s.date = "2012-06-03"
10
10
  s.description = "retrieve the sourcecode for a method"
11
11
  s.email = "jrmair@gmail.com"
12
- s.files = [".gemtest", ".travis.yml", ".yardopts", "Gemfile", "LICENSE", "README.markdown", "Rakefile", "lib/method_source.rb", "lib/method_source/source_location.rb", "lib/method_source/version.rb", "method_source.gemspec", "test/test.rb", "test/test_helper.rb"]
12
+ s.files = [".gemtest", ".travis.yml", ".yardopts", "Gemfile", "LICENSE", "README.markdown", "Rakefile", "lib/method_source.rb", "lib/method_source/code_helpers.rb", "lib/method_source/source_location.rb", "lib/method_source/version.rb", "method_source.gemspec", "test/test.rb", "test/test_helper.rb"]
13
13
  s.homepage = "http://banisterfiend.wordpress.com"
14
14
  s.require_paths = ["lib"]
15
- s.rubygems_version = "1.8.10"
15
+ s.rubygems_version = "1.8.24"
16
16
  s.summary = "retrieve the sourcecode for a method"
17
17
  s.test_files = ["test/test.rb", "test/test_helper.rb"]
18
18
 
data/test/test.rb CHANGED
@@ -1,4 +1,4 @@
1
- direc = File.dirname(__FILE__)
1
+ direc = File.expand_path(File.dirname(__FILE__))
2
2
 
3
3
  require 'rubygems'
4
4
  require 'bacon'
@@ -33,6 +33,10 @@ describe MethodSource do
33
33
  @lambda_comment = "# This is a comment for MyLambda\n"
34
34
  @lambda_source = "MyLambda = lambda { :lambda }\n"
35
35
  @proc_source = "MyProc = Proc.new { :proc }\n"
36
+ @hello_instance_evaled_source = " def hello_\#{name}(*args)\n send_mesg(:\#{name}, *args)\n end\n"
37
+ @hello_instance_evaled_source_2 = " def \#{name}_two()\n if 44\n 45\n end\n end\n"
38
+ @hello_class_evaled_source = " def hello_\#{name}(*args)\n send_mesg(:\#{name}, *args)\n end\n"
39
+ @hi_module_evaled_source = " def hi_\#{name}\n @var = \#{name}\n end\n"
36
40
  end
37
41
 
38
42
  it 'should define methods on Method and UnboundMethod and Proc' do
@@ -58,15 +62,27 @@ describe MethodSource do
58
62
  $o.method(:hello).source.should == @hello_singleton_source
59
63
  end
60
64
 
61
-
62
65
  it 'should return a comment for method' do
63
66
  method(:hello).comment.should == @hello_comment
64
67
  end
65
68
 
69
+ # These tests fail because of http://jira.codehaus.org/browse/JRUBY-4576
70
+ unless defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
71
+ it 'should return source for an *_evaled method' do
72
+ M.method(:hello_name).source.should == @hello_instance_evaled_source
73
+ M.method(:name_two).source.should == @hello_instance_evaled_source_2
74
+ M.instance_method(:hello_name).source.should == @hello_class_evaled_source
75
+ M.instance_method(:hi_name).source.should == @hi_module_evaled_source
76
+ end
77
+ end
78
+
79
+ it "should raise error for evaled methods that do not pass __FILE__ and __LINE__ + 1 as its arguments" do
80
+ lambda { M.instance_method(:name_three).source }.should.raise MethodSource::SourceNotFoundError
81
+ end
66
82
 
67
83
  if !is_rbx?
68
84
  it 'should raise for C methods' do
69
- lambda { method(:puts).source }.should.raise RuntimeError
85
+ lambda { method(:puts).source }.should.raise MethodSource::SourceNotFoundError
70
86
  end
71
87
  end
72
88
  end
data/test/test_helper.rb CHANGED
@@ -48,3 +48,51 @@ def comment_test5; end
48
48
  MyLambda = lambda { :lambda }
49
49
  MyProc = Proc.new { :proc }
50
50
 
51
+
52
+ name = "name"
53
+
54
+ M.instance_eval <<-METHOD, __FILE__, __LINE__ + 1
55
+ def hello_#{name}(*args)
56
+ send_mesg(:#{name}, *args)
57
+ end
58
+ METHOD
59
+
60
+ M.class_eval <<-METHOD, __FILE__, __LINE__ + 1
61
+ def hello_#{name}(*args)
62
+ send_mesg(:#{name}, *args)
63
+ end
64
+ METHOD
65
+
66
+ # module_eval to DRY code up
67
+ #
68
+ M.module_eval <<-METHOD, __FILE__, __LINE__ + 1
69
+
70
+ # module_eval is used here
71
+ #
72
+ def hi_#{name}
73
+ @var = #{name}
74
+ end
75
+ METHOD
76
+
77
+ # case where 2 methods are defined inside an _eval block
78
+ #
79
+ M.instance_eval <<EOF, __FILE__, __LINE__ + 1
80
+
81
+ def #{name}_one()
82
+ if 43
83
+ 44
84
+ end
85
+ end
86
+
87
+
88
+ def #{name}_two()
89
+ if 44
90
+ 45
91
+ end
92
+ end
93
+ EOF
94
+
95
+ # class_eval without filename and lineno + 1 parameter
96
+
97
+ M.class_eval "def #{name}_three; @tempfile.#{name}; end"
98
+
metadata CHANGED
@@ -1,62 +1,54 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: method_source
3
- version: !ruby/object:Gem::Version
4
- hash: 4452373466457572121
5
- prerelease:
6
- segments:
7
- - 0
8
- - 7
9
- - 1
10
- version: 0.7.1
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.pre.1
5
+ prerelease: 4
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - John Mair (banisterfiend)
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2012-02-29 00:00:00 Z
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
12
+ date: 2012-06-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
21
15
  name: bacon
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
24
17
  none: false
25
- requirements:
18
+ requirements:
26
19
  - - ~>
27
- - !ruby/object:Gem::Version
28
- hash: 3461773182973075480
29
- segments:
30
- - 1
31
- - 1
32
- - 0
20
+ - !ruby/object:Gem::Version
33
21
  version: 1.1.0
34
22
  type: :development
35
- version_requirements: *id001
36
- - !ruby/object:Gem::Dependency
37
- name: rake
38
23
  prerelease: false
39
- requirement: &id002 !ruby/object:Gem::Requirement
24
+ version_requirements: !ruby/object:Gem::Requirement
40
25
  none: false
41
- requirements:
26
+ requirements:
42
27
  - - ~>
43
- - !ruby/object:Gem::Version
44
- hash: 2854635824043747355
45
- segments:
46
- - 0
47
- - 9
48
- version: "0.9"
28
+ - !ruby/object:Gem::Version
29
+ version: 1.1.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '0.9'
49
38
  type: :development
50
- version_requirements: *id002
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '0.9'
51
46
  description: retrieve the sourcecode for a method
52
47
  email: jrmair@gmail.com
53
48
  executables: []
54
-
55
49
  extensions: []
56
-
57
50
  extra_rdoc_files: []
58
-
59
- files:
51
+ files:
60
52
  - .gemtest
61
53
  - .travis.yml
62
54
  - .yardopts
@@ -65,6 +57,7 @@ files:
65
57
  - README.markdown
66
58
  - Rakefile
67
59
  - lib/method_source.rb
60
+ - lib/method_source/code_helpers.rb
68
61
  - lib/method_source/source_location.rb
69
62
  - lib/method_source/version.rb
70
63
  - method_source.gemspec
@@ -72,37 +65,28 @@ files:
72
65
  - test/test_helper.rb
73
66
  homepage: http://banisterfiend.wordpress.com
74
67
  licenses: []
75
-
76
68
  post_install_message:
77
69
  rdoc_options: []
78
-
79
- require_paths:
70
+ require_paths:
80
71
  - lib
81
- required_ruby_version: !ruby/object:Gem::Requirement
72
+ required_ruby_version: !ruby/object:Gem::Requirement
82
73
  none: false
83
- requirements:
84
- - - ">="
85
- - !ruby/object:Gem::Version
86
- hash: 2002549777813010636
87
- segments:
88
- - 0
89
- version: "0"
90
- required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
79
  none: false
92
- requirements:
93
- - - ">="
94
- - !ruby/object:Gem::Version
95
- hash: 2002549777813010636
96
- segments:
97
- - 0
98
- version: "0"
80
+ requirements:
81
+ - - ! '>'
82
+ - !ruby/object:Gem::Version
83
+ version: 1.3.1
99
84
  requirements: []
100
-
101
85
  rubyforge_project:
102
- rubygems_version: 1.8.12
86
+ rubygems_version: 1.8.24
103
87
  signing_key:
104
88
  specification_version: 3
105
89
  summary: retrieve the sourcecode for a method
106
- test_files:
90
+ test_files:
107
91
  - test/test.rb
108
92
  - test/test_helper.rb