所以这里关于“使用”的含义有些模糊。显然 d 被使用,因为 b.rb (也被使用)D.new
在最后调用。如果我们警告 'used' 表示“代码是从该文件执行的,而不是在 require 过程中”,那么下面的代码就结束了,因为我可以在 ruby 1.9.3 上获得
require 'set'
def analyze(filename)
require_depth = 0
files = Set.new
set_trace_func( lambda do |event, file, line, id, binding, classname|
case event
when 'call'then require_depth += 1 if id == :require && classname == Kernel
when 'return' then require_depth -= 1 if id == :require && classname == Kernel
when 'line'
files << file if require_depth == 0
end
end)
load filename
set_trace_func nil
files.reject {|f| f == __FILE__ || f =~ %r{/lib/ruby/site_ruby}}
end
您可以通过运行来使用它analyse 'a.rb'
(假设所有涉及的文件都在加载路径上)。这样做是使用 ruby 的 set_trace_func 来监听正在发生的事情。第一部分是粗略地尝试忽略调用 require 期间发生的所有事情。然后我们累积每行执行的 ruby 的文件名。最后一行只是清理垃圾(例如补丁所需的 rubygems 文件)。
这实际上不适用于测试示例:当 B.new 运行时,实际上并没有执行 b.rb 中的任何代码行。但是,如果 B(和 C、D 等)具有初始化方法(或某些被调用的代码行),那么您应该得到所需的结果。这是非常简单的东西,可能会被各种各样的东西愚弄。特别是如果您在(比如说)B 上调用一个方法,但该方法的实现不在 b.rb 中(例如,使用 attr_accessor 定义的访问器),则不会记录 b.rb
您可能可以更好地使用调用事件,但我认为 set_trace_func 不能做更多事情。
如果您使用的是 ruby 2.0,那么您可以使用 TracePoint,它是set_trace_func
. 它的语义略有不同,特别是当我们跟踪方法调用时,更容易获取调用它的类,所以
require 'set'
def analyze(filename)
require_depth = 0
files = Set.new
classes_to_files = {}
trace = TracePoint.new(:call, :line, :return, :c_call, :class) do |tp|
case tp.event
when :class
classes_to_files[tp.self] = tp.path
when :call, :c_call then
if tp.method_id == :require && tp.defined_class == Kernel
require_depth += 1
else
if require_depth == 0
if path = classes_to_files[tp.self] || classes_to_files[tp.self.class]
files << path
end
end
end
when :return then require_depth -= 1 if tp.method_id == :require && tp.defined_class == Kernel
when :line
if require_depth == 0
files << tp.path
end
end
end
trace.enable
load filename
trace.disable
files.reject {|f| f == __FILE__ || f =~ %r{/lib/ruby/site_ruby}}
end
确实为测试示例返回 a,b,c。它仍然受到基本限制,即它只知道实际执行的代码。