Skip to content

Instantly share code, notes, and snippets.

@xiangzhuyuan
Created September 11, 2014 13:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xiangzhuyuan/305ede51ccbfd6af61d0 to your computer and use it in GitHub Desktop.
Save xiangzhuyuan/305ede51ccbfd6af61d0 to your computer and use it in GitHub Desktop.
学学 rspec
#!/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)
@xiangzhuyuan
Copy link
Author

# ~/.rvm/gems/ruby-2.1.2/bin/ruby_executable_hooks

这个是什么呢?

#!/usr/bin/env ruby

title = "ruby #{ARGV*" "}"
$0    = ARGV.shift
Process.setproctitle(title) if Process.methods.include?(:setproctitle)

require 'rubygems'
begin
  require 'executable-hooks/hooks'
  Gem::ExecutableHooks.run($0)
rescue LoadError
  warn "unable to load executable-hooks/hooks" if ENV.key?('ExecutableHooks_DEBUG')
end

eval File.read($0), binding, $0 # 这个 eval 方法呢?

eval

这个里面提到了 binding

Binding

Objects of class Binding encapsulate the execution context at some particular place in the code and retain this context for future use. The variables, methods, value of self, and possibly an iterator block that can be accessed in this context are all retained. Binding objects can be created using Kernel#binding, and are made available to the callback of Kernel#set_trace_func.

These binding objects can be passed as the second argument of the Kernel#eval method, establishing an environment for the evaluation.

Binding 这个对象能够封装特定的执行上下文,然后能够保存这个上下文待日后使用.
对象,方法, self 自己的值,甚至一个遍历的块都能够在上下文获取到.
这个 Binding 对象可以通过 Kernel#binding 获取到, 可以在 Kernel#set_trace_func 的回调里可以用.

在 Kernel#eval 方法里可以作为第二个参数传入 binding 对象,在这个特定的上下文环境里执行第一个参数里的以 string 方式保存的代码!

@xiangzhuyuan
Copy link
Author

Ruby中,有多种方法可以实现方法的动态调用。

  1. 使用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!

  1. 使用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。

@xiangzhuyuan
Copy link
Author

    # 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 是用来执行代码的.

@xiangzhuyuan
Copy link
Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment