如何在Ruby中查找所有引用的文件

时间:2013-04-06 18:25:08

标签: ruby import requires

我想检测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.rb获取的列表是b.rb, c.rb。没有D或E,因为它们没有被直接引用。希望这是有道理的!

1 个答案:

答案 0 :(得分:1)

所以这里有一些关于使用''手段。显然使用了d,因为b.rb(也是使用的)最后调用D.new。如果我们发出警告,请使用'表示"代码是从该文件执行的,而不是在需求过程中#34;然后下面的代码是关闭的,因为我可以得到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。它仍然受到基本的限制,它只知道实际执行的代码。