[DRE-commits] [SCM] ruby-method-source.git branch, master, updated. debian/0.7.1-1-4-g401a87c
Youhei SASAKI
uwabami at gfd-dennou.org
Sat Jan 19 22:14:37 UTC 2013
The following commit has been merged in the master branch:
commit a312035c9ba43e8b9cbada9b0b7f5fb8db2ad3ab
Author: Youhei SASAKI <uwabami at gfd-dennou.org>
Date: Sun Jan 20 07:01:00 2013 +0900
Imported Upstream version 0.8.1
diff --git a/Rakefile b/Rakefile
index 92c0234..7dad800 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,8 +1,8 @@
-dlext = Config::CONFIG['DLEXT']
+dlext = RbConfig::CONFIG['DLEXT']
direc = File.dirname(__FILE__)
require 'rake/clean'
-require 'rake/gempackagetask'
+require 'rubygems/package_task'
require "#{direc}/lib/method_source/version"
CLOBBER.include("**/*.#{dlext}", "**/*~", "**/*#*", "**/*.log", "**/*.o")
@@ -29,7 +29,7 @@ def apply_spec_defaults(s)
end
task :test do
- sh "bacon -q #{direc}/test/test.rb"
+ sh "bacon -q #{direc}/test/test.rb #{direc}/test/test_code_helpers.rb"
end
desc "reinstall gem"
@@ -41,13 +41,16 @@ end
desc "Set up and run tests"
task :default => [:test]
+desc "Build the gemspec file"
+task :gemspec => "ruby:gemspec"
+
namespace :ruby do
spec = Gem::Specification.new do |s|
apply_spec_defaults(s)
s.platform = Gem::Platform::RUBY
end
- Rake::GemPackageTask.new(spec) do |pkg|
+ Gem::PackageTask.new(spec) do |pkg|
pkg.need_zip = false
pkg.need_tar = false
end
diff --git a/lib/method_source.rb b/lib/method_source.rb
index 9a3c325..7d16c3b 100644
--- a/lib/method_source.rb
+++ b/lib/method_source.rb
@@ -5,74 +5,66 @@ direc = File.dirname(__FILE__)
require "#{direc}/method_source/version"
require "#{direc}/method_source/source_location"
+require "#{direc}/method_source/code_helpers"
module MethodSource
- # Determine if a string of code is a valid Ruby expression.
- # @param [String] code The code to validate.
- # @return [Boolean] Whether or not the code is a valid Ruby expression.
- # @example
- # valid_expression?("class Hello") #=> false
- # valid_expression?("class Hello; end") #=> true
- def self.valid_expression?(str)
- if defined?(Rubinius::Melbourne19) && RUBY_VERSION =~ /^1\.9/
- Rubinius::Melbourne19.parse_string(str)
- elsif defined?(Rubinius::Melbourne)
- Rubinius::Melbourne.parse_string(str)
- else
- catch(:valid) {
- eval("BEGIN{throw :valid}\n#{str}")
- }
- end
- true
- rescue SyntaxError
- false
- end
+ extend MethodSource::CodeHelpers
+
+ # An Exception to mark errors that were raised trying to find the source from
+ # a given source_location.
+ #
+ class SourceNotFoundError < StandardError; end
# Helper method responsible for extracting method body.
# Defined here to avoid polluting `Method` class.
# @param [Array] source_location The array returned by Method#source_location
- # @return [File] The opened source file
- def self.source_helper(source_location)
- return nil if !source_location.is_a?(Array)
-
- file_name, line = source_location
- File.open(file_name) do |file|
- (line - 1).times { file.readline }
-
- code = ""
- loop do
- val = file.readline
- code << val
-
- return code if valid_expression?(code)
- end
- end
+ # @param [String] method_name
+ # @return [String] The method body
+ def self.source_helper(source_location, name=nil)
+ raise SourceNotFoundError, "Could not locate source for #{name}!" unless source_location
+ file, line = *source_location
+
+ expression_at(lines_for(file), line)
+ rescue SyntaxError => e
+ raise SourceNotFoundError, "Could not parse source for #{name}: #{e.message}"
end
# Helper method responsible for opening source file and buffering up
# the comments for a specified method. Defined here to avoid polluting
# `Method` class.
# @param [Array] source_location The array returned by Method#source_location
+ # @param [String] method_name
# @return [String] The comments up to the point of the method.
- def self.comment_helper(source_location)
- return nil if !source_location.is_a?(Array)
-
- file_name, line = source_location
- File.open(file_name) do |file|
- buffer = ""
- (line - 1).times do
- line = file.readline
- # Add any line that is a valid ruby comment,
- # but clear as soon as we hit a non comment line.
- if (line =~ /^\s*#/) || (line =~ /^\s*$/)
- buffer << line.lstrip
- else
- buffer.replace("")
- end
- end
+ def self.comment_helper(source_location, name=nil)
+ raise SourceNotFoundError, "Could not locate source for #{name}!" unless source_location
+ file, line = *source_location
- buffer
- end
+ comment_describing(lines_for(file), line)
+ end
+
+ # Load a memoized copy of the lines in a file.
+ #
+ # @param [String] file_name
+ # @param [String] method_name
+ # @return [Array<String>] the contents of the file
+ # @raise [SourceNotFoundError]
+ def self.lines_for(file_name, name=nil)
+ @lines_for_file ||= {}
+ @lines_for_file[file_name] ||= File.readlines(file_name)
+ rescue Errno::ENOENT => e
+ raise SourceNotFoundError, "Could not load source for #{name}: #{e.message}"
+ end
+
+ # @deprecated — use MethodSource::CodeHelpers#complete_expression?
+ def self.valid_expression?(str)
+ complete_expression?(str)
+ rescue SyntaxError
+ false
+ end
+
+ # @deprecated — use MethodSource::CodeHelpers#expression_at
+ def self.extract_code(source_location)
+ source_helper(source_location)
end
# This module is to be included by `Method` and `UnboundMethod` and
@@ -104,8 +96,9 @@ module MethodSource
end
# Return the sourcecode for the method as a string
- # (This functionality is only supported in Ruby 1.9 and above)
# @return [String] The method sourcecode as a string
+ # @raise SourceNotFoundException
+ #
# @example
# Set.instance_method(:clear).source.display
# =>
@@ -114,34 +107,19 @@ module MethodSource
# self
# end
def source
- if respond_to?(:source_location)
- source = MethodSource.source_helper(source_location)
-
- raise "Cannot locate source for this method: #{name}" if !source
- else
- raise "#{self.class}#source not supported by this Ruby version (#{RUBY_VERSION})"
- end
-
- source
+ MethodSource.source_helper(source_location, defined?(name) ? name : inspect)
end
# Return the comments associated with the method as a string.
- # (This functionality is only supported in Ruby 1.9 and above)
# @return [String] The method's comments as a string
+ # @raise SourceNotFoundException
+ #
# @example
# Set.instance_method(:clear).comment.display
# =>
# # Removes all elements and returns self.
def comment
- if respond_to?(:source_location)
- comment = MethodSource.comment_helper(source_location)
-
- raise "Cannot locate source for this method: #{name}" if !comment
- else
- raise "#{self.class}#comment not supported by this Ruby version (#{RUBY_VERSION})"
- end
-
- comment
+ MethodSource.comment_helper(source_location, defined?(name) ? name : inspect)
end
end
end
diff --git a/lib/method_source/code_helpers.rb b/lib/method_source/code_helpers.rb
new file mode 100644
index 0000000..c564f54
--- /dev/null
+++ b/lib/method_source/code_helpers.rb
@@ -0,0 +1,139 @@
+module MethodSource
+
+ module CodeHelpers
+ # Retrieve the first expression starting on the given line of the given file.
+ #
+ # This is useful to get module or method source code.
+ #
+ # @param [Array<String>, File, String] file The file to parse, either as a File or as
+ # @param [Fixnum] line_number The line number at which to look.
+ # NOTE: The first line in a file is
+ # line 1!
+ # @param [Hash] options The optional configuration parameters.
+ # @option options [Boolean] :strict If set to true, then only completely
+ # valid expressions are returned. Otherwise heuristics are used to extract
+ # expressions that may have been valid inside an eval.
+ # @option options [Fixnum] :consume A number of lines to automatically
+ # consume (add to the expression buffer) without checking for validity.
+ # @return [String] The first complete expression
+ # @raise [SyntaxError] If the first complete expression can't be identified
+ def expression_at(file, line_number, options={})
+ options = {
+ :strict => false,
+ :consume => 0
+ }.merge!(options)
+
+ lines = file.is_a?(Array) ? file : file.each_line.to_a
+
+ relevant_lines = lines[(line_number - 1)..-1] || []
+
+ extract_first_expression(relevant_lines, options[:consume])
+ rescue SyntaxError => e
+ raise if options[:strict]
+
+ begin
+ extract_first_expression(relevant_lines) do |code|
+ code.gsub(/\#\{.*?\}/, "temp")
+ end
+ rescue SyntaxError => e2
+ raise e
+ end
+ end
+
+ # Retrieve the comment describing the expression on the given line of the given file.
+ #
+ # This is useful to get module or method documentation.
+ #
+ # @param [Array<String>, File, String] file The file to parse, either as a File or as
+ # a String or an Array of lines.
+ # @param [Fixnum] line_number The line number at which to look.
+ # NOTE: The first line in a file is line 1!
+ # @return [String] The comment
+ def comment_describing(file, line_number)
+ lines = file.is_a?(Array) ? file : file.each_line.to_a
+
+ extract_last_comment(lines[0..(line_number - 2)])
+ end
+
+ # Determine if a string of code is a complete Ruby expression.
+ # @param [String] code The code to validate.
+ # @return [Boolean] Whether or not the code is a complete Ruby expression.
+ # @raise [SyntaxError] Any SyntaxError that does not represent incompleteness.
+ # @example
+ # complete_expression?("class Hello") #=> false
+ # complete_expression?("class Hello; end") #=> true
+ # complete_expression?("class 123") #=> SyntaxError: unexpected tINTEGER
+ def complete_expression?(str)
+ old_verbose = $VERBOSE
+ $VERBOSE = nil
+
+ catch(:valid) do
+ eval("BEGIN{throw :valid}\n#{str}")
+ end
+
+ # Assert that a line which ends with a , or \ is incomplete.
+ str !~ /[,\\]\s*\z/
+ rescue IncompleteExpression
+ false
+ ensure
+ $VERBOSE = old_verbose
+ end
+
+ private
+
+ # Get the first expression from the input.
+ #
+ # @param [Array<String>] lines
+ # @param [Fixnum] consume A number of lines to automatically
+ # consume (add to the expression buffer) without checking for validity.
+ # @yield a clean-up function to run before checking for complete_expression
+ # @return [String] a valid ruby expression
+ # @raise [SyntaxError]
+ def extract_first_expression(lines, consume=0, &block)
+ code = consume.zero? ? "" : lines.slice!(0..(consume - 1)).join
+
+ lines.each do |v|
+ code << v
+ return code if complete_expression?(block ? block.call(code) : code)
+ end
+ raise SyntaxError, "unexpected $end"
+ end
+
+ # Get the last comment from the input.
+ #
+ # @param [Array<String>] lines
+ # @return [String]
+ def extract_last_comment(lines)
+ buffer = ""
+
+ lines.each do |line|
+ # Add any line that is a valid ruby comment,
+ # but clear as soon as we hit a non comment line.
+ if (line =~ /^\s*#/) || (line =~ /^\s*$/)
+ buffer << line.lstrip
+ else
+ buffer.replace("")
+ end
+ end
+
+ buffer
+ end
+
+ # An exception matcher that matches only subsets of SyntaxErrors that can be
+ # fixed by adding more input to the buffer.
+ module IncompleteExpression
+ def self.===(ex)
+ return false unless SyntaxError === ex
+ case ex.message
+ when /unexpected (\$end|end-of-file|end-of-input|END_OF_FILE)/, # mri, jruby, ruby-2.0, ironruby
+ /embedded document meets end of file/, # =begin
+ /unterminated (quoted string|string|regexp) meets end of file/, # "quoted string" is ironruby
+ /missing 'end' for/, /: expecting '[})\]]'$/, /can't find string ".*" anywhere before EOF/, /: expecting keyword_end/, /expecting kWHEN/ # rbx
+ true
+ else
+ false
+ end
+ end
+ end
+ end
+end
diff --git a/lib/method_source/source_location.rb b/lib/method_source/source_location.rb
index 9161854..1e2a22a 100644
--- a/lib/method_source/source_location.rb
+++ b/lib/method_source/source_location.rb
@@ -46,7 +46,7 @@ module MethodSource
set_trace_func nil
@file = File.expand_path(@file) if @file && File.exist?(File.expand_path(@file))
end
- return [@file, @line] if File.exist?(@file.to_s)
+ [@file, @line] if @file
end
end
end
diff --git a/lib/method_source/version.rb b/lib/method_source/version.rb
index b8142bf..092e7ec 100644
--- a/lib/method_source/version.rb
+++ b/lib/method_source/version.rb
@@ -1,3 +1,3 @@
module MethodSource
- VERSION = "0.7.1"
+ VERSION = "0.8.1"
end
diff --git a/metadata.yml b/metadata.yml
index a1db8f6..1e7b944 100644
--- a/metadata.yml
+++ b/metadata.yml
@@ -1,62 +1,54 @@
---- !ruby/object:Gem::Specification
+--- !ruby/object:Gem::Specification
name: method_source
-version: !ruby/object:Gem::Version
- hash: 4452373466457572121
+version: !ruby/object:Gem::Version
+ version: 0.8.1
prerelease:
- segments:
- - 0
- - 7
- - 1
- version: 0.7.1
platform: ruby
-authors:
+authors:
- John Mair (banisterfiend)
autorequire:
bindir: bin
cert_chain: []
-
-date: 2012-02-29 00:00:00 Z
-dependencies:
-- !ruby/object:Gem::Dependency
+date: 2012-10-17 00:00:00.000000000 Z
+dependencies:
+- !ruby/object:Gem::Dependency
name: bacon
- prerelease: false
- requirement: &id001 !ruby/object:Gem::Requirement
+ requirement: !ruby/object:Gem::Requirement
none: false
- requirements:
+ requirements:
- - ~>
- - !ruby/object:Gem::Version
- hash: 3461773182973075480
- segments:
- - 1
- - 1
- - 0
+ - !ruby/object:Gem::Version
version: 1.1.0
type: :development
- version_requirements: *id001
-- !ruby/object:Gem::Dependency
- name: rake
prerelease: false
- requirement: &id002 !ruby/object:Gem::Requirement
+ version_requirements: !ruby/object:Gem::Requirement
none: false
- requirements:
+ requirements:
- - ~>
- - !ruby/object:Gem::Version
- hash: 2854635824043747355
- segments:
- - 0
- - 9
- version: "0.9"
+ - !ruby/object:Gem::Version
+ version: 1.1.0
+- !ruby/object:Gem::Dependency
+ name: rake
+ requirement: !ruby/object:Gem::Requirement
+ none: false
+ requirements:
+ - - ~>
+ - !ruby/object:Gem::Version
+ version: '0.9'
type: :development
- version_requirements: *id002
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ none: false
+ requirements:
+ - - ~>
+ - !ruby/object:Gem::Version
+ version: '0.9'
description: retrieve the sourcecode for a method
email: jrmair at gmail.com
executables: []
-
extensions: []
-
extra_rdoc_files: []
-
-files:
+files:
- .gemtest
- .travis.yml
- .yardopts
@@ -65,44 +57,38 @@ files:
- 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_code_helpers.rb
- test/test_helper.rb
homepage: http://banisterfiend.wordpress.com
licenses: []
-
post_install_message:
rdoc_options: []
-
-require_paths:
+require_paths:
- lib
-required_ruby_version: !ruby/object:Gem::Requirement
+required_ruby_version: !ruby/object:Gem::Requirement
none: false
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- hash: 2002549777813010636
- segments:
- - 0
- version: "0"
-required_rubygems_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - ! '>='
+ - !ruby/object:Gem::Version
+ version: '0'
+required_rubygems_version: !ruby/object:Gem::Requirement
none: false
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- hash: 2002549777813010636
- segments:
- - 0
- version: "0"
+ requirements:
+ - - ! '>='
+ - !ruby/object:Gem::Version
+ version: '0'
requirements: []
-
rubyforge_project:
-rubygems_version: 1.8.12
+rubygems_version: 1.8.23
signing_key:
specification_version: 3
summary: retrieve the sourcecode for a method
-test_files:
+test_files:
- test/test.rb
+- test/test_code_helpers.rb
- test/test_helper.rb
diff --git a/method_source.gemspec b/method_source.gemspec
index 83a727d..d24b3d9 100644
--- a/method_source.gemspec
+++ b/method_source.gemspec
@@ -2,19 +2,19 @@
Gem::Specification.new do |s|
s.name = "method_source"
- s.version = "0.7.0"
+ s.version = "0.8.1"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["John Mair (banisterfiend)"]
- s.date = "2012-01-01"
+ s.date = "2012-10-17"
s.description = "retrieve the sourcecode for a method"
s.email = "jrmair at gmail.com"
- 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"]
+ 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_code_helpers.rb", "test/test_helper.rb"]
s.homepage = "http://banisterfiend.wordpress.com"
s.require_paths = ["lib"]
- s.rubygems_version = "1.8.10"
+ s.rubygems_version = "1.8.23"
s.summary = "retrieve the sourcecode for a method"
- s.test_files = ["test/test.rb", "test/test_helper.rb"]
+ s.test_files = ["test/test.rb", "test/test_code_helpers.rb", "test/test_helper.rb"]
if s.respond_to? :specification_version then
s.specification_version = 3
diff --git a/test/test.rb b/test/test.rb
index 425e56a..4743a50 100644
--- a/test/test.rb
+++ b/test/test.rb
@@ -1,4 +1,4 @@
-direc = File.dirname(__FILE__)
+direc = File.expand_path(File.dirname(__FILE__))
require 'rubygems'
require 'bacon'
@@ -33,6 +33,10 @@ describe MethodSource do
@lambda_comment = "# This is a comment for MyLambda\n"
@lambda_source = "MyLambda = lambda { :lambda }\n"
@proc_source = "MyProc = Proc.new { :proc }\n"
+ @hello_instance_evaled_source = " def hello_\#{name}(*args)\n send_mesg(:\#{name}, *args)\n end\n"
+ @hello_instance_evaled_source_2 = " def \#{name}_two()\n if 44\n 45\n end\n end\n"
+ @hello_class_evaled_source = " def hello_\#{name}(*args)\n send_mesg(:\#{name}, *args)\n end\n"
+ @hi_module_evaled_source = " def hi_\#{name}\n @var = \#{name}\n end\n"
end
it 'should define methods on Method and UnboundMethod and Proc' do
@@ -58,15 +62,27 @@ describe MethodSource do
$o.method(:hello).source.should == @hello_singleton_source
end
-
it 'should return a comment for method' do
method(:hello).comment.should == @hello_comment
end
+ # These tests fail because of http://jira.codehaus.org/browse/JRUBY-4576
+ unless defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
+ it 'should return source for an *_evaled method' do
+ M.method(:hello_name).source.should == @hello_instance_evaled_source
+ M.method(:name_two).source.should == @hello_instance_evaled_source_2
+ M.instance_method(:hello_name).source.should == @hello_class_evaled_source
+ M.instance_method(:hi_name).source.should == @hi_module_evaled_source
+ end
+ end
+
+ it "should raise error for evaled methods that do not pass __FILE__ and __LINE__ + 1 as its arguments" do
+ lambda { M.instance_method(:name_three).source }.should.raise MethodSource::SourceNotFoundError
+ end
if !is_rbx?
it 'should raise for C methods' do
- lambda { method(:puts).source }.should.raise RuntimeError
+ lambda { method(:puts).source }.should.raise MethodSource::SourceNotFoundError
end
end
end
diff --git a/test/test_code_helpers.rb b/test/test_code_helpers.rb
new file mode 100644
index 0000000..ba83a63
--- /dev/null
+++ b/test/test_code_helpers.rb
@@ -0,0 +1,41 @@
+describe MethodSource::CodeHelpers do
+ before do
+ @tester = Object.new.extend(MethodSource::CodeHelpers)
+ end
+
+ [
+ ["p = '", "'"],
+ ["def", "a", "(); end"],
+ ["p = <<FOO", "lots", "and", "lots of", "foo", "FOO"],
+ ["[", ":lets,", "'list',", "[/nested/", "], things ]"],
+ ["abc =~ /hello", "/"],
+ ["issue = %W/", "343/"],
+ ["pouts(<<HI, 'foo", "bar", "HI", "baz')"],
+ ["=begin", "no-one uses this syntax anymore...", "=end"],
+ ["puts 1, 2,", "3"],
+ ["puts 'hello'\\", "'world'"]
+ ].each do |lines|
+ it "should not raise an error on broken lines: #{lines.join("\\n")}" do
+ 1.upto(lines.size - 1) do |i|
+ @tester.complete_expression?(lines[0...i].join("\n") + "\n").should == false
+ end
+ @tester.complete_expression?(lines.join("\n")).should == true
+ end
+ end
+
+ [
+ ["end"],
+ ["puts )("],
+ ["1 1"],
+ ["puts :"]
+ ] + (RbConfig::CONFIG['ruby_install_name'] == 'rbx' ? [] : [
+ ["def", "method(1"], # in this case the syntax error is "expecting ')'".
+ ["o = Object.new.tap{ def o.render;","'MEH'", "}"] # in this case the syntax error is "expecting keyword_end".
+ ]).compact.each do |foo|
+ it "should raise an error on invalid syntax like #{foo.inspect}" do
+ lambda{
+ @tester.complete_expression?(foo.join("\n"))
+ }.should.raise(SyntaxError)
+ end
+ end
+end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 53da4e5..3aabdf1 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -48,3 +48,51 @@ def comment_test5; end
MyLambda = lambda { :lambda }
MyProc = Proc.new { :proc }
+
+name = "name"
+
+M.instance_eval <<-METHOD, __FILE__, __LINE__ + 1
+ def hello_#{name}(*args)
+ send_mesg(:#{name}, *args)
+ end
+METHOD
+
+M.class_eval <<-METHOD, __FILE__, __LINE__ + 1
+ def hello_#{name}(*args)
+ send_mesg(:#{name}, *args)
+ end
+METHOD
+
+# module_eval to DRY code up
+#
+M.module_eval <<-METHOD, __FILE__, __LINE__ + 1
+
+ # module_eval is used here
+ #
+ def hi_#{name}
+ @var = #{name}
+ end
+METHOD
+
+# case where 2 methods are defined inside an _eval block
+#
+M.instance_eval <<EOF, __FILE__, __LINE__ + 1
+
+ def #{name}_one()
+ if 43
+ 44
+ end
+ end
+
+
+ def #{name}_two()
+ if 44
+ 45
+ end
+ end
+EOF
+
+# class_eval without filename and lineno + 1 parameter
+
+M.class_eval "def #{name}_three; @tempfile.#{name}; end"
+
--
ruby-method-source.git
More information about the Pkg-ruby-extras-commits
mailing list