2

我想检测 Ruby 文件直接引用的所有文件以用于文档目的。阅读基本要求列表是不完整的,因为有些文件是可传递导入的,而另一些文件是导入但从未使用过的。例如:

a.rb:

require 'b'
require 'e'
class A; end
B.new; C.new

b.rb:

require 'c'
require 'd'
class B; end
C.new; D.new

c.rb:
class C; end

(d.rb and e.rb are just like c.rb)

然后我想要的列表a.rbb.rb, c.rb. 没有 D 或 E,因为它们没有被直接引用。希望这是有道理的!

4

1 回答 1

1

所以这里关于“使用”的含义有些模糊。显然 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​​ 的文件名。最后一行只是清理垃圾(例如补丁所需的 ruby​​gems 文件)。

这实际上不适用于测试示例:当 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。它仍然受到基本限制,即它只知道实际执行的代码。

于 2013-04-07T10:32:01.510 回答