visualize_ruby 0.11.0 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -1
- data/README.md +12 -13
- data/docs/asset-manifest.json +6 -0
- data/docs/index.html +1 -0
- data/docs/manifest.json +15 -0
- data/docs/service-worker.js +1 -0
- data/docs/static/css/main.8d2a69c3.css +2 -0
- data/docs/static/css/main.8d2a69c3.css.map +1 -0
- data/docs/static/js/main.fcab249e.js +2 -0
- data/docs/static/js/main.fcab249e.js.map +1 -0
- data/lib/visualize_ruby.rb +2 -0
- data/lib/visualize_ruby/builder.rb +37 -19
- data/lib/visualize_ruby/edge.rb +7 -3
- data/lib/visualize_ruby/execution_tracer.rb +14 -33
- data/lib/visualize_ruby/graphviz.rb +23 -16
- data/lib/visualize_ruby/highlight_tracer.rb +89 -24
- data/lib/visualize_ruby/input_coercer.rb +61 -0
- data/lib/visualize_ruby/namable.rb +37 -0
- data/lib/visualize_ruby/node.rb +5 -4
- data/lib/visualize_ruby/parser.rb +1 -0
- data/lib/visualize_ruby/parser/begin.rb +1 -1
- data/lib/visualize_ruby/parser/block.rb +1 -10
- data/lib/visualize_ruby/parser/case.rb +13 -8
- data/lib/visualize_ruby/parser/conditions.rb +5 -6
- data/lib/visualize_ruby/parser/return.rb +12 -0
- data/lib/visualize_ruby/runner.rb +78 -16
- data/lib/visualize_ruby/touchable.rb +13 -6
- data/lib/visualize_ruby/version.rb +1 -1
- data/logo.jpg +0 -0
- data/visualize_ruby.gemspec +4 -3
- metadata +33 -14
- data/CODE_OF_CONDUCT.md +0 -74
@@ -8,9 +8,12 @@ module VisualizeRuby
|
|
8
8
|
# @param [Hash{line: Integer, event: Symbol}] executed_events
|
9
9
|
# @param [Symbol] color
|
10
10
|
def initialize(builder:, executed_events: [], color: OPTIONS.fetch(:color))
|
11
|
-
@builder
|
12
|
-
@executed_events
|
13
|
-
@color
|
11
|
+
@builder = builder
|
12
|
+
@executed_events = executed_events
|
13
|
+
@color = color
|
14
|
+
@last_touched_node = nil
|
15
|
+
@last_touched_edge = nil
|
16
|
+
@step_increment = 0
|
14
17
|
end
|
15
18
|
|
16
19
|
# @return [VisualizeRuby::Builder::Result]
|
@@ -21,43 +24,105 @@ module VisualizeRuby
|
|
21
24
|
|
22
25
|
private
|
23
26
|
|
24
|
-
attr_reader :builder,
|
27
|
+
attr_reader :builder,
|
28
|
+
:color,
|
29
|
+
:executed_events,
|
30
|
+
:last_touched_node,
|
31
|
+
:last_touched_edge
|
25
32
|
|
26
33
|
def mark!
|
27
|
-
|
28
|
-
|
29
|
-
all_edges.detect do |e|
|
30
|
-
if e.node_a.line == a && (e.node_b.line || executed_lines.last) == b # end nodes do not have lineno
|
31
|
-
touch_nodes(e, except: [last_touch])
|
32
|
-
last_touch = e.nodes[1]
|
33
|
-
check_lineno_connections(e)
|
34
|
-
e.touch(color)
|
35
|
-
end
|
36
|
-
end || (last_touch = nil)
|
34
|
+
paired_line_events.to_a.each.with_index do |(a, c)|
|
35
|
+
build_exe_edge(a, c) unless find_edge(a, c)
|
37
36
|
end
|
38
37
|
end
|
39
38
|
|
40
|
-
def
|
41
|
-
|
42
|
-
if
|
43
|
-
|
44
|
-
|
39
|
+
def find_edge(a, c)
|
40
|
+
all_edges.detect do |e|
|
41
|
+
if e.node_a.line == a && (e.node_b.line || executed_lines.last) == c # end nodes do not have lineno
|
42
|
+
check_lineno_connections(e)
|
43
|
+
touch(e)
|
45
44
|
end
|
46
45
|
end
|
47
46
|
end
|
48
47
|
|
49
|
-
def
|
50
|
-
|
48
|
+
def step(increment=true)
|
49
|
+
if increment
|
50
|
+
@step_increment += 1
|
51
|
+
else
|
52
|
+
@step_increment
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def touch(e, except = nil)
|
57
|
+
touched_edge = false
|
58
|
+
e.nodes.reject { |n| n == last_touched_node || n == except }.each do |n|
|
59
|
+
touched_edge = touch_in_order(e, n, touched_edge)
|
60
|
+
end
|
61
|
+
touch_edge(e, touched_edge) # if it didn't happen already
|
62
|
+
end
|
63
|
+
|
64
|
+
def touch_in_order(e, n, touched_edge)
|
65
|
+
if e.node_a == last_touched_node
|
66
|
+
touched_edge = touch_edge(e, touched_edge)
|
67
|
+
n.touch(color, step: step)
|
68
|
+
else
|
69
|
+
n.touch(color, step: step)
|
70
|
+
touched_edge = touch_edge(e, touched_edge)
|
71
|
+
end
|
72
|
+
@last_touched_node = n
|
73
|
+
touched_edge
|
74
|
+
end
|
75
|
+
|
76
|
+
def touch_edge(e, touched_edge)
|
77
|
+
if !touched_edge && e != last_touched_edge
|
78
|
+
e.touch(color, step: step)
|
79
|
+
@last_touched_edge = e
|
80
|
+
touched_edge = true
|
81
|
+
end
|
82
|
+
touched_edge
|
83
|
+
end
|
84
|
+
|
85
|
+
def build_exe_edge(a, c)
|
86
|
+
node_a, graph_a = find_node(line: a, graphs: builder.graphs)
|
87
|
+
node_b, _ = find_node(line: c, graphs: builder.graphs)
|
88
|
+
if node_a && node_b && node_a != node_b
|
89
|
+
touch(exe_edge(graph_a, node_a, node_b))
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def find_node(line:, graphs: builder.graphs)
|
94
|
+
graphs.each do |graph|
|
95
|
+
graph = graph
|
96
|
+
node = graph.nodes.detect { |n| n.line == line }
|
97
|
+
return node, graph if node
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def exe_edge(graph_a, node_a, node_b)
|
102
|
+
if (exe_edge = all_edges.detect { |e| e.node_b == node_b && e.node_a == node_a })
|
103
|
+
exe_edge
|
104
|
+
else
|
105
|
+
new = Edge.new(nodes: [node_a, node_b], type: :execution, style: :dotted)
|
106
|
+
graph_a.edges << new
|
107
|
+
new
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Associated nodes and edges that are on the same line
|
112
|
+
def check_lineno_connections(e)
|
113
|
+
e.nodes.each do |n|
|
114
|
+
touch(n.lineno_connection, e.nodes) if n.lineno_connection
|
115
|
+
end
|
51
116
|
end
|
52
117
|
|
53
118
|
def all_edges
|
54
|
-
|
119
|
+
builder.graphs.flat_map(&:edges)
|
55
120
|
end
|
56
121
|
|
57
122
|
def paired_line_events
|
58
123
|
line_events = executed_events.select { |e| e[:event] == :line } + [executed_events.last]
|
59
|
-
setup_paring(line_events) do |a,
|
60
|
-
[line_events[a][:line], line_events[
|
124
|
+
setup_paring(line_events) do |a, c|
|
125
|
+
[line_events[a][:line], line_events[c][:line]]
|
61
126
|
end
|
62
127
|
end
|
63
128
|
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "tempfile"
|
2
|
+
|
3
|
+
module VisualizeRuby
|
4
|
+
class InputCoercer
|
5
|
+
attr_reader :name, :input
|
6
|
+
|
7
|
+
def initialize(input, name:)
|
8
|
+
@input = input
|
9
|
+
@name = name
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_file
|
13
|
+
@to_file ||= case input
|
14
|
+
when File
|
15
|
+
input
|
16
|
+
when Pathname
|
17
|
+
File.open(input)
|
18
|
+
when String
|
19
|
+
temp_file
|
20
|
+
else
|
21
|
+
raise ArgumentError, "#{name} was given an unknown type #{input.class}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def read
|
26
|
+
case input
|
27
|
+
when String
|
28
|
+
input
|
29
|
+
when File, Tempfile, Pathname
|
30
|
+
to_file.read
|
31
|
+
else
|
32
|
+
raise ArgumentError, "#{name} was given an unknown type #{input.class}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def close!
|
37
|
+
@temp_file.close! if @temp_file
|
38
|
+
end
|
39
|
+
|
40
|
+
def temp_file
|
41
|
+
@temp_file ||= begin
|
42
|
+
file = Tempfile.new(%w(calling_code .rb))
|
43
|
+
file.write(input)
|
44
|
+
file.rewind
|
45
|
+
file
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def load_file
|
50
|
+
load(to_file.path)
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_proc
|
54
|
+
if input.is_a?(Proc)
|
55
|
+
input
|
56
|
+
else
|
57
|
+
Proc.new { load_file }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module VisualizeRuby
|
2
|
+
module Namable
|
3
|
+
DEFAULT_DISPLAYER = -> (attr) do
|
4
|
+
r = attr.values.compact.join(" ")
|
5
|
+
r.empty? ? nil : r
|
6
|
+
end
|
7
|
+
|
8
|
+
def post_initialize(name_displayer: nil, **args)
|
9
|
+
@name_displayer = name_displayer || DEFAULT_DISPLAYER
|
10
|
+
super if defined? super
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
@name_displayer.call(build_name_list)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.included(klass)
|
18
|
+
klass.extend(ClassMethods)
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
def add_names(*names)
|
23
|
+
@name_registry = name_registry.concat(names).uniq
|
24
|
+
end
|
25
|
+
|
26
|
+
def name_registry
|
27
|
+
@name_registry ||= []
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def build_name_list
|
34
|
+
self.class.name_registry.each_with_object({}) { |meth, hash| hash[meth] = __send__(meth) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/visualize_ruby/node.rb
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
module VisualizeRuby
|
2
2
|
class Node
|
3
|
+
include Namable
|
4
|
+
add_names :label
|
3
5
|
include Touchable
|
4
6
|
include Optionalable
|
5
|
-
attr_reader :style, :line
|
7
|
+
attr_reader :style, :line, :label
|
6
8
|
attr_accessor :type, :id, :lineno_connection
|
7
|
-
|
8
9
|
def initialize(name: nil, type: :action, style: :rounded, ast: nil, line: nil, id: nil, **opts)
|
9
|
-
@
|
10
|
+
@label = name || (ast ? AstHelper.new(ast).description : nil)
|
10
11
|
@type = type
|
11
12
|
@style = style
|
12
|
-
@id = id || (ast ? AstHelper.new(ast).id :
|
13
|
+
@id = id || (ast ? AstHelper.new(ast).id : @label)
|
13
14
|
@line = line || AstHelper.new(ast).first_line
|
14
15
|
post_initialize(opts)
|
15
16
|
end
|
@@ -23,7 +23,7 @@ module VisualizeRuby
|
|
23
23
|
edge = _edges.detect { |e| e.node_b == n }
|
24
24
|
edge.nodes[1] = last_node
|
25
25
|
nodes.delete(n)
|
26
|
-
|
26
|
+
elsif n.type != :return # 1. (-> n) 2. (-> n -> last_node)
|
27
27
|
edges << Edge.new(nodes: [n, last_node])
|
28
28
|
end
|
29
29
|
end
|
@@ -18,17 +18,8 @@ module VisualizeRuby
|
|
18
18
|
|
19
19
|
def yield_block(action, item, on_object, color, enumerable)
|
20
20
|
nodes << on_object_node = Node.new(ast: on_object, color: color)
|
21
|
-
nodes << item_node = Node.new(type: :argument, ast: item, color: color) if item
|
22
21
|
nodes << action_node = Node.new(ast: action, color: color)
|
23
|
-
|
24
|
-
if item_node
|
25
|
-
edges << Edge.new(nodes: [on_object_node, item_node], color: color)
|
26
|
-
edges << Edge.new(nodes: [item_node, action_node], color: color)
|
27
|
-
action_node.lineno_connection = edges.last
|
28
|
-
else
|
29
|
-
edges << Edge.new(nodes: [on_object_node, action_node], color: color)
|
30
|
-
end
|
31
|
-
|
22
|
+
edges << Edge.new(nodes: [on_object_node, action_node], name: item, color: color)
|
32
23
|
edges << Edge.new(nodes: [action_node, on_object_node], color: color, name: "↺") if enumerable
|
33
24
|
end
|
34
25
|
|
@@ -3,20 +3,25 @@ module VisualizeRuby
|
|
3
3
|
class Case < Base
|
4
4
|
# @return [Array<VisualizeRuby::Node>, Array<VisualizeRuby::Edge>]
|
5
5
|
def parse
|
6
|
-
|
7
|
-
condition_node = Node.new(ast:
|
6
|
+
ast_condition_node, *ast_when_nodes, ast_else_node = @ast.children
|
7
|
+
condition_node = Node.new(ast: ast_condition_node, type: :decision)
|
8
8
|
nodes << condition_node
|
9
|
-
|
10
|
-
|
9
|
+
|
10
|
+
ast_when_nodes.each do |ast_when_node|
|
11
|
+
edge_name, actions = ast_when_node.children
|
11
12
|
action_nodes, action_edges = Parser.new(ast: actions).parse
|
12
13
|
edges << Edge.new(name: AstHelper.new(edge_name).description, nodes: [condition_node, action_nodes.first])
|
13
14
|
nodes.concat(action_nodes)
|
14
15
|
edges.concat(action_edges)
|
15
16
|
end
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
|
18
|
+
if ast_else_node
|
19
|
+
else_node = Node.new(ast: ast_else_node, type: :action)
|
20
|
+
else_edge = Edge.new(name: "else", nodes: [condition_node, else_node])
|
21
|
+
nodes << else_node
|
22
|
+
edges << else_edge
|
23
|
+
end
|
24
|
+
|
20
25
|
return nodes, edges
|
21
26
|
end
|
22
27
|
end
|
@@ -4,24 +4,23 @@ module VisualizeRuby
|
|
4
4
|
def set_conditions(condition)
|
5
5
|
condition_nodes, condition_edges = Parser.new(ast: condition).parse
|
6
6
|
condition_nodes.first.type = :decision
|
7
|
-
nodes.
|
8
|
-
edges.
|
7
|
+
nodes.unshift(*condition_nodes)
|
8
|
+
edges.unshift(*condition_edges)
|
9
9
|
condition_nodes
|
10
10
|
end
|
11
11
|
|
12
12
|
# @return [Array<VisualizeRuby::Node>, Array<VisualizeRuby::Edge>]
|
13
13
|
def parse
|
14
14
|
last_node = nil
|
15
|
-
|
16
|
-
|
17
|
-
node = set_conditions(c).first
|
15
|
+
@ast.children.reverse.map do |c|
|
16
|
+
node = set_conditions(c).last
|
18
17
|
if last_node
|
19
18
|
edges << Edge.new(name: self.class.name.split("::").last.upcase, nodes: [node, last_node])
|
20
19
|
last_node.lineno_connection = edges.last
|
21
20
|
end
|
22
21
|
last_node = node
|
23
22
|
node
|
24
|
-
end
|
23
|
+
end
|
25
24
|
return nodes, edges
|
26
25
|
end
|
27
26
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module VisualizeRuby
|
2
|
+
class Parser
|
3
|
+
class Return < Base
|
4
|
+
# @return [Array<VisualizeRuby::Node>, Array<VisualizeRuby::Edge>]
|
5
|
+
def parse
|
6
|
+
nodes, edges = Parser.new(ast: @ast.children[0]).parse
|
7
|
+
nodes.last.type = :return
|
8
|
+
return [nodes, edges]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -1,36 +1,98 @@
|
|
1
|
+
require "active_support/core_ext/hash/compact"
|
2
|
+
|
1
3
|
module VisualizeRuby
|
2
4
|
class Runner
|
3
|
-
# @return [String,
|
5
|
+
# @return [String, File, Pathname, Proc] The code that calls the graphed code.
|
4
6
|
attr_accessor :calling_code
|
5
|
-
# @return [String,
|
7
|
+
# @return [String, File, Pathname] The code to be graphed.
|
6
8
|
attr_accessor :ruby_code
|
7
|
-
# @return [Symbol, NilClass]
|
9
|
+
# @return [Symbol, NilClass, String] To output DOT format as a string use String class as value.
|
8
10
|
attr_accessor :output_format
|
9
|
-
# @return [String]
|
11
|
+
# @return [String, Pathname] Add the exe name to select a format ie. png, jpg, svg, dot...
|
10
12
|
attr_accessor :output_path
|
11
|
-
# @param [
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
# @param [TrueClass, FalseClass] in line body when calling methods on self. Looks better when tracing execution.
|
14
|
+
attr_writer :in_line_local_method_calls
|
15
|
+
# @param [TrueClass, FalseClass] Duplicate nodes with the same description are merged to point single node.
|
16
|
+
attr_writer :unique_nodes
|
17
|
+
# @params [Array<String>, NilClass] When a graph has many sub-graphs only include listed.
|
18
|
+
attr_writer :only_graphs
|
19
|
+
# @param [String, File]
|
20
|
+
# @param [Proc]
|
21
|
+
def trace(calling_code = nil, &block)
|
22
|
+
@calling_code = calling_code || block
|
17
23
|
end
|
18
24
|
|
25
|
+
attr_reader :output
|
26
|
+
|
19
27
|
def run!
|
20
|
-
|
21
|
-
|
28
|
+
@run ||= begin
|
29
|
+
highlight_trace
|
30
|
+
@output ||= VisualizeRuby::Graphviz.new(
|
31
|
+
builder,
|
32
|
+
graphs: filter_graphs,
|
33
|
+
unique_nodes: unique_nodes,
|
34
|
+
only_graphs: only_graphs,
|
35
|
+
).to_graph({ path: output_path, format: output_format }.compact)
|
36
|
+
end
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def in_line_local_method_calls
|
41
|
+
@in_line_local_method_calls ||= traced?
|
42
|
+
end
|
43
|
+
|
44
|
+
def options(opts={})
|
45
|
+
opts.each do |key, value|
|
46
|
+
public_send("#{key}=", value)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def graphs
|
51
|
+
graphs = builder.graphs.map(&:name).compact
|
52
|
+
[builder.options.fetch(:label, "default")].concat(graphs)
|
22
53
|
end
|
23
54
|
|
24
55
|
private
|
25
56
|
|
26
57
|
def builder
|
27
|
-
@builder ||= VisualizeRuby::Builder.new(
|
58
|
+
@builder ||= VisualizeRuby::Builder.new(
|
59
|
+
ruby_code: ruby_code,
|
60
|
+
in_line_local_method_calls: in_line_local_method_calls
|
61
|
+
).build
|
62
|
+
end
|
63
|
+
|
64
|
+
def filter_graphs
|
65
|
+
if traced?
|
66
|
+
builder.graphs.select do |g|
|
67
|
+
g.nodes.any? { |n| n.touched > 0 }
|
68
|
+
end
|
69
|
+
else
|
70
|
+
builder.graphs
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def traced?
|
75
|
+
!!calling_code
|
76
|
+
end
|
77
|
+
|
78
|
+
def unique_nodes
|
79
|
+
@unique_nodes ||= true
|
80
|
+
end
|
81
|
+
|
82
|
+
def only_graphs
|
83
|
+
@only_graphs ||= nil
|
28
84
|
end
|
29
85
|
|
30
86
|
def highlight_trace
|
31
|
-
return unless
|
32
|
-
executed_events = VisualizeRuby::ExecutionTracer.new(
|
33
|
-
|
87
|
+
return unless traced?
|
88
|
+
executed_events = VisualizeRuby::ExecutionTracer.new(
|
89
|
+
builder,
|
90
|
+
calling_code: calling_code
|
91
|
+
).trace.executed_events
|
92
|
+
VisualizeRuby::HighlightTracer.new(
|
93
|
+
builder: builder,
|
94
|
+
executed_events: executed_events
|
95
|
+
).highlight!
|
34
96
|
end
|
35
97
|
end
|
36
98
|
end
|