-
-
Save xiangzhuyuan/305ede51ccbfd6af61d0 to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby_executable_hooks 这个有点意思哦! | |
# | |
# This file was generated by RubyGems. | |
# | |
# The application 'rspec-core' is installed as part of a gem, and | |
# this file is here to facilitate running it. | |
# | |
require 'rubygems' | |
version = ">= 0" | |
if ARGV.first | |
str = ARGV.first | |
str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding | |
if str =~ /\A_(.*)_\z/ and Gem::Version.correct?($1) then | |
version = $1 | |
ARGV.shift | |
end | |
end | |
gem 'rspec-core', version | |
load Gem.bin_path('rspec-core', 'rspec', version) |
Ruby中,有多种方法可以实现方法的动态调用。
- 使用send方法
第一种实现动态方法调用是使用send
方法,send方法在Object类中定义,方法的第一个参数是一个符号用来表示所要调用的方法,后面则是所调用方法需要的参数。
“This is a dog1″.send(:length) => 14
上面的代码中通过send方法去对一个字符串执行length操作,返回字符串的长度。
class TestClass
def hello(*args)
”Hello ” + args.join(‘ ‘)
end
end
a = TestClass.new
puts a.send :hello, “This”, “is”, “a”, “dog!”
执行结果为:
Hello This is a dog!
- 使用Method类和UnboundMethod类
另一种实现动态方法调用是使用Object类的method方法,这个方法返回一个Method类的对象。我们可以使用call方法来执行方法调用。
test1 = “This is a dog1″.method(:length)
test1.call => 14
class Test
def initialize(var)
@var = var
end
def hello()
”Hello, @var = #{@var}”
end
end
k = Test.new(10)
m = k.method(:hello)
m.call #=> “Hello, @iv = 99″
l = Test.new(‘Grant’)
m = l.method(“hello”)
m.call #=> “Hello, @iv = Fred”
可以在使用对象的任何地方使用method对象,当调用call方法时,参数所指明的方法会被执行,这种行为有些像C语言中的函数指针。你也可以把method对象作为一个迭代器使用。
def square(a)
a*a
end
mObj = method(:square)
[1, 2, 3, 4].collect(&mObj) => [1 4 9 16]
Method对象都是和某一特定对象绑定的,也就是说你需要通过某一对象使用Method对象。你也可以通过UnboundMethod类创建对象,然后再把它绑定到某个具体的对象中。如果UnboundMethod对象调用时尚未绑定,则会引发异常。
class Double
def get_value
2 * @side
end
def initialize(side)
@side = side
end
end
a = Double.instance_method(:get_value) #返回一个UnboundMethod对象
s = Double.new(50)
b = a.bind(s)
puts b.call
执行结果为:
100
看下面一个更具体的例子:
`class CommandInterpreter
def do_2() print “This is 2\n”; end
def do_1() print “This is 1\n”; end
def do_4() print “This is 4\n”; end
def do_3() print “This is 3\n”; end
Dispatcher = {
?2 => instance_method(:do_2),
?1 => instance_method(:do_1),
?4 => instance_method(:do_4),
?3 => instance_method(:do_3)
}
def interpret(string)
string.each_byte {|i| Dispatcher[i].bind(self).call }
end
end
interpreter = CommandInterpreter.new
interpreter.interpret(’1234′)
执行结果为:
This is 1
This is 2
This is 3
This is 4
3. 使用eval方法
我们还可以使用eval方法实现方法动态调用。eval方法在Kernel模块中定义,有多种变体如class_eval,module_eval,instance_eval等。Eval方法将分析其后的字符串参数并把这个字符串参数作为Ruby代码执行。
str = “Hello”
eval “str + ‘ World!’” => Hello World!
sentence = %q{“This is a test!”.length}
eval sentence => 15
当我们在使用eval方法时,我们可以通过eval方法的第二个参数指明eval所运行代码的上下文环境,这个参数可以是Binding类对象或Proc类对象。Binding类封装了代码在某一环境运行的上下文,可以供以后使用。
class BindingTest
def initialize(n)
@value = n
end
def getBinding
return binding() #使用Kernel#binding方法返回一个Binding对象
end
end
obj1 = BindingTest.new(10)
binding1 = obj1.getBinding
obj2 = BindingTest.new(“Binding Test”)
binding2 = obj2.getBinding
puts eval(“@value”, binding1) #=> 10
puts eval(“@value”, binding2) #=> Binding Test
puts eval(“@value”) #=> nil
可以看到上述代码中,@value在binding1所指明的上下文环境中值为10,在binding2所指明的上下文环境中值为Binding Test。当eval方法不提供binding参数时,在当前上下文环境中@value并未定义,值为nil。
# load(filename, wrap=false) -> true
#
# Loads and executes the Ruby
# program in the file _filename_. If the filename does not
# resolve to an absolute path, the file is searched for in the library
# directories listed in <code>$:</code>. If the optional _wrap_
# parameter is +true+, the loaded script will be executed
# under an anonymous module, protecting the calling program's global
# namespace. In no circumstance will any local variables in the loaded
# file be propagated to the loading environment.
#加载并执行, 如果不是绝对地址,就在库 $: 里搜,
#如果第二个参数是 true 的话这个脚本就在一个匿名的 module 里执行,给一个命名空间,起到保护作用.
#任何情况下,文件里的对象都会在当前环境里被加载
# require(name) -> true or false
#
# Loads the given +name+, returning +true+ if successful and +false+ if the
# feature is already loaded.
#
# If the filename does not resolve to an absolute path, it will be searched
# for in the directories listed in <code>$LOAD_PATH</code> (<code>$:</code>).
#
# If the filename has the extension ".rb", it is loaded as a source file; if
# the extension is ".so", ".o", or ".dll", or the default shared library
# extension on the current platform, Ruby loads the shared library as a
# Ruby extension. Otherwise, Ruby tries adding ".rb", ".so", and so on
# to the name until found. If the file named cannot be found, a LoadError
# will be raised.
#
# For Ruby extensions the filename given may use any shared library
# extension. For example, on Linux the socket extension is "socket.so" and
# <code>require 'socket.dll'</code> will load the socket extension.
#
# The absolute path of the loaded file is added to
# <code>$LOADED_FEATURES</code> (<code>$"</code>). A file will not be
# loaded again if its path already appears in <code>$"</code>. For example,
# <code>require 'a'; require './a'</code> will not load <code>a.rb</code>
# again.
#
# require "my-library.rb"
# require "db-driver"
#
# Any constants or globals within the loaded source file will be available
# in the calling program's global namespace. However, local variables will
# not be propagated to the loading environment.
另外一个好的比喻就是
The require() method is quite similar to load(), but it’s meant for a different purpose. You use load() to execute code, and you use require() to import libraries.
require 是用来加载库的,而 load 是用来执行代码的.
gem 'rspec-core', version
load Gem.bin_path('rspec-core', 'rspec', version)
# gem 方法:
# ~/.rvm/rubies/ruby-2.1.2/lib/ruby/2.1.0/rubygems/core_ext/kernel_gem.rb
##
# RubyGems adds the #gem method to allow activation of specific gem versions
# and overrides the #require method on Kernel to make gems appear as if they
# live on the <code>$LOAD_PATH</code>. See the documentation of these methods
# for further detail.
module Kernel
# REFACTOR: This should be pulled out into some kind of hacks file.
remove_method :gem if 'method' == defined? gem # from gem_prelude.rb on 1.9
##
# Use Kernel#gem to activate a specific version of +gem_name+.
#
# +requirements+ is a list of version requirements that the
# specified gem must match, most commonly "= example.version.number". See
# Gem::Requirement for how to specify a version requirement.
#
# If you will be activating the latest version of a gem, there is no need to
# call Kernel#gem, Kernel#require will do the right thing for you.
#
# Kernel#gem returns true if the gem was activated, otherwise false. If the
# gem could not be found, didn't match the version requirements, or a
# different version was already activated, an exception will be raised.
#
# Kernel#gem should be called *before* any require statements (otherwise
# RubyGems may load a conflicting library version).
#
# In older RubyGems versions, the environment variable GEM_SKIP could be
# used to skip activation of specified gems, for example to test out changes
# that haven't been installed yet. Now RubyGems defers to -I and the
# RUBYLIB environment variable to skip activation of a gem.
#
# Example:
#
# GEM_SKIP=libA:libB ruby -I../libA -I../libB ./mycode.rb
def gem(gem_name, *requirements) # :doc:
skip_list = (ENV['GEM_SKIP'] || "").split(/:/)
raise Gem::LoadError, "skipping #{gem_name}" if skip_list.include? gem_name
if gem_name.kind_of? Gem::Dependency
unless Gem::Deprecate.skip
warn "#{Gem.location_of_caller.join ':'}:Warning: Kernel.gem no longer "\
"accepts a Gem::Dependency object, please pass the name "\
"and requirements directly"
end
requirements = gem_name.requirement
gem_name = gem_name.name
end
spec = Gem::Dependency.new(gem_name, *requirements).to_spec #关键
spec.activate if spec
end
private :gem
end
load Gem.bin_path('rspec-core', 'rspec', version)
##
# Find the full path to the executable for gem +name+. If the +exec_name+
# is not given, the gem's default_executable is chosen, otherwise the
# specified executable's path is returned. +requirements+ allows
# you to specify specific gem versions.
def self.bin_path(name, exec_name = nil, *requirements)
# TODO: fails test_self_bin_path_bin_file_gone_in_latest
# Gem::Specification.find_by_name(name, *requirements).bin_file exec_name
raise ArgumentError, "you must supply exec_name" unless exec_name
requirements = Gem::Requirement.default if
requirements.empty?
specs = Gem::Dependency.new(name, requirements).matching_specs(true)
raise Gem::GemNotFoundException,
"can't find gem #{name} (#{requirements})" if specs.empty?
specs = specs.find_all { |spec|
spec.executables.include? exec_name
} if exec_name
unless spec = specs.last
msg = "can't find gem #{name} (#{requirements}) with executable #{exec_name}"
raise Gem::GemNotFoundException, msg
end
spec.bin_file exec_name
end
这个是什么呢?
eval
这个里面提到了
binding
Binding 这个对象能够封装特定的执行上下文,然后能够保存这个上下文待日后使用.
对象,方法, self 自己的值,甚至一个遍历的块都能够在上下文获取到.
这个 Binding 对象可以通过 Kernel#binding 获取到, 可以在 Kernel#set_trace_func 的回调里可以用.
在 Kernel#eval 方法里可以作为第二个参数传入 binding 对象,在这个特定的上下文环境里执行第一个参数里的以 string 方式保存的代码!