visualize_ruby 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8489b3403bb2883ed03313d4faf03e784de496e0fce4cf2f7ab77c03144a9a40
4
- data.tar.gz: fcd4b746efc60f1c2dff570b9b946ad38b2521d05e28db91e590868a9a8ec880
3
+ metadata.gz: 284bdd9d3e46c28221780826ae5376b9630aa1d528614a2aa0fc4d577cfabbaa
4
+ data.tar.gz: a56f98ea0a7b26cdb206594bd9e1b08407b4cc3035452a56bd022ab1b6046ce4
5
5
  SHA512:
6
- metadata.gz: 04b3288b13388cbab83b196b2b5c7930419c68cb3129bf29524eabde800e21c6ed754a61673eaad3f96190863373e4600367f9661865945be7821e30e5abe3f0
7
- data.tar.gz: ced7743c62506238228f807c180b666d659c8d02adac2be0e57e8c48f0250cfccd03768f53aede1a07de1965e763b1f358db2bb40b0ecc44692570756ef28874
6
+ metadata.gz: 3637961a42bca6ea8f9c017d23c42b3627fbf24575faa5da775d98fb9f0b09006d0f93fce0bae4cf3ca7bad7cdfd5283e4b1e6dc3e6a82e3bc5df3d1afdcc23e
7
+ data.tar.gz: 0b1660513b29d821a74f402f93aa5383182f50e885777550b577d2e8ca87d8d81a51b6b35ec18c4fc82be01875e7289d3cf1aa2c676f4b31ebf11ca8063729f8
data/.gitignore CHANGED
@@ -9,3 +9,7 @@
9
9
 
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
+ .idea/*
13
+ npm-debug.log
14
+ *.gem
15
+ Gemfile.lock
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # VisualizeRuby
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/visualize_ruby`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ Write a Ruby class and see method interactions. Works with procedural code and bare methods.</span>
4
+ This is experimental project and does not support all types of code.
5
+ If you'd like it to support more types of code please pull request.
4
6
 
5
- TODO: Delete this and the text above, and describe your gem
7
+ [Demo](https://visualize-ruby.herokuapp.com/)
6
8
 
7
9
  ## Installation
8
10
 
@@ -22,7 +24,20 @@ Or install it yourself as:
22
24
 
23
25
  ## Usage
24
26
 
25
- TODO: Write usage instructions here
27
+ ```ruby
28
+ require "visualize_ruby"
29
+
30
+ ruby_code = <<-RUBY
31
+ if hungry?
32
+ eat
33
+ else
34
+ work
35
+ end
36
+ RUBY
37
+
38
+ results = VisualizeRuby::Builder.new(ruby_code: ruby_code).build
39
+ VisualizeRuby::Graphviz.new(*results).to_graph(png: "example.png")
40
+ ```
26
41
 
27
42
  ## Development
28
43
 
@@ -2,8 +2,9 @@ require "visualize_ruby/version"
2
2
  require "visualize_ruby/parser"
3
3
  require "visualize_ruby/node"
4
4
  require "visualize_ruby/edge"
5
+ require "visualize_ruby/graph"
6
+ require "visualize_ruby/builder"
5
7
  require "visualize_ruby/graphviz"
6
8
 
7
9
  module VisualizeRuby
8
- # Your code goes here...
9
10
  end
@@ -0,0 +1,64 @@
1
+ require "dissociated_introspection"
2
+
3
+ module VisualizeRuby
4
+ class Builder
5
+
6
+ def initialize(ruby_code:)
7
+ @ruby_code = ruby_code
8
+ end
9
+
10
+ def build
11
+ ruby_code = DissociatedIntrospection::RubyCode.build_from_source(@ruby_code)
12
+ ruby_class = DissociatedIntrospection::RubyClass.new(ruby_code)
13
+
14
+ if ruby_class.class?
15
+ [build_from_class(ruby_class), { label: ruby_class.class_name }]
16
+ elsif bare_methods?(ruby_code)
17
+ wrap_bare_methods(ruby_code)
18
+ else
19
+ Graph.new(ruby_code: @ruby_code)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def build_from_class(ruby_class)
26
+ graphs = build_graphs_by_method(ruby_class)
27
+
28
+ graphs.each do |graph|
29
+ graphs.each do |sub_graph|
30
+ sub_graph.nodes.each do |node|
31
+ sub_graph.edges << Edge.new(
32
+ nodes: [node, graph.nodes.first],
33
+ dir: :none
34
+ ) if node.name == graph.name
35
+ end
36
+ end
37
+ end
38
+
39
+ graphs
40
+ end
41
+
42
+ def build_graphs_by_method(ruby_class)
43
+ ruby_class.defs.map do |meth|
44
+ Graph.new(ruby_code: meth.body, name: meth.name)
45
+ end
46
+ end
47
+
48
+ def bare_methods?(ruby_code)
49
+ ruby_code.ast.type == :def ||
50
+ ruby_code.ast.type == :begin && ruby_code.ast.children.map(&:type).uniq == [:def]
51
+ end
52
+
53
+ def wrap_bare_methods(ruby_code)
54
+ wrapped_ruby_code = <<~Ruby
55
+ class BareMethodsClass
56
+ #{ruby_code.source}
57
+ end
58
+ Ruby
59
+ di_ruby_code = DissociatedIntrospection::RubyCode.build_from_source(wrapped_ruby_code)
60
+ ruby_class = DissociatedIntrospection::RubyClass.new(di_ruby_code)
61
+ build_from_class(ruby_class)
62
+ end
63
+ end
64
+ end
@@ -5,7 +5,7 @@ module VisualizeRuby
5
5
  :node_b,
6
6
  :dir
7
7
  def initialize(name: nil, nodes:, dir: :forward)
8
- @name = name
8
+ @name = name.to_s if name
9
9
  @node_a = nodes[0]
10
10
  @node_b = nodes[1]
11
11
  @dir = dir
@@ -24,6 +24,8 @@ module VisualizeRuby
24
24
  case dir
25
25
  when :forward
26
26
  "->"
27
+ when :none
28
+ "-"
27
29
  end
28
30
  end
29
31
 
@@ -33,4 +35,4 @@ module VisualizeRuby
33
35
 
34
36
  alias_method :to_s, :inspect
35
37
  end
36
- end
38
+ end
@@ -0,0 +1,18 @@
1
+ module VisualizeRuby
2
+ class Graph
3
+ attr_reader :name, :nodes, :edges
4
+
5
+ def initialize(ruby_code:, name: nil)
6
+ @name = name.to_s
7
+ @nodes, @edges = Parser.new(ruby_code).parse
8
+ end
9
+
10
+ def to_hash
11
+ {
12
+ name: name,
13
+ edges: edges.map(&:to_a),
14
+ nodes: nodes.map(&:to_a),
15
+ }
16
+ end
17
+ end
18
+ end
@@ -2,22 +2,30 @@ require "ruby-graphviz"
2
2
 
3
3
  module VisualizeRuby
4
4
  class Graphviz
5
- attr_reader :nodes, :edges
5
+ attr_reader :graphs, :label
6
6
 
7
- def initialize(nodes, edges)
8
- @nodes = nodes
9
- @edges = edges
7
+ def initialize(graphs, label: nil)
8
+ @graphs = [*graphs]
9
+ @label = label
10
10
  end
11
11
 
12
12
  def to_graph(type: :digraph, **output)
13
- g = GraphViz.new(:G, :type => type)
14
- edges.each do |edge|
15
- node_a = g.add_node(edge.node_a.name.to_s, shape: edge.node_a.shape)
16
- node_b = g.add_node(edge.node_b.name.to_s, shape: edge.node_b.shape)
17
- g.add_edges(node_a, node_b, label: edge.name.to_s, dir: edge.dir)
13
+ g = GraphViz.new(:G, type: type, label: label)
14
+ nodes = {}
15
+ sub_graphs = graphs.reverse.map.with_index do |graph, index|
16
+ sub_graph = g.add_graph("cluster#{index}", **{ label: graph.name }.reject { |_, v| v.nil? })
17
+ graph.nodes.each do |node|
18
+ nodes[node.name] = sub_graph.add_node(node.name, shape: node.shape)
19
+ end
20
+ [graph, sub_graph]
18
21
  end
19
22
 
23
+ sub_graphs.each do |r_graph, g_graph|
24
+ r_graph.edges.each do |edge|
25
+ g_graph.add_edges(nodes[edge.node_a.name], nodes[edge.node_b.name], **{ label: edge.name, dir: edge.dir }.reject { |_, v| v.nil? })
26
+ end
27
+ end
20
28
  g.output(output)
21
29
  end
22
30
  end
23
- end
31
+ end
@@ -3,7 +3,7 @@ module VisualizeRuby
3
3
  attr_reader :name
4
4
  attr_accessor :type
5
5
  def initialize(name:, type: :action)
6
- @name = name
6
+ @name = name.to_s
7
7
  @type = type
8
8
  end
9
9
 
@@ -39,4 +39,4 @@ module VisualizeRuby
39
39
 
40
40
  alias_method :to_s, :inspect
41
41
  end
42
- end
42
+ end
@@ -1,4 +1,14 @@
1
1
  require "parser/current"
2
+ require_relative "parser/or"
3
+ require_relative "parser/and"
4
+ require_relative "parser/ast_helper"
5
+ require_relative "parser/begin"
6
+ require_relative "parser/send"
7
+ require_relative "parser/str"
8
+ require_relative "parser/if"
9
+ require_relative "parser/type"
10
+ require_relative "parser/true"
11
+ require_relative "parser/false"
2
12
 
3
13
  module VisualizeRuby
4
14
  class Parser
@@ -8,136 +18,13 @@ module VisualizeRuby
8
18
  @ast = ast
9
19
  end
10
20
 
21
+ # @return [Array<VisualizeRuby::Node>, Array<VisualizeRuby::Edge>]
11
22
  def parse
12
- merge *Parser.const_get(ast.type.to_s.capitalize).new(ast).parse
23
+ merge *parse_by_type
13
24
 
14
25
  return nodes, edges
15
26
  end
16
-
17
- class If
18
- def initialize(ast)
19
- @ast = ast
20
- end
21
-
22
- def parse
23
- condition, on_true, on_false = @ast.children.to_a
24
- nodes = []
25
- edges = []
26
- on_false_nodes, on_false_edges = Parser.new(ast: on_false).parse
27
- on_false_node = on_false_nodes.first
28
- condition_nodes, condition_edges = Parser.new(ast: condition).parse
29
- condition_nodes.first.type = :decision
30
- nodes.concat(condition_nodes)
31
- edges.concat(condition_edges)
32
- on_true_node = Node.new(name: AstHelper.new(on_true).description)
33
- nodes << on_true_node
34
- nodes.concat(on_false_nodes)
35
-
36
- condition_nodes.each do |condition_node|
37
- edges << Edge.new(name: "true", nodes: [condition_node, on_true_node])
38
- edges << Edge.new(name: "false", nodes: [condition_node, on_false_node])
39
- end
40
- edges.concat(on_false_edges)
41
- return [nodes, edges]
42
- end
43
- end
44
-
45
- class Or
46
- def initialize(ast)
47
- @ast = ast
48
- end
49
-
50
- def parse
51
- last_node = nil
52
- edges = []
53
- nodes = @ast.children.reverse.map do |c|
54
- node = Node.new(name: AstHelper.new(c).description, type: :decision)
55
- edges << Edge.new(name: "OR", nodes: [node, last_node]) if last_node
56
- last_node = node
57
- node
58
- end.reverse
59
- return nodes, edges
60
- end
61
- end
62
-
63
- class And
64
- def initialize(ast)
65
- @ast = ast
66
- end
67
-
68
- def parse
69
- last_node = nil
70
- edges = []
71
- nodes = @ast.children.reverse.map do |c|
72
- node = Node.new(name: c.children.last, type: :decision)
73
- edges << Edge.new(name: "AND", nodes: [node, last_node]) if last_node
74
- last_node = node
75
- node
76
- end.reverse
77
- return nodes, edges
78
- end
79
- end
80
-
81
- class AstHelper
82
- def initialize(ast)
83
- @ast = ast
84
- end
85
-
86
- def description(ast: @ast)
87
- case ast
88
- when Symbol, String
89
- ast
90
- when NilClass
91
- nil
92
- else
93
- ast.children.flat_map do |c|
94
- description(ast: c)
95
- end.reject do |c|
96
- c.nil? || c == :""
97
- end.join(" ")
98
- end
99
- end
100
-
101
- end
102
-
103
- class Begin
104
- def initialize(ast)
105
- @ast = ast
106
- end
107
-
108
- def parse
109
- edges = []
110
- last_node = nil
111
- nodes = @ast.children.to_a.compact.reverse.map do |a|
112
- node = Node.new(name: AstHelper.new(a).description, type: :action)
113
- edges << Edge.new(nodes: [node, last_node]) if last_node
114
- last_node = node
115
- end
116
-
117
- return nodes.reverse, edges.reverse
118
- end
119
- end
120
-
121
- class Send
122
- def initialize(ast)
123
- @ast = ast
124
- end
125
-
126
- def parse
127
- return [Node.new(name: AstHelper.new(@ast).description, type: :action)], []
128
- end
129
- end
130
-
131
- class Str
132
- def initialize(ast)
133
- @ast = ast
134
- end
135
-
136
- def parse
137
- return [Node.new(name: AstHelper.new(@ast).description, type: :action)], []
138
- end
139
- end
140
-
27
+
141
28
  def nodes
142
29
  @nodes ||= []
143
30
  end
@@ -146,9 +33,17 @@ module VisualizeRuby
146
33
  @edges ||= []
147
34
  end
148
35
 
36
+ private
37
+
38
+ def parse_by_type
39
+ Parser.const_get(ast.type.to_s.capitalize).new(ast).parse
40
+ rescue NameError
41
+ Str.new(ast).parse
42
+ end
43
+
149
44
  def merge(nodes, edges)
150
45
  self.nodes.concat(nodes)
151
46
  self.edges.concat(edges)
152
47
  end
153
48
  end
154
- end
49
+ end
@@ -0,0 +1,22 @@
1
+ module VisualizeRuby
2
+ class Parser
3
+ class And
4
+ def initialize(ast)
5
+ @ast = ast
6
+ end
7
+
8
+ # @return [Array<VisualizeRuby::Node>, Array<VisualizeRuby::Edge>]
9
+ def parse
10
+ last_node = nil
11
+ edges = []
12
+ nodes = @ast.children.reverse.map do |c|
13
+ node = Node.new(name: c.children.last, type: :decision)
14
+ edges << Edge.new(name: "AND", nodes: [node, last_node]) if last_node
15
+ last_node = node
16
+ node
17
+ end.reverse
18
+ return nodes, edges
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,27 @@
1
+ module VisualizeRuby
2
+ class Parser
3
+ class AstHelper
4
+ def initialize(ast)
5
+ @ast = ast
6
+ end
7
+
8
+ def description(ast: @ast)
9
+ case ast
10
+ when Symbol, String
11
+ ast
12
+ when NilClass
13
+ nil
14
+
15
+ else
16
+ ast.children.flat_map do |c|
17
+ description(ast: c)
18
+ end.reject do |c|
19
+ c.nil? || c == :""
20
+ end.join(" ")
21
+ end
22
+ rescue NoMethodError
23
+ ast
24
+ end
25
+ end
26
+ end
27
+ end