ruby_to_uml 2.0.0 → 3.0.0

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: 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