Skip to content

Instantly share code, notes, and snippets.

@hiroara
Last active December 26, 2015 23:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hiroara/7228642 to your computer and use it in GitHub Desktop.
Save hiroara/7228642 to your computer and use it in GitHub Desktop.
Rubyのメソッド実行をキャプチャしてツリー構造で返すモジュール
module Capturable
EXCLUDE_METHODS = [:class, :send, :respond_to?, :capture_methods, :__send__, :__id__]
def self.included(base)
base.class_eval do
def self.capture_methods options={}
method_list = options[:method_list]
exclude_methods = options[:exclude_methods] || []
exclude_regexp = options[:exclude_regexp] || /\A_/
source_root = case options[:source_root]
when :rails then /\A#{Rails.root.to_s}/
else options[:source_root]
end
if method_list.nil?
method_list = (public_instance_methods - EXCLUDE_METHODS).reject do |method_sym|
method_sym.to_s =~ exclude_regexp
end
method_list = method_list - superclass.public_instance_methods unless options[:superclass_methods]
end
(method_list - exclude_methods).each do |method_sym|
orig_method = public_instance_method method_sym
next if source_root && (!orig_method.source_location || !orig_method.source_location.first =~ source_root)
define_method method_sym, -> (*args, &block) do
Capturable.call_method self.class, method_sym, false do
orig_method.bind(self).call *args, &block
end
end
end
class_method_list = options[:class_method_list]
exclude_class_methods = options[:exclude_class_methods] || []
if class_method_list.nil?
class_method_list = (public_methods - EXCLUDE_METHODS).reject do |method_sym|
method_sym.to_s =~ exclude_regexp
end
method_list = method_list - superclass.public_methods unless options[:superclass_methods]
end
(class_method_list - exclude_class_methods).map do |method_sym|
orig_method = public_method method_sym
next if source_root && (!orig_method.source_location || !orig_method.source_location.first =~ source_root)
define_singleton_method method_sym, -> (*args, &block) do
Capturable.call_method self, method_sym, true do
orig_method.call *args, &block
end
end
end
end
end
end
module_function
def capture &block
@method_head = @tree_root = MethodTree.new self, :capture, true
begin
block.call
ensure
tree_root = @tree_root
@tree_root = @method_head = nil
end
tree_root.to_h
end
def in_capture_block?
!!@method_head
end
def call_method clazz, method_sym, class_method, &block
Capturable.visit_method MethodTree.new(clazz, method_sym, class_method) if Capturable.in_capture_block?
result = block.call
Capturable.leave_method if Capturable.in_capture_block?
result
end
def visit_method method_tree
@method_head = @method_head.append_child method_tree
end
def leave_method
@method_head = @method_head.parent
end
class MethodTree
attr_accessor :name, :parent, :children
def initialize clazz, name, class_method
@clazz = clazz
@name = name
@children = []
@class_method = class_method
end
def append_child child
@children << child
child.parent = self
child
end
def to_h
signature = @class_method ? "#{@clazz}.#{@name}" : "#{@clazz}##{@name}"
{ signature => @children.map { |child| child.to_h } }
end
def to_s
to_h.to_s
end
end
end
require './capturable'
class Foo
include Capturable
def foo
puts "foo!"
Hoge.new.hoge2
self.class.class_foo
end
def self.class_foo
puts "class_foo!"
end
capture_methods exclude_class_methods: [:new]
end
class Hoge
include Capturable
def hoge; Foo.new.foo; end
def hoge2; puts "hoge2!"; end
capture_methods exclude_class_methods: [:new]
end
tree = Capturable.capture do
Hoge.new.hoge
end
puts tree
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment