如何在另一个文件中包含Ruby源内联?

Jam*_*sen 4 ruby command-line

我有许多Ruby文件,每个文件声明一个Class,但每个文件都可以从命令行运行.

我想将以下功能放在每个文件的底部,尽可能减少重复:

if __FILE__ == $0
  # instantiate the class and pass ARGV to instance.run
end
Run Code Online (Sandbox Code Playgroud)

我的第一直觉是这样做:

# /lib/scriptize.rb:
Kernel.class_eval do
  def scriptize(&block)
    block.call(ARGV) if __FILE__ == $0
  end
end

# /lib/some_other_file.rb:
include 'scriptize'
class Foo
  # ...
end
scriptize { |args| Foo.new.run(args) }
Run Code Online (Sandbox Code Playgroud)

但这不起作用因为__FILE__被评估scriptize.rb,所以它永远不会 Foo.

我想解决方案是字面上内容scriptize.rb,但我不知道语法.我可以使用eval,但这仍然是相当多的重复 - 它不能真正简化为我添加的方法Kernel.

kch*_*kch 11

尝试评估它.

eval(IO.read(rubyfile), binding)
Run Code Online (Sandbox Code Playgroud)

这就是Rails的初始化程序在加载文件时所做的事情config/environments,因为它需要在Rails::Initializer.run块中对它们进行评估.

binding是一个ruby方法,它将返回当前上下文,当传递给eval它时,会使它评估调用环境中的代码.


试试这个:

  # my_class.rb 
  class MyClass
    def run
      puts 'hi'
    end
  end

  eval(IO.read('whereami.rb'), binding)


  # whereami.rb 
  puts __FILE__


  $ ruby my_class.rb 
  my_class.rb
Run Code Online (Sandbox Code Playgroud)


ram*_*ion 5

使用caller来确定你是调用堆栈的顶部有多接近:

---------------------------------------------------------- Kernel#caller
     caller(start=1)    => array
------------------------------------------------------------------------
     Returns the current execution stack---an array containing strings
     in the form ``_file:line_'' or ``_file:line: in `method'_''. The
     optional _start_ parameter determines the number of initial stack
     entries to omit from the result.

        def a(skip)
          caller(skip)
        end
        def b(skip)
          a(skip)
        end
        def c(skip)
          b(skip)
        end
        c(0)   #=> ["prog:2:in `a'", "prog:5:in `b'", "prog:8:in `c'", "prog:10"]
        c(1)   #=> ["prog:5:in `b'", "prog:8:in `c'", "prog:11"]
        c(2)   #=> ["prog:8:in `c'", "prog:12"]
        c(3)   #=> ["prog:13"]
Run Code Online (Sandbox Code Playgroud)

这给出了这个定义 scriptize

# scriptize.rb
def scriptize
    yield ARGV if caller.size == 1
end
Run Code Online (Sandbox Code Playgroud)

现在,作为一个例子,我们可以使用两个相互需要的库/可执行文件

# libexA.rb
require 'scriptize'
require 'libexB'

puts "in A, caller = #{caller.inspect}"
if __FILE__ == $0
    puts "A is the main script file"
end

scriptize { |args| puts "A was called with #{args.inspect}" }

# libexB.rb
require 'scriptize'
require 'libexA'

puts "in B, caller = #{caller.inspect}"
if __FILE__ == $0
    puts "B is the main script file"
end

scriptize { |args| puts "B was called with #{args.inspect}" }
Run Code Online (Sandbox Code Playgroud)

所以当我们从命令行运行时:

% ruby libexA.rb 1 2 3 4
in A, caller = ["./libexB.rb:2:in `require'", "./libexB.rb:2", "libexA.rb:2:in `require'", "libexA.rb:2"]
in B, caller = ["libexA.rb:2:in `require'", "libexA.rb:2"]
in A, caller = []
A is the main script file
A was called with ["1", "2", "3", "4"]
% ruby libexB.rb 4 3 2 1
in B, caller = ["./libexA.rb:2:in `require'", "./libexA.rb:2", "libexB.rb:2:in `require'", "libexB.rb:2"]
in A, caller = ["libexB.rb:2:in `require'", "libexB.rb:2"]
in B, caller = []
B is the main script file
B was called with ["4", "3", "2", "1"]
Run Code Online (Sandbox Code Playgroud)

所以这显示了使用 scriptize 和 if $0 == __FILE__

但是,请考虑:

  1. if $0 == __FILE__ ... end 是一个标准的 ruby​​ 习惯用法,很容易被其他人阅读你的代码识别
  2. require 'scriptize'; scriptize { |args| ... } 为相同的效果打字更多。

为了让它真正值得,你需要在 scriptize 的主体中有更多的共性 - 初始化一些文件,解析参数等。一旦它变得足够复杂,你可能会更好地分解出一种不同的方式 - 可能通过脚本化您的类,以便它可以实例化它们并执行主脚本主体,或者拥有一个主脚本,该脚本根据名称动态需要您的一个类。