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 +4 -4
- data/bin/ruby_to_uml +1 -3
- data/lib/ruby_to_uml.rb +5 -9
- data/lib/ruby_to_uml/cli.rb +11 -0
- data/lib/ruby_to_uml/nomnoml_dsl_generator.rb +136 -0
- data/lib/ruby_to_uml/path_transformer.rb +32 -0
- data/lib/ruby_to_uml/uml_diagram_renderer/uml_diagram_renderer.rb +27 -0
- data/lib/ruby_to_uml/uml_diagram_renderer/uml_diagram_template.erb +20 -0
- data/lib/ruby_to_uml/uml_info_generator/ast_processor.rb +16 -0
- data/lib/ruby_to_uml/uml_info_generator/info_classes.rb +147 -0
- data/lib/ruby_to_uml/uml_info_generator/processor_helpers.rb +219 -0
- data/lib/ruby_to_uml/uml_info_generator/uml_info_generator.rb +42 -0
- metadata +30 -12
- data/lib/ruby_to_uml/diagram.rb +0 -78
- data/lib/ruby_to_uml/parser_sexp.rb +0 -148
- data/lib/ruby_to_uml/runner.rb +0 -79
- data/lib/ruby_to_uml/uml_class.rb +0 -56
- data/lib/ruby_to_uml/version.rb +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: befc4a48683c7d789764a96b099056651fccb47fb9c32ce7cc0530634bfc5e73
|
|
4
|
+
data.tar.gz: a91d111eac181e1ebdaa72841cb8166230819e6ef53b4ca33273fd67d4ff8fd9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6aa2837bb4861fe1a01e10b83c0848bb453b5ffedcb74f4e02f7fa3683e5fbca10075dc2edc733cd1c8286290bc1307a9b87ff7579672127680f261472f9aeec
|
|
7
|
+
data.tar.gz: 545c66c708ffb85f36f4a32e070204a40c28e9846d26fa8fbed0db40d5781837c89b3c6c9460a68e8d4d3eb4685038c989e9f4da10f0ac7b8c0f5d50b9fdc8ce
|
data/bin/ruby_to_uml
CHANGED
data/lib/ruby_to_uml.rb
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require 'ruby_to_uml/
|
|
4
|
-
require 'ruby_to_uml/
|
|
5
|
-
require 'ruby_to_uml/
|
|
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(/\[/, ']').gsub(/\]/, '[')
|
|
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(/\[/, ']').gsub(/\]/, '[')
|
|
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:
|
|
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-
|
|
11
|
+
date: 2021-06-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
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:
|
|
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/
|
|
52
|
-
- lib/ruby_to_uml/
|
|
53
|
-
- lib/ruby_to_uml/
|
|
54
|
-
- lib/ruby_to_uml/
|
|
55
|
-
- lib/ruby_to_uml/
|
|
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.
|
|
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.
|
|
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: []
|
data/lib/ruby_to_uml/diagram.rb
DELETED
|
@@ -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(/\?/, '?')
|
|
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
|
data/lib/ruby_to_uml/runner.rb
DELETED
|
@@ -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
|