Last active
December 12, 2015 03:09
-
-
Save michaelfeathers/4704833 to your computer and use it in GitHub Desktop.
Generating dot files from Ruby code.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'ap' | |
require 'ripper' | |
CLASS_TEXT = "class A; end; class B; end" | |
CLASS_SEXP = Ripper.sexp(CLASS_TEXT) | |
BIG_TEXT = "class A; def a; @a = b; end; def b; @d = a; @e = a; end; end; module B; def b; end; end" | |
BIG_SEXP = Ripper.sexp(BIG_TEXT) | |
FIELDS_TEXT = "class A; def a; @a = @b; end; end" | |
FIELDS_SEXP = Ripper.sexp(FIELDS_TEXT) | |
class Object | |
def ary?; is_a? Array; end | |
def is_str?; is_a? String; end | |
def nonempty_ary? | |
ary? && (not empty?) | |
end | |
end | |
def sexp_select sexp, symbols | |
(1..(max_nesting_level = 20)).map {|n| sexp.flatten(n) | |
.select(&:nonempty_ary?) | |
.select {|e| symbols.include? e[0] }} | |
.select(&:nonempty_ary?) | |
.flatten(1) | |
end | |
class MethodNode | |
def initialize sexp | |
@expression = sexp | |
end | |
def name | |
@name ||= @expression.flatten.select(&:is_str?).first | |
end | |
def body_without_method_name | |
@expression.flatten.drop(3) | |
end | |
def dependencies names_in_scope | |
names_in_scope & body_without_method_name.select(&:is_str?) | |
end | |
end | |
class ClassNode | |
def initialize sexp | |
@expression = sexp | |
end | |
def field_names | |
@field_names ||= @expression.flatten | |
.each_cons(2) | |
.select {|marker, _| marker == :@ivar } | |
.map {|_,name| name } | |
end | |
def method_names | |
method_nodes.keys | |
end | |
def names | |
@names ||= field_names + method_names | |
end | |
def method_nodes | |
@method_nodes ||= Hash[sexp_select(@expression, [:def]).map {|sexp| MethodNode.new(sexp) } | |
.map {|mn| [mn.name, mn] }] | |
end | |
def dependencies | |
@dependencies ||= method_nodes.map {|n,mn| [n, mn.dependencies(names)] } | |
end | |
end | |
def q string | |
"\"#{string}\"" | |
end | |
def dot_names names | |
" { node [shape=ellipse] #{names.map(&:to_s).map {|s| q(s) }.join(' ')}}" | |
end | |
def dot_method_dependencies source_name, target_names | |
target_names.map {|tn| " #{q(source_name)} -> #{q(tn)}" }.join($/) | |
end | |
def dot_class class_node | |
<<-TEXT | |
digraph { | |
#{dot_names(class_node.names)} | |
#{class_node.dependencies.map {|source, targets| dot_method_dependencies(source, targets)}.join($/)} | |
} | |
TEXT | |
end | |
CS = sexp_select(BIG_SEXP, [:class, :module]).map { |e| ClassNode.new(e) } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment