method_source 0.7.1 → 0.8.pre.1

Sign up to get free protection for your applications and to get access to all the features.
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