我有很多ruby文件,每一个都声明了一个Class
,但是每一个都可以从命令行运行。
我想将以下功能放在每个文件的底部,尽可能减少重复:
if __FILE__ == $0
# instantiate the class and pass ARGV to instance.run
end
我的第一反应是这样做:
# /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) }
但这不起作用,因为
__FILE__
是在scriptize.rb
中计算的,所以它永远不是foo。我认为解决方案是直接内联
scriptize.rb
的内容,但我不知道语法。我可以使用eval
,但这仍然是相当多的重复——它不能真正简化为我添加到Kernel
中的方法。 最佳答案
使用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"]
这给出了
scriptize
的定义。# scriptize.rb
def scriptize
yield ARGV if caller.size == 1
end
现在,作为一个例子,我们可以使用两个相互需要的库/可执行文件
# 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}" }
所以当我们从命令行运行时:
% 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"]
因此,这显示了使用scriptize和
if $0 == __FILE__
的等价性。但是,请考虑:
if $0 == __FILE__ ... end
是一个标准的ruby习惯用法,阅读您的代码的其他人很容易识别require 'scriptize'; scriptize { |args| ... }
更多的是为了同样的效果而输入。为了让这一切变得真正有价值,你需要在scriptize的主体中有更多的通用性——初始化一些文件,解析参数等等。一旦它变得足够复杂,你最好以不同的方式分解更改——也许传递scriptize你的类,因此,它可以实例化它们并执行主脚本体,或者拥有一个主脚本,根据名称的不同动态地需要一个类。