visualize_ruby 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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