我有许多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
的方法。
答案 0 :(得分:11)
尝试评估它。
eval(IO.read(rubyfile), binding)
这是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
答案 1 :(得分:4)
使用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
是标准的红宝石成语,很容易被其他人阅读您的代码识别require 'scriptize'; scriptize { |args| ... }
为同样的效果打字更多。为了让它真的值得,你需要在scriptize的主体中有更多的共性 - 初始化一些文件,解析参数等等。一旦它变得足够复杂,你可能会更好地解决问题以不同的方式进行更改 - 可能会通过脚本化您的类,因此它可以实例化它们并执行主脚本体,或者有一个主脚本根据名称动态地需要您的一个类。
答案 2 :(得分:1)
或者,您只需将__FILE__
传递给scriptize
# /lib/scriptize.rb:
module Kernel
def scriptize(calling_file, &block)
block.call(ARGV) if calling_file == $0
end
end
# /lib/some_other_file.rb:
...
scriptize(__FILE__) { |args| Foo.new.run(args) }
我也花时间取消了class_eval
的事情。 (您也可以取消整个module
内容,因为默认情况下Kernel
是您的范围。
答案 3 :(得分:1)
另一种方法是Test::Unit
如何做到这一点。测试用例文件中只有一个类定义(和require 'test/unit'
)。
'test / unit'库设置一个at_exit
处理程序,可以自动运行任何测试用例和套件。如果您最常见的情况是运行这些类文件,并偶尔将它们用作库,您可以执行类似的操作,并设置全局以在将其作为库包含时禁用自动运行。
例如:
# tc_mytest.rb
require 'test/unit'
class TC_MyTest < Test::Unit::TestCase
def test_succeed
assert(true, 'Assertion was true.')
end
def test_fail
assert(false, 'Assertion was false.')
end
end
无需运行任何寻找锅炉:
% ruby tc_mytest.rb
Loaded suite tc_mytest
Started
F.
Finished in 0.007241 seconds.
1) Failure:
test_fail(TC_MyTest) [tc_mytest.rb:8]:
Assertion was false.
<false> is not true.
2 tests, 2 assertions, 1 failures, 0 errors
答案 4 :(得分:1)
我们可以使用eval(IO.read('filename.rb'),绑定)
实施例: -
def setup
@driver = Selenium::WebDriver.for :chrome
@base_url = "http://stage.checkinforgood.com/"
@driver.manage.timeouts.implicit_wait = 30
@verification_errors = []
end
def teardown
@driver.quit
assert_equal [], @verification_errors
end
require "selenium-webdriver"
require "test/unit"
class C4g < Test::Unit::TestCase
eval(IO.read('setup.rb'), binding)
def test_login
@driver.get "http://stage.checkinforgood.com/"
@driver.find_element(:link, "Sign In").click
@driver.find_element(:id, "user_email").clear
@driver.find_element(:id, "user_email").send_keys "vtr@weboniselab.com"
@driver.find_element(:id, "user_password").clear
@driver.find_element(:id, "user_password").send_keys "test123"
@driver.find_element(:id, "user_submit").click
end
def element_present?(how, what)
@driver.find_element(how, what)
true
rescue Selenium::WebDriver::Error::NoSuchElementError
false
end
def verify(&blk)
yield
rescue Test::Unit::AssertionFailedError => ex
@verification_errors << ex
end
end
现在我们可以跑了,
$ ruby c4g.rb
答案 5 :(得分:-1)
load 'somefile'