2013-04-06 4 views
2

문서 목적으로 Ruby 파일이 직접 참조하는 모든 파일을 검색하려고합니다. 일부 파일은 전이 적으로 가져오고 다른 파일은 가져 오지만 사용하지 않기 때문에 기본 필수 목록을 읽는 것은 완료되지 않았습니다. 예를 들어 :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는 직접 참조되지 않았으므로 없습니다. 희망이 이해가!

+0

'e.rb'를 제외하고'c.rb'를 포함하는 것은 해결해야 할 어려운 문제입니다 ("정지 문제"와 동등 할 수도 있습니다, 확실하지 않습니다). 루비 코드를 완전히 파싱하고 가능한 모든 코드 경로를 이해해야합니다. 단순한 경량 의존성 (예 : 클래스 정의 및 기본 인스턴스 생성) 만 찾으면이 90 %를 정확하게 만들 수 있습니다. 따라서 90 %의 정확도가 수용 가능합니까? –

+0

@NeilSlater 여기에 가입 할 수 있습니까? - http://chat.stackoverflow.com/rooms/27184/ruby-conceptual? –

+0

@NeilSlater 예 90 % 또는 그 이하도 OK입니다. – alexloh

답변

1

여기에는 '사용 된'의미와 관련하여 다소 모호한 부분이 있습니다. b.rb (역시 사용되는)가 D.new을 결국 호출하기 때문에 분명히 d가 사용됩니다. 우리가 '사용'을 의미하는주의 경우 "코드 중이 아닌, 해당 파일에서 실행 된 과정을 필요로"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' (관련된 모든 파일이로드 경로에 있다고 가정)을 실행하십시오. 이것이하는 일은 루비의 set_trace_func를 사용하여 현재 일어나고있는 일을 경청합니다. 첫 번째 부분은 요구하는 동안 발생하는 모든 것을 무시하려는 조잡한 시도입니다. 그런 다음 실행 된 루비의 모든 행의 파일 이름을 누적합니다. 마지막 줄은 정크를 제거하는 것입니다 (예 : 패치가 필요로하는 rubygems 파일).

B.new가 실행될 때 b.rb의 코드 행이 실제로 실행되지 않습니다. 테스트 예제에서는 실제로 작동하지 않습니다. 그러나 B (와 C, D 등)가 초기화 메소드 (또는 호출되는 코드 라인)를 가지고 있다면 원하는 결과를 얻어야합니다. 그것은 꽤 단순한 물건이고 모든 재료에 의해 바보가 될 수 있습니다. 특히 B에서 메서드를 호출했지만 해당 메서드의 구현이 b.rb (예 : attr_accessor로 정의 된 접근 자)가 아니면 b.rb가 기록되지 않습니다.

더 나은 호출 이벤트를 사용하지만 훨씬 더 set_trace_func 함께 할 수 있다고 생각하지 않습니다.

ruby ​​2.0을 사용하는 경우 set_trace_func을 대신하는 TracePoint를 사용할 수 있습니다. 우리는 방법은 테스트 예를 들어 A, B, C를 반환하지 그래서

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 

에 호출 된 클래스를 얻기 위해 그것의 쉽게 전화를 추적 할 때 특히 약간 다른 의미를 가지고 있습니다. 실제로 실행되는 코드에 대해서만 알고 있다는 근본적인 제한이 있습니다.

+0

감사! 그 아이디어는 효과가 있지만 나는 그것을 조금씩 수정했다 (1.9 버전). (더 큰) 깊이가 1 인 한, 어떤 사건에 대해서도 파일을 추가했다. 즉, 추가 후 깊이를 호출하고, 빼기 전에 반환하고, 깊이 = 1 일 때 호출한다. . 나는 아직도 그것을 지껄이다. – alexloh

+0

당신이 지적한 것처럼 동적 분석은 까다 롭습니다 만 포괄적 인 테스트 스위트가 있어야 작동 할 수 있습니다. – alexloh