ruby_to_uml 2.0.0 → 3.0.0

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: 3445cfab16eaa84745cd0b367155f28c86beb1a79e93c072aaf6dfe8b599154b
4
- data.tar.gz: 76841acf154c49b453862ffae8525bcae1846a864a0e332cd534a5278e386465
3
+ metadata.gz: befc4a48683c7d789764a96b099056651fccb47fb9c32ce7cc0530634bfc5e73
4
+ data.tar.gz: a91d111eac181e1ebdaa72841cb8166230819e6ef53b4ca33273fd67d4ff8fd9
5
5
  SHA512:
6
- metadata.gz: f7120950f0bf81be765c5758f7d94785c606f8b77cffc8ced2995d80f1aad96bc071bad8334c232c486ee64bc90d85eaaa57524fae0c6683575f29aef1acc549
7
- data.tar.gz: 9cc132f121042b7e817b95b58f84187b4bb796712cb580f827407b3ec8999d51677e53a4410c8a0fd08a3dcc6e934b642cbe7d69f7716379a6250eb9a7c08412
6
+ metadata.gz: 6aa2837bb4861fe1a01e10b83c0848bb453b5ffedcb74f4e02f7fa3683e5fbca10075dc2edc733cd1c8286290bc1307a9b87ff7579672127680f261472f9aeec
7
+ data.tar.gz: 545c66c708ffb85f36f4a32e070204a40c28e9846d26fa8fbed0db40d5781837c89b3c6c9460a68e8d4d3eb4685038c989e9f4da10f0ac7b8c0f5d50b9fdc8ce
data/bin/ruby_to_uml CHANGED
@@ -1,7 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
2
 
4
3
  require 'ruby_to_uml'
5
4
 
6
- runner = RubyToUML::Runner.new ARGV
7
- runner.run
5
+ RubyToUML::CLI.start(ARGV)
data/lib/ruby_to_uml.rb CHANGED
@@ -1,9 +1,5 @@
1
- # frozen_string_literal: true
2
-
3
- require 'ruby_to_uml/version'
4
- require 'ruby_to_uml/runner'
5
- require 'ruby_to_uml/parser_sexp'
6
- require 'ruby_to_uml/uml_class'
7
- require 'ruby_to_uml/diagram'
8
-
9
- RubyToUml = RubyToUML # to create alias for module
1
+ require 'ruby_to_uml/uml_diagram_renderer/uml_diagram_renderer'
2
+ require 'ruby_to_uml/uml_info_generator/uml_info_generator'
3
+ require 'ruby_to_uml/cli'
4
+ require 'ruby_to_uml/nomnoml_dsl_generator'
5
+ require 'ruby_to_uml/path_transformer'
@@ -0,0 +1,11 @@
1
+ module RubyToUML
2
+ module CLI
3
+ def self.start(arguments)
4
+ abort('Usage: ruby_to_uml [source directory or file]') if arguments.empty?
5
+ file_paths = PathTransformer.transform_files_and_or_directories_paths_to_file_paths(arguments)
6
+ uml_info = UMLInfoGenerator.process_files(file_paths)
7
+ dsl = NomnomlDSLGenerator.generate_dsl(uml_info)
8
+ UMLDiagramRenderer.create_diagram(dsl.to_s)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,136 @@
1
+ module RubyToUML
2
+ module NomnomlDSLGenerator
3
+ def self.generate_dsl(uml_info)
4
+ classes = create_class_dsl(uml_info.classes)
5
+ modules = create_modules_dsl(uml_info.modules)
6
+ relationships = create_relationships_dsl(uml_info.relationships)
7
+ NomnomlDSL.new(style, classes, modules, relationships)
8
+ end
9
+
10
+ class << NomnomlDSLGenerator
11
+ private
12
+
13
+ def style
14
+ <<~MSG
15
+ #direction: right
16
+ #zoom: 0.9
17
+
18
+ #font: Roboto
19
+ #fontSize: 20
20
+ #leading: 2
21
+ #padding: 12
22
+
23
+ #fillArrows: true
24
+ #arrowSize: 0.5
25
+ #spacing: 130
26
+
27
+ #lineWidth: 1.5
28
+ #stroke: #33322E
29
+
30
+ #.class: fill=#FEDCC4 title=bold
31
+ #.module: fill=#D9E6FF title=bold
32
+ MSG
33
+ end
34
+
35
+ def create_class_dsl(class_infos)
36
+ class_infos.each_with_object('') do |class_info, dsl_string|
37
+ name = class_info.name
38
+ instance_variables = class_info.instance_variables_info.join('; ')
39
+ instance_methods = instance_methods_dsl(class_info.instance_methods_info)
40
+ singleton_methods = singleton_methods_dsl(class_info.singleton_methods_info)
41
+
42
+ class_dsl = <<~MSG
43
+ [<class> #{name} |
44
+ #{instance_variables} |
45
+ #{instance_methods} |
46
+ #{singleton_methods}
47
+ ]
48
+ MSG
49
+
50
+ dsl_string << class_dsl
51
+ end
52
+ end
53
+
54
+ def create_modules_dsl(module_infos)
55
+ module_infos.each_with_object('') do |module_info, dsl_string|
56
+ name = module_info.name
57
+ instance_methods = instance_methods_dsl(module_info.instance_methods_info)
58
+ singleton_methods = singleton_methods_dsl(module_info.singleton_methods_info)
59
+
60
+ module_dsl = <<~MSG
61
+ [<module> #{name} |
62
+ #{instance_methods} |
63
+ #{singleton_methods}
64
+ ]
65
+ MSG
66
+
67
+ dsl_string << module_dsl
68
+ end
69
+ end
70
+
71
+ def create_relationships_dsl(relationship_infos)
72
+ relationship_infos.each_with_object('') do |relationship_info, dsl_string|
73
+ subject = relationship_info.subject
74
+ verb = relationship_info.verb
75
+ object = relationship_info.object
76
+
77
+ arrow_dictionary = {
78
+ inherits: '<:-',
79
+ includes: '<-',
80
+ extends: '<-',
81
+ prepends: '<-'
82
+ }
83
+
84
+ arrow = arrow_dictionary[verb]
85
+
86
+ relationship_dsl = "[#{subject}] #{verb} #{arrow} [#{object}]\n"
87
+
88
+ dsl_string << relationship_dsl
89
+ end
90
+ end
91
+
92
+ def instance_methods_dsl(method_infos)
93
+ method_infos.map do |method_info|
94
+ instance_method_dsl(method_info)
95
+ end.join('; ').gsub(/\[/, '&rbrack;').gsub(/\]/, '&lbrack;')
96
+ end
97
+
98
+ def singleton_methods_dsl(method_infos)
99
+ method_infos.map do |method_info|
100
+ singleton_method_dsl(method_info)
101
+ end.join('; ').gsub(/\[/, '&rbrack;').gsub(/\]/, '&lbrack;')
102
+ end
103
+
104
+ def instance_method_dsl(method_info)
105
+ type_dictionary = {
106
+ public: '+',
107
+ protected: '#',
108
+ private: '-'
109
+ }
110
+
111
+ type = type_dictionary[method_info.type]
112
+ name = method_info.name
113
+ arguments = method_info.parameters
114
+
115
+ arguments = arguments.empty? ? '' : "(#{arguments.join(', ')})"
116
+
117
+ "#{type}#{name}#{arguments}"
118
+ end
119
+
120
+ def singleton_method_dsl(method_info)
121
+ name = method_info.name
122
+ arguments = method_info.parameters
123
+
124
+ arguments = arguments.empty? ? '' : "(#{arguments.join(', ')})"
125
+
126
+ "self.#{name}#{arguments}"
127
+ end
128
+ end
129
+
130
+ NomnomlDSL = Struct.new(:style, :classes, :modules, :relationships) do
131
+ def to_s
132
+ style + classes + modules + relationships
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,32 @@
1
+ require 'pathname'
2
+
3
+ module RubyToUML
4
+ module PathTransformer
5
+ def self.transform_files_and_or_directories_paths_to_file_paths(paths)
6
+ paths.map { |path| resolve_absolute_path(path) }
7
+ .map { |path| path.file? ? path : resolve_children(path) }
8
+ .flatten
9
+ .map(&:to_s)
10
+ .filter { |path| path.match?(/\.rb$/) }
11
+ .sort
12
+ end
13
+
14
+ class << PathTransformer
15
+ private
16
+
17
+ def resolve_absolute_path(path)
18
+ Pathname.new(File.expand_path(path, Dir.pwd))
19
+ end
20
+
21
+ def resolve_children(path)
22
+ path.children.map do |child|
23
+ if child.file?
24
+ child
25
+ elsif child.directory?
26
+ resolve_children(child)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,27 @@
1
+ require 'erb'
2
+ require 'tilt'
3
+
4
+ module RubyToUML
5
+ module UMLDiagramRenderer
6
+ def self.create_diagram(dsl_string)
7
+ html = render_diagram(dsl_string)
8
+ save_html_file(html)
9
+ end
10
+
11
+ class << UMLDiagramRenderer
12
+ private
13
+
14
+ def render_diagram(dsl_string)
15
+ absolute_path = File.expand_path('uml_diagram_template.erb', __dir__)
16
+ template = Tilt.new(absolute_path)
17
+ template.render(Object.new, dsl_string: dsl_string)
18
+ end
19
+
20
+ def save_html_file(data)
21
+ File.open('uml_class_diagram.html', 'w') do |file|
22
+ file << data
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,20 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Diagram</title>
8
+ </head>
9
+ <body>
10
+ <script src="//unpkg.com/graphre/dist/graphre.js"></script>
11
+ <script src="//unpkg.com/nomnoml/dist/nomnoml.js"></script>
12
+
13
+ <canvas id="target-canvas"></canvas>
14
+ <script>
15
+ var canvas = document.getElementById('target-canvas');
16
+ var source = `<%= dsl_string %>`;
17
+ nomnoml.draw(canvas, source);
18
+ </script>
19
+ </body>
20
+ </html>
@@ -0,0 +1,16 @@
1
+ module RubyToUML
2
+ module UMLInfoGenerator
3
+ class ASTProcessor < Parser::AST::Processor
4
+ include ProcessorHelpers
5
+ include ClassAndRelationshipsProcessor
6
+ include ModuleProcesor
7
+ attr_reader :classes, :modules, :relationships
8
+
9
+ def initialize
10
+ @classes = []
11
+ @modules = []
12
+ @relationships = []
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,147 @@
1
+ module RubyToUML
2
+ module UMLInfoGenerator
3
+ ClassInfo = Struct.new(
4
+ :name,
5
+ :instance_methods_info,
6
+ :singleton_methods_info,
7
+ :instance_variables_info
8
+ )
9
+
10
+ ModuleInfo = Struct.new(
11
+ :name,
12
+ :instance_methods_info,
13
+ :singleton_methods_info
14
+ )
15
+
16
+ RelationshipInfo = Struct.new(:subject, :object, :verb) do
17
+ def to_s
18
+ "#{subject} #{verb} #{object}"
19
+ end
20
+ end
21
+
22
+ InstanceMethodInfo = Struct.new(:name, :type, :parameters) do
23
+ def to_s
24
+ "#{type} #{name}(#{parameters.join(', ')})"
25
+ end
26
+ end
27
+
28
+ SingletonMethodInfo = Struct.new(:name, :parameters) do
29
+ def to_s
30
+ "self.#{name}(#{parameters.join(', ')})"
31
+ end
32
+ end
33
+
34
+ class UMLInfo
35
+ attr_reader :classes, :modules, :relationships
36
+
37
+ def initialize(classes, modules, relationships)
38
+ @classes = classes
39
+ @modules = modules
40
+ @relationships = relationships
41
+ end
42
+
43
+ def class_names
44
+ classes.map(&:name)
45
+ end
46
+
47
+ def module_names
48
+ modules.map(&:name)
49
+ end
50
+
51
+ def class_instance_methods
52
+ classes.map { |class_info| class_info.instance_methods_info.map(&:to_s).join("\n") }
53
+ end
54
+
55
+ def module_instance_methods
56
+ modules.map { |class_info| class_info.instance_methods_info.map(&:to_s).join("\n") }
57
+ end
58
+
59
+ def class_singleton_methods
60
+ classes.map { |class_info| class_info.singleton_methods_info.map(&:to_s).join("\n") }
61
+ end
62
+
63
+ def module_singleton_methods
64
+ modules.map { |class_info| class_info.singleton_methods_info.map(&:to_s).join("\n") }
65
+ end
66
+
67
+ def class_instance_variables
68
+ classes.map(&:instance_variables_info)
69
+ end
70
+
71
+ def relationship_descriptions
72
+ relationships.map(&:to_s)
73
+ end
74
+
75
+ def merge(other_uml_info)
76
+ unique_relationships = (relationships + other_uml_info.relationships).uniq
77
+ unique_classes = merge_classes(classes, other_uml_info.classes)
78
+ unique_modules = merge_modules(modules, other_uml_info.modules)
79
+ UMLInfo.new(unique_classes, unique_modules, unique_relationships)
80
+ end
81
+
82
+ private
83
+
84
+ def merge_classes(classes, other_classes)
85
+ merge_entities(classes, other_classes, &merge_class_attributes(classes))
86
+ end
87
+
88
+ def merge_modules(modules, other_modules)
89
+ merge_entities(modules, other_modules, &merge_module_attributes(modules))
90
+ end
91
+
92
+ def merge_entities(group, other_group, &merge_attributes)
93
+ unique_entities = []
94
+ merged = []
95
+
96
+ distinct_entities = (group + other_group).uniq
97
+
98
+ distinct_entities.each do |entity_1|
99
+ next if merged.include?(entity_1.name)
100
+
101
+ matched_entities = []
102
+ distinct_entities.each do |entity_2|
103
+ matched_entities << entity_2 if entity_1.name == entity_2.name && entity_1.object_id != entity_2.object_id
104
+ end
105
+
106
+ if matched_entities.empty?
107
+ unique_entities << entity_1
108
+ else
109
+ unique_entities << merge_attributes.call([entity_1, *matched_entities])
110
+ merged << entity_1.name
111
+ end
112
+ end
113
+
114
+ unique_entities
115
+ end
116
+
117
+ def merge_class_attributes(classes)
118
+ getters = %i[instance_methods_info singleton_methods_info instance_variables_info]
119
+ merge_attributes(classes, getters)
120
+ end
121
+
122
+ def merge_module_attributes(classes)
123
+ getters = %i[instance_methods_info singleton_methods_info]
124
+ merge_attributes(classes, getters)
125
+ end
126
+
127
+ def merge_attributes(_objects, getters)
128
+ lambda do |objects|
129
+ example_object = objects[0]
130
+
131
+ uniq_attributes = []
132
+ getters.each do |getter|
133
+ attribute = []
134
+ objects.each do |object|
135
+ object.send(getter)
136
+ attribute << object.send(getter)
137
+ end
138
+ uniq_attributes << attribute.flatten.uniq
139
+ end
140
+
141
+ klass = example_object.class
142
+ klass.new(example_object.name, *uniq_attributes)
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,219 @@
1
+ module RubyToUML
2
+ module UMLInfoGenerator
3
+ module ProcessorHelpers
4
+ private
5
+
6
+ def get_class_name(node)
7
+ constant, inherit, children = *node
8
+ get_constant_name(constant)
9
+ end
10
+
11
+ def get_class_body(node)
12
+ body_node_index = 2
13
+ node.children[body_node_index]
14
+ end
15
+
16
+ def get_superclass_name(node)
17
+ constant, inherit, children = *node
18
+ inherit ? get_constant_name(inherit) : nil
19
+ end
20
+
21
+ def get_instance_methods_closure
22
+ type = :public
23
+ lambda do |node, instance_methods_info|
24
+ case node.type
25
+ when :def
26
+ method_name = get_method_name(node)
27
+ args = get_instance_method_args(node)
28
+ instance_methods_info << InstanceMethodInfo.new(method_name, type, args)
29
+ when :send
30
+ method_name = get_send_method(node)
31
+ new_type = get_method_type_change(method_name)
32
+ type = new_type if new_type
33
+ end
34
+ end
35
+ end
36
+
37
+ def get_singleton_methods_closure
38
+ lambda do |node, singleton_methods_info|
39
+ if node.type == :defs
40
+ method_name = get_singleton_method_name(node)
41
+ args = get_singleton_method_args(node)
42
+ singleton_methods_info << SingletonMethodInfo.new(method_name, args)
43
+ end
44
+ end
45
+ end
46
+
47
+ def get_instance_variables_closure
48
+ lambda do |node, instance_variables_info|
49
+ if node.type == :def && get_method_name(node) == :initialize
50
+ method_body_node = BodyNodeWrapper.new(get_method_body_node(node))
51
+ closure = lambda do |node|
52
+ if node.type == :ivar || node.type == :ivasgn
53
+ variable_name = get_instance_variable_name(node)
54
+ instance_variables_info << variable_name
55
+ end
56
+ end
57
+ method_body_node.simple_operation(&closure)
58
+ end
59
+ end
60
+ end
61
+
62
+ def add_inheritence_relationship(class_name, superclass_name)
63
+ relationships << RelationshipInfo.new(class_name, superclass_name, :inherits)
64
+ end
65
+
66
+ def add_module_relationships_if_exist_closure(class_name)
67
+ lambda do |node|
68
+ if node.type == :send
69
+ _, method, module_name = *node
70
+ if %i[include extend prepend].include? method
71
+ verb = "#{method}s".to_sym
72
+ add_module_relationship(class_name, module_name, verb)
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ def add_module_relationship(class_name, arguments, type)
79
+ module_name = get_constant_name(arguments)
80
+ relationships << RelationshipInfo.new(class_name, module_name, type)
81
+ end
82
+
83
+ def get_constant_name(const_node)
84
+ constant_name_index = 1
85
+ const_node.children[constant_name_index]
86
+ end
87
+
88
+ def get_method_name(def_node)
89
+ name_index = 0
90
+ def_node.children[name_index]
91
+ end
92
+
93
+ def get_instance_method_args(def_node)
94
+ args_index = 1
95
+ get_arguments(def_node.children[args_index])
96
+ end
97
+
98
+ def get_send_method(send_node)
99
+ caller, method, arguments = *send_node
100
+ method
101
+ end
102
+
103
+ def get_method_type_change(method_name)
104
+ %i[public private protected].include?(method_name) ? method_name : nil
105
+ end
106
+
107
+ def get_singleton_method_name(defs_node)
108
+ name_index = 1
109
+ defs_node.children[name_index]
110
+ end
111
+
112
+ def get_singleton_method_args(defs_node)
113
+ args_index = 2
114
+ get_arguments(defs_node.children[args_index])
115
+ end
116
+
117
+ def get_arguments(node)
118
+ return [] if node.children.nil?
119
+
120
+ node.children.each_with_object([]) { |node, args| args << node.children[0] }
121
+ end
122
+
123
+ def get_method_body_node(def_node)
124
+ body_index = 2
125
+ def_node.children[body_index]
126
+ end
127
+
128
+ def get_instance_variable_name(node)
129
+ name_index = 0
130
+ node.children[name_index]
131
+ end
132
+
133
+ def get_module_name(node)
134
+ constant, = *node
135
+ get_constant_name(constant)
136
+ end
137
+
138
+ def get_module_body(node)
139
+ _, body = *node
140
+ body
141
+ end
142
+ end
143
+
144
+ module ClassAndRelationshipsProcessor
145
+ def on_class(node)
146
+ class_name = get_class_name(node)
147
+ superclass_name = get_superclass_name(node)
148
+ class_body_node = BodyNodeWrapper.new(get_class_body(node))
149
+ instance_methods_info = class_body_node.array_operation(&get_instance_methods_closure)
150
+ singleton_methods_info = class_body_node.array_operation(&get_singleton_methods_closure)
151
+ instance_variables_info = class_body_node.array_operation(&get_instance_variables_closure)
152
+
153
+ add_inheritence_relationship(class_name, superclass_name) if superclass_name
154
+ class_body_node.simple_operation(&add_module_relationships_if_exist_closure(class_name))
155
+
156
+ add_class(class_name, instance_methods_info, singleton_methods_info, instance_variables_info)
157
+
158
+ node.updated(nil, process_all(node))
159
+ end
160
+
161
+ private
162
+
163
+ def add_class(name, instance_methods_info, singleton_methods_info, instance_variables_info)
164
+ classes << ClassInfo.new(name, instance_methods_info, singleton_methods_info, instance_variables_info)
165
+ end
166
+ end
167
+
168
+ module ModuleProcesor
169
+ def on_module(node)
170
+ module_name = get_module_name(node)
171
+ module_body_node = BodyNodeWrapper.new(get_module_body(node))
172
+ instance_methods_info = module_body_node.array_operation(&get_instance_methods_closure)
173
+ singleton_methods_info = module_body_node.array_operation(&get_singleton_methods_closure)
174
+
175
+ add_module(module_name, instance_methods_info, singleton_methods_info)
176
+
177
+ node.updated(nil, process_all(node))
178
+ end
179
+
180
+ private
181
+
182
+ def add_module(name, instance_methods_info, singleton_methods_info)
183
+ modules << ModuleInfo.new(name, instance_methods_info, singleton_methods_info)
184
+ end
185
+ end
186
+
187
+ class BodyNodeWrapper
188
+ def initialize(body_node)
189
+ @body_node = body_node
190
+ end
191
+
192
+ def array_operation(&operation)
193
+ array = []
194
+ if body_node.nil?
195
+ nil
196
+ elsif body_node.type == :begin
197
+ body_node.children.each { |node| operation.call(node, array) }
198
+ else
199
+ operation.call(body_node, array)
200
+ end
201
+ array
202
+ end
203
+
204
+ def simple_operation(&operation)
205
+ if body_node.nil?
206
+ nil
207
+ elsif body_node.type == :begin
208
+ body_node.children.each { |node| operation.call(node) }
209
+ else
210
+ operation.call(body_node)
211
+ end
212
+ end
213
+
214
+ private
215
+
216
+ attr_reader :body_node
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,42 @@
1
+ require 'parser/current'
2
+
3
+ require 'ruby_to_uml/uml_info_generator/processor_helpers'
4
+ require 'ruby_to_uml/uml_info_generator/ast_processor'
5
+ require 'ruby_to_uml/uml_info_generator/info_classes'
6
+
7
+ module RubyToUML
8
+ module UMLInfoGenerator
9
+ def self.process_files(file_paths)
10
+ uml_infos = file_paths.each_with_object([]) do |file_path, uml_infos|
11
+ uml_infos << process_file(file_path)
12
+ end
13
+
14
+ uml_infos.reduce(:merge)
15
+ end
16
+
17
+ def self.process_multiple_code_snippets(code_snippets)
18
+ uml_infos = code_snippets.each_with_object([]) do |code, uml_infos|
19
+ uml_infos << process_code(code)
20
+ end
21
+
22
+ uml_infos.reduce(:merge)
23
+ end
24
+
25
+ def self.process_file(file_path)
26
+ top_level_node = Parser::CurrentRuby.parse_file(file_path)
27
+ parse_ast_to_uml_info(top_level_node)
28
+ end
29
+
30
+ def self.process_code(code)
31
+ top_level_node = Parser::CurrentRuby.parse(code)
32
+ parse_ast_to_uml_info(top_level_node)
33
+ end
34
+
35
+ def self.parse_ast_to_uml_info(top_level_node)
36
+ processor = ASTProcessor.new
37
+ processor.process(top_level_node)
38
+ UMLInfo.new(processor.classes, processor.modules, processor.relationships)
39
+ end
40
+ private_class_method :parse_ast_to_uml_info
41
+ end
42
+ end
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_to_uml
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Iuliu Pop
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-13 00:00:00.000000000 Z
11
+ date: 2021-06-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: activesupport
14
+ name: erb
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
@@ -25,7 +25,21 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: ruby_parser
28
+ name: parser
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: tilt
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - ">="
@@ -48,11 +62,15 @@ extra_rdoc_files: []
48
62
  files:
49
63
  - bin/ruby_to_uml
50
64
  - lib/ruby_to_uml.rb
51
- - lib/ruby_to_uml/diagram.rb
52
- - lib/ruby_to_uml/parser_sexp.rb
53
- - lib/ruby_to_uml/runner.rb
54
- - lib/ruby_to_uml/uml_class.rb
55
- - lib/ruby_to_uml/version.rb
65
+ - lib/ruby_to_uml/cli.rb
66
+ - lib/ruby_to_uml/nomnoml_dsl_generator.rb
67
+ - lib/ruby_to_uml/path_transformer.rb
68
+ - lib/ruby_to_uml/uml_diagram_renderer/uml_diagram_renderer.rb
69
+ - lib/ruby_to_uml/uml_diagram_renderer/uml_diagram_template.erb
70
+ - lib/ruby_to_uml/uml_info_generator/ast_processor.rb
71
+ - lib/ruby_to_uml/uml_info_generator/info_classes.rb
72
+ - lib/ruby_to_uml/uml_info_generator/processor_helpers.rb
73
+ - lib/ruby_to_uml/uml_info_generator/uml_info_generator.rb
56
74
  homepage: https://github.com/iulspop/ruby_to_uml
57
75
  licenses:
58
76
  - MIT
@@ -66,15 +84,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
66
84
  requirements:
67
85
  - - ">="
68
86
  - !ruby/object:Gem::Version
69
- version: 3.0.0
87
+ version: 3.0.1
70
88
  required_rubygems_version: !ruby/object:Gem::Requirement
71
89
  requirements:
72
90
  - - ">="
73
91
  - !ruby/object:Gem::Version
74
92
  version: '0'
75
93
  requirements: []
76
- rubygems_version: 3.2.3
94
+ rubygems_version: 3.2.15
77
95
  signing_key:
78
96
  specification_version: 4
79
- summary: ruby_to_uml is a tool that creates class diagrams from Ruby code.
97
+ summary: ruby_to_uml is a tool that creates UML class diagrams from Ruby code.
80
98
  test_files: []
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyToUML
4
- # Creates and store a yUML api string for generating diagram
5
- # * type of @statements: 1..* String
6
- class Diagram
7
- attr_accessor :statements
8
-
9
- def initialize
10
- @statements = []
11
- end
12
-
13
- def create(&blk)
14
- instance_eval(&blk)
15
- self
16
- end
17
-
18
- # Adds the given statement to the @diagram array
19
- # Statement can either be a String or an UmlClass
20
- def add(statement)
21
- # TODO: Add some sort of validation
22
-
23
- @statements << statement if statement.is_a? String
24
- if statement.is_a? UmlClass
25
-
26
- @statements << statement.to_s
27
-
28
- statement.children&.each do |child|
29
- @statements << "[#{statement.name}]^[#{child.name}]"
30
- end
31
-
32
- unless statement.associations.empty?
33
- statement.associations.each do |name, type|
34
- next if name =~ /-/
35
-
36
- cardinality = (" #{statement.associations["#{name}-n"]}" if statement.associations["#{name}-n"])
37
- @statements << "[#{statement.name}]-#{name}#{cardinality}>[#{type}]"
38
- end
39
- end
40
-
41
- end
42
- end
43
-
44
- # Sorts the statements array so that
45
- # 1. Class definitions
46
- # 2. Inheritance
47
- # 3. Associations
48
- # Otherwise, strange behavior can happen in the downloaded graph
49
- def compute!
50
- class_def = /^\[[\w;?|=!]*?\]$/
51
- inheritance = /\[(.*?)\]\^\[(.*?)\]/
52
- association = /\[.*\]-.*>\[.*\]/
53
-
54
- @statements.sort! do |x, y|
55
- if x =~ class_def && y =~ inheritance
56
- -1
57
- elsif x =~ class_def && y =~ association
58
- -1
59
- elsif x =~ inheritance && y =~ association
60
- -1
61
- elsif x =~ class_def && y =~ class_def
62
- 0
63
- elsif x =~ inheritance && y =~ inheritance
64
- 0
65
- elsif x =~ association && y =~ association
66
- 0
67
- else
68
- 1
69
- end
70
- end
71
- end
72
-
73
- # returns just the DSL text for diagram
74
- def get_dsl
75
- @statements.join(', ')
76
- end
77
- end
78
- end
@@ -1,148 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'ruby_parser'
4
- require 'pathname'
5
-
6
- module RubyToUML
7
- # Parses files using S-Expressions given by the RubyParser gem
8
- #
9
- # * type of @classes: 0..* UmlClass
10
- #
11
- class ParserSexp
12
- def initialize(path)
13
- @path_to_folder_or_file = path
14
- end
15
-
16
- # Parses the source code of the files in @files
17
- # to build uml classes. Returns an array containing all the
18
- # parsed classes or nil if no ruby file were found in the
19
- # @files array.
20
- def parse_sources!
21
- source_files = nil
22
- if path_to_folder_or_file.match /.rb/
23
- source_files = [path_to_folder_or_file]
24
- else
25
- files = list_child_files_paths(Pathname.new(path_to_folder_or_file))
26
- source_files = files.select { |f| f.match(/\.rb/) }
27
- end
28
-
29
- return nil if source_files.empty?
30
-
31
- all_uml_classes = []
32
- source_files.each do |file|
33
- file_content = File.read(file)
34
- parse_file(file_content).each do |uml_class|
35
- all_uml_classes << uml_class
36
- end
37
- end
38
-
39
- # Removes duplicates between variables and associations in the class
40
- all_uml_classes.each do |uml_class|
41
- uml_class.finalize_uml_class_info(all_uml_classes)
42
- end
43
- end
44
-
45
- # Parse the given string, and return the parsed classes
46
- def parse_file(file_content)
47
- uml_classes = []
48
-
49
- s_exp = RubyParser.new.parse(file_content)
50
-
51
- if sexp_contains_one_class?(s_exp)
52
- uml_classes << parse_class(s_exp)
53
- else
54
- s_exp.each_of_type :class do |a_class|
55
- uml_classes << parse_class(a_class)
56
- end
57
- end
58
-
59
- uml_classes
60
- end
61
-
62
- private
63
-
64
- attr_reader :path_to_folder_or_file
65
-
66
- def list_child_files_paths(path)
67
- path.children.collect do |child|
68
- if child.file?
69
- child
70
- elsif child.directory?
71
- list_child_files_paths(child) + [child]
72
- end
73
- end.select { |x| x }.flatten(1).map(&:to_s)
74
- end
75
-
76
- def sexp_contains_one_class?(s_exp)
77
- s_exp[0] == :class
78
- end
79
-
80
- # Creates a UmlClass from a class s-expression
81
- def parse_class(class_s_exp)
82
- # Checks if the class is in a module
83
- uml_class = is_module?(class_s_exp)
84
-
85
- # Let's start by building the associations of the class
86
- each_association_for class_s_exp do |variable, type, cardinality|
87
- uml_class.associations[variable] = type
88
- uml_class.associations["#{variable}-n"] = cardinality if cardinality
89
- end
90
-
91
- # Searching for a s(:const, :Const) right after the class name, which
92
- # means the class inherits from a parents class, :Const
93
- if class_s_exp[2] && (class_s_exp[2][0] == :const)
94
- classname = recursive_class_name_find class_s_exp[2]
95
- uml_class.parent = classname unless classname.nil?
96
- elsif class_s_exp[2] && (class_s_exp[2][0] == :colon2)
97
- # If the parent class belongs to a module
98
- classname = recursive_class_name_find class_s_exp[2]
99
- uml_class.parent = classname unless classname.nil?
100
- end
101
-
102
- # Looks-up for instance methods
103
- class_s_exp.each_of_type :defn do |instance_method|
104
- # Handle question marks in method names
105
- uml_class.methods << instance_method[1].to_s.gsub(/\?/, '&#63;')
106
-
107
- # Now looking for @variables, inside instance methods
108
- # I'm looking at assignments such as @var = x
109
- instance_method.each_of_type :iasgn do |assignment|
110
- if assignment[1].instance_of?(Symbol) && assignment[1].to_s =~ /@/
111
- variable = assignment[1].to_s.gsub('@', '')
112
- uml_class.variables << variable unless uml_class.variables.include? variable
113
- end
114
- end
115
- end
116
- uml_class
117
- end
118
-
119
- def is_module?(class_s_exp)
120
- if class_s_exp[1].instance_of?(Symbol)
121
- UmlClass.new class_s_exp[1].to_s
122
- else
123
- classname = recursive_class_name_find class_s_exp[1]
124
- UmlClass.new classname unless classname.nil?
125
- end
126
- end
127
-
128
- # Yields the variable, the type and the cardinality for each associations
129
- def each_association_for(a_class)
130
- if comments = a_class.comments
131
- comments.split(/\n/).each do |line|
132
- line.match(/type of @(\w*): ([0-9.*n]* )?([:|\w]*)\b/) do |m|
133
- yield m[1], m[3], m[2]&.chop
134
- end
135
- end
136
- end
137
- end
138
-
139
- def recursive_class_name_find(sexp)
140
- return nil if sexp.nil?
141
- return sexp[1].to_s if sexp[0] == :const || sexp[0] == :colon3
142
-
143
- classname = recursive_class_name_find sexp[1]
144
- classname = '' if classname.nil?
145
- "#{classname}::#{sexp[2]}"
146
- end
147
- end
148
- end
@@ -1,79 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'optparse'
4
- require 'net/http'
5
- require 'erb'
6
-
7
- module RubyToUML
8
- class Runner
9
- def initialize(args)
10
- abort('Usage: ruby_to_uml [source directory]') if args.empty?
11
-
12
- @args = args
13
- @smart_mode = false
14
- @link_mode = false
15
-
16
- parse_options
17
- end
18
-
19
- def run
20
- classes = parse_s_expressions
21
- abort('No ruby files in the directory.') unless classes
22
- classes.each { |c| c.infer_types! classes } if smart_mode
23
-
24
- diagram = create_diagram(classes)
25
-
26
- if link_mode
27
- uri = yuml_uri(diagram)
28
- puts "Link to yUML Diagram: #{uri}"
29
- else
30
- png = download_diagram(yuml_uri(diagram, type: '.png'))
31
- save_file(png, type: '.png')
32
- puts 'Diagram saved in uml.png'
33
- end
34
- end
35
-
36
- private
37
-
38
- attr_reader :args
39
- attr_accessor :smart_mode, :link_mode
40
-
41
- def parse_options
42
- OptionParser.new do |opts|
43
- opts.on('-s', '--smart') { self.smart_mode = true }
44
- opts.on('-l', '--link') { self.link_mode = true }
45
- opts.on('-v', '--version') { puts VERSION }
46
- end.parse!(args)
47
- end
48
-
49
- def parse_s_expressions
50
- path = args[0]
51
- ParserSexp.new(path).parse_sources!
52
- end
53
-
54
- def create_diagram(classes)
55
- diagram = Diagram.new
56
- diagram.create do
57
- classes.each { |c| add c }
58
- end.compute!
59
- diagram
60
- end
61
-
62
- def yuml_uri(diagram, type: '')
63
- scheme = 'https://'
64
- host = 'yuml.me'
65
- path = "/diagram/boring/class/#{ERB::Util.url_encode(diagram.get_dsl)}"
66
- uri = URI(scheme + host + path + type)
67
- end
68
-
69
- def download_diagram(uri)
70
- Net::HTTP.get_response(uri).body
71
- end
72
-
73
- def save_file(data, type: '')
74
- File.open("uml#{type}", 'w') do |file|
75
- file << data
76
- end
77
- end
78
- end
79
- end
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_support/inflector' # To uses String#classify
4
- module RubyToUML
5
- # Represents a parsed uml class
6
- class UmlClass
7
- attr_accessor :name, :variables, :methods, :associations, :parent, :children
8
-
9
- def initialize(name)
10
- @name = name
11
- @variables = []
12
- @methods = []
13
- @associations = {}
14
- end
15
-
16
- def finalize_uml_class_info(classes)
17
- remove_duplicate_var_and_add_associations(classes)
18
- end
19
-
20
- def to_s
21
- '[' + @name + '|' +
22
- @variables.collect { |var| var }.join(';') + '|' +
23
- @methods.collect { |met| met }.join(';') + ']'
24
- end
25
-
26
- # Tries to create an association with the attributes in @variables.
27
- def infer_types!(classes)
28
- class_names = classes.collect(&:name)
29
- @variables.each do |attribute|
30
- next unless class_names.include? attribute.classify
31
-
32
- # A type has match with the attribute's name
33
- @associations[attribute] = attribute.classify
34
-
35
- # If it's a plural, adds a cardinality
36
- @associations["#{attribute}-n"] = '*' if attribute == attribute.pluralize
37
- end
38
- finalize_uml_class_info(classes)
39
- end
40
-
41
- private
42
-
43
- # Deletes variables from the @variables array if they appear
44
- # in an association.
45
- # Sets the @children variable
46
- def remove_duplicate_var_and_add_associations(classes)
47
- @variables -= @associations.keys unless @associations.nil?
48
- @children = classes.select do |c|
49
- if c.parent == @name
50
- c.parent = nil
51
- true
52
- end
53
- end
54
- end
55
- end
56
- end
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyToUML
4
- VERSION = '2.0.0'
5
- end