xumlidot 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/xumlidot +39 -0
- data/lib/xumlidot.rb +10 -0
- data/lib/xumlidot/diagram.rb +18 -0
- data/lib/xumlidot/diagram/dot.rb +55 -0
- data/lib/xumlidot/diagram/dot/klass.rb +75 -0
- data/lib/xumlidot/diagram/dot/module.rb +11 -0
- data/lib/xumlidot/diagram/shared/naming.rb +19 -0
- data/lib/xumlidot/diagram/xmi.rb +190 -0
- data/lib/xumlidot/diagram/xmi/argument.rb +34 -0
- data/lib/xumlidot/diagram/xmi/attribute.rb +24 -0
- data/lib/xumlidot/diagram/xmi/constant.rb +16 -0
- data/lib/xumlidot/diagram/xmi/id.rb +39 -0
- data/lib/xumlidot/diagram/xmi/klass.rb +133 -0
- data/lib/xumlidot/diagram/xmi/method.rb +38 -0
- data/lib/xumlidot/diagram/xmi/superklass.rb +18 -0
- data/lib/xumlidot/directory_tree.rb +24 -0
- data/lib/xumlidot/options.rb +104 -0
- data/lib/xumlidot/parsers.rb +14 -0
- data/lib/xumlidot/parsers/args.rb +159 -0
- data/lib/xumlidot/parsers/call.rb +92 -0
- data/lib/xumlidot/parsers/file.rb +15 -0
- data/lib/xumlidot/parsers/generic.rb +106 -0
- data/lib/xumlidot/parsers/klass_definition.rb +76 -0
- data/lib/xumlidot/parsers/method_signature.rb +95 -0
- data/lib/xumlidot/parsers/module_definition.rb +55 -0
- data/lib/xumlidot/parsers/scope.rb +54 -0
- data/lib/xumlidot/parsers/stack.rb +96 -0
- data/lib/xumlidot/types.rb +18 -0
- data/lib/xumlidot/types/argument.rb +61 -0
- data/lib/xumlidot/types/arguments.rb +14 -0
- data/lib/xumlidot/types/attribute.rb +34 -0
- data/lib/xumlidot/types/attributes.rb +11 -0
- data/lib/xumlidot/types/constant.rb +49 -0
- data/lib/xumlidot/types/constants.rb +57 -0
- data/lib/xumlidot/types/inherited_module.rb +24 -0
- data/lib/xumlidot/types/instance_methods.rb +10 -0
- data/lib/xumlidot/types/klass.rb +60 -0
- data/lib/xumlidot/types/klass_definition.rb +61 -0
- data/lib/xumlidot/types/klass_methods.rb +10 -0
- data/lib/xumlidot/types/method.rb +10 -0
- data/lib/xumlidot/types/method_signature.rb +64 -0
- data/lib/xumlidot/types/methods.rb +10 -0
- data/lib/xumlidot/types/module.rb +11 -0
- data/lib/xumlidot/types/module_definition.rb +14 -0
- data/lib/xumlidot/types/superklass.rb +32 -0
- data/spec/spec_helper.rb +2 -0
- metadata +175 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d57e014e5a04129a7ca8bfcc0cc8dae7e2b4db752de4bf5ad7643db37251de91
|
4
|
+
data.tar.gz: 5fa6e4b9c7004a2078a12123d5d506bd2d6479271285cd868fb87ba61a67b319
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e3dc0e126cb359825379197fb3537881156436db6b65667c2c96cbfe31468e1ad406e78c485125ee334236cd7c9b018e717fd9a2dc29136b376b544adbbff537
|
7
|
+
data.tar.gz: 5d7f6a45fdd7b7f847581991b67e4dee05b0c3aa518ce176c886d898a19a59e729f0d9f3e122ab09dbbf66e2af62c673f122819fa75e6149c315adb3a5f3997f
|
data/bin/xumlidot
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative '../lib/xumlidot'
|
4
|
+
|
5
|
+
options = ::Xumlidot::Options.parse(ARGV)
|
6
|
+
|
7
|
+
# TODO: user input
|
8
|
+
directories = [ARGV[0]]
|
9
|
+
|
10
|
+
dt = Xumlidot::DirectoryTree.new(directories, options)
|
11
|
+
|
12
|
+
# This is our tree of klasses/modules etc
|
13
|
+
# since we can't assume rails conventions for naming
|
14
|
+
# in a Ruby program.
|
15
|
+
constants = ::Xumlidot::Parsers::Stack::Constants.new
|
16
|
+
|
17
|
+
# TODO: move into directory tree
|
18
|
+
dt.find_all_rb_files do |path|
|
19
|
+
STDERR.puts path if ::Xumlidot::Options.debug == true
|
20
|
+
file_contents = File.read(path)
|
21
|
+
|
22
|
+
@parser = Xumlidot::Parsers::File.new(file_contents, constants)
|
23
|
+
@parser.parse
|
24
|
+
end
|
25
|
+
|
26
|
+
# If a class is inherited from but does not exist in the constants
|
27
|
+
# we will create a dummy class.
|
28
|
+
#
|
29
|
+
# if a class is inherited from, we want to find it using the constant lookup
|
30
|
+
# rules definind in the resolver
|
31
|
+
#
|
32
|
+
# and what ... we want to add a reference too it?
|
33
|
+
#
|
34
|
+
# This REALLY should be done whenever we add a superklass
|
35
|
+
# i.e. yeah, its a hack
|
36
|
+
constants.resolve_inheritance()
|
37
|
+
|
38
|
+
diagram = ::Xumlidot::Diagram.new(constants, options)
|
39
|
+
diagram.draw
|
data/lib/xumlidot.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 'diagram/dot'
|
2
|
+
require_relative 'diagram/xmi'
|
3
|
+
|
4
|
+
module Xumlidot
|
5
|
+
class Diagram
|
6
|
+
def initialize(stack, options)
|
7
|
+
@diagram = if options[:diagram_type] == :dot
|
8
|
+
::Xumlidot::Diagram::Dot.new(stack, options)
|
9
|
+
else
|
10
|
+
::Xumlidot::Diagram::Xmi.new(stack, options)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def draw
|
15
|
+
@diagram.draw
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative 'dot/klass'
|
2
|
+
require_relative 'dot/module'
|
3
|
+
|
4
|
+
module Xumlidot
|
5
|
+
class Diagram
|
6
|
+
class Dot
|
7
|
+
|
8
|
+
def initialize(stack, options = nil)
|
9
|
+
@stack = stack
|
10
|
+
@options = options
|
11
|
+
@output = []
|
12
|
+
end
|
13
|
+
|
14
|
+
# We have to draw any connecting labels AFTER we have drawn the klasses
|
15
|
+
# in order to have something to connect.
|
16
|
+
def draw
|
17
|
+
@output << header
|
18
|
+
@stack.traverse do |klass|
|
19
|
+
klass.extend(::Xumlidot::Diagram::Dot::Klass)
|
20
|
+
@output << klass.draw
|
21
|
+
end
|
22
|
+
|
23
|
+
@stack.traverse do |klass|
|
24
|
+
# Check - i shouldnt need to extend twice
|
25
|
+
klass.extend(::Xumlidot::Diagram::Dot::Klass)
|
26
|
+
|
27
|
+
if @options.inheritance
|
28
|
+
output = klass.draw_inheritence
|
29
|
+
@output << output unless output.nil?
|
30
|
+
end
|
31
|
+
|
32
|
+
if @options.composition
|
33
|
+
klass.constants.each do |k|
|
34
|
+
@output << klass.draw_composition(k)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
@output << footer
|
39
|
+
|
40
|
+
@output.uniq.each { |l| puts l }
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def header
|
46
|
+
%(digraph graph_title {
|
47
|
+
graph[overlap=false, splines=true, bgcolor="white"])
|
48
|
+
end
|
49
|
+
|
50
|
+
def footer
|
51
|
+
'}'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../shared/naming'
|
4
|
+
|
5
|
+
module Xumlidot
|
6
|
+
class Diagram
|
7
|
+
class Dot
|
8
|
+
module Method
|
9
|
+
def to_dot
|
10
|
+
to_s.gsub('{}', '\{\}')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Attribute
|
15
|
+
def to_dot
|
16
|
+
to_s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module Klass
|
21
|
+
include ::Xumlidot::Diagram::Shared::Naming
|
22
|
+
def draw
|
23
|
+
[draw_klass].compact.join('\r\n')
|
24
|
+
end
|
25
|
+
|
26
|
+
def draw_klass
|
27
|
+
label = draw_identifier(@definition)
|
28
|
+
"\"#{draw_identifier(@definition)}\" [shape=Mrecord, label=\"{#{label}|#{draw_methods}}\"]"
|
29
|
+
end
|
30
|
+
|
31
|
+
def draw_composition(composee)
|
32
|
+
"\"#{draw_identifier(composee.definition)}\" -> \"#{draw_identifier(@definition)}\" [label=\"\", arrowhead=\"diamond\", arrowtail=\"diamond\"]"
|
33
|
+
end
|
34
|
+
|
35
|
+
def draw_inheritence
|
36
|
+
return nil if @definition.superklass.empty? && @definition.inherited_modules.empty?
|
37
|
+
|
38
|
+
dot = ""
|
39
|
+
if !@definition.superklass.empty?
|
40
|
+
dot += "\"#{draw_identifier(@definition)}\" -> \"#{draw_ancestor(@definition.superklass)}\" [label=\"\", arrowhead=\"empty\", arrowtail=\"onormal\"]\n"
|
41
|
+
end
|
42
|
+
|
43
|
+
return dot if @definition.inherited_modules.empty?
|
44
|
+
|
45
|
+
@definition.inherited_modules.each do |m|
|
46
|
+
next if m.empty?
|
47
|
+
dot += "\"#{draw_identifier(@definition)}\" -> \"#{draw_ancestor(m)}\" [label=\"#{m.type.to_s}s\", arrowhead=\"empty\", arrowtail=\"onormal\"]\n"
|
48
|
+
end
|
49
|
+
|
50
|
+
dot
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def draw_methods
|
56
|
+
@attributes.each { |a| a.extend(Attribute) }
|
57
|
+
@class_methods.each { |a| a.extend(Method) }
|
58
|
+
@instance_methods.each { |a| a.extend(Method) }
|
59
|
+
|
60
|
+
km = ''
|
61
|
+
km += @attributes.map(&:to_dot).join('\l')
|
62
|
+
km += '\\l' unless km.end_with?('\\l')
|
63
|
+
|
64
|
+
km += @class_methods.map(&:to_dot).join('\l')
|
65
|
+
km += '\\l' unless km.end_with?('\\l')
|
66
|
+
km += '|' if instance_methods.size.positive?
|
67
|
+
|
68
|
+
km += @instance_methods.map(&:to_dot).join('\l')
|
69
|
+
km += '\\l' unless km.end_with?('\\l')
|
70
|
+
km
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Xumlidot
|
2
|
+
class Diagram
|
3
|
+
module Shared
|
4
|
+
module Naming
|
5
|
+
def draw_identifier(d = @definition)
|
6
|
+
[d.name.name, d.name.namespace.reverse].reverse.flatten.join('::')
|
7
|
+
end
|
8
|
+
|
9
|
+
def draw_name
|
10
|
+
@definition.name.name.join('::')
|
11
|
+
end
|
12
|
+
|
13
|
+
def draw_ancestor(d = @definition)
|
14
|
+
[d.name, d.namespace.reverse].reverse.flatten.join('::')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
require_relative 'xmi/argument'
|
2
|
+
require_relative 'xmi/attribute'
|
3
|
+
require_relative 'xmi/klass'
|
4
|
+
require_relative 'xmi/method'
|
5
|
+
|
6
|
+
require 'rexml/document'
|
7
|
+
|
8
|
+
module Xumlidot
|
9
|
+
class Diagram
|
10
|
+
class Xmi
|
11
|
+
|
12
|
+
include REXML
|
13
|
+
|
14
|
+
class Elements < Array
|
15
|
+
|
16
|
+
def initialize(options)
|
17
|
+
super()
|
18
|
+
@options = options
|
19
|
+
end
|
20
|
+
|
21
|
+
def draw
|
22
|
+
xml = header
|
23
|
+
uniq.each { |de| xml += de }
|
24
|
+
xml += yield if block_given?
|
25
|
+
xml += footer
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def header
|
31
|
+
''
|
32
|
+
end
|
33
|
+
|
34
|
+
def footer
|
35
|
+
''
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class ModelAssociationElements < Elements
|
40
|
+
end
|
41
|
+
|
42
|
+
class DiagramAssociationElements < Elements
|
43
|
+
end
|
44
|
+
|
45
|
+
class ModelElements < Elements
|
46
|
+
private
|
47
|
+
|
48
|
+
def header
|
49
|
+
%(<uml:Model name="#{@options[:model_name]}" xmi:id="jcBKnwaFYHEAAQV_">)
|
50
|
+
end
|
51
|
+
|
52
|
+
def footer
|
53
|
+
%(</uml:Model>)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class DiagramElements < Elements
|
58
|
+
private
|
59
|
+
|
60
|
+
def header
|
61
|
+
%(<uml:Diagram diagramType="ClassDiagram" documentation="" name="#{@options[:title]}" toolName="Visual Paradigm" xmi:id="QfZKnwaFYHEAAQj3">
|
62
|
+
<uml:Diagram.element>)
|
63
|
+
end
|
64
|
+
|
65
|
+
def footer
|
66
|
+
%( </uml:Diagram.element>
|
67
|
+
</uml:Diagram>)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# We need to keep track of the ids assigned in the body for each class.
|
72
|
+
class NamespaceToId
|
73
|
+
def initialize
|
74
|
+
@namespace_to_id = {}
|
75
|
+
@id_to_namespace = {}
|
76
|
+
end
|
77
|
+
|
78
|
+
# reverse lookup
|
79
|
+
def has_value?(id)
|
80
|
+
@id_to_namespace[id] != nil
|
81
|
+
end
|
82
|
+
|
83
|
+
def has?(full_namespace)
|
84
|
+
@namespace_to_id[full_namespace] != nil
|
85
|
+
end
|
86
|
+
|
87
|
+
def [](name)
|
88
|
+
@namespace_to_id[name]
|
89
|
+
end
|
90
|
+
|
91
|
+
def []=(name, id)
|
92
|
+
@namespace_to_id[name] = id
|
93
|
+
@id_to_namespace[id] = name
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def initialize(stack, options = nil)
|
98
|
+
@stack = stack
|
99
|
+
@options = options
|
100
|
+
|
101
|
+
@namespace_to_id = NamespaceToId.new
|
102
|
+
|
103
|
+
@model = ModelElements.new(options)
|
104
|
+
@diagram = DiagramElements.new(options)
|
105
|
+
|
106
|
+
@model_associations = ModelAssociationElements.new(options)
|
107
|
+
@diagram_associations = DiagramAssociationElements.new(options)
|
108
|
+
end
|
109
|
+
|
110
|
+
def draw
|
111
|
+
# First traversal we're just assigning ids to everything so that when
|
112
|
+
# we come to draw things we can do a lookup on the ids for any composition
|
113
|
+
# aggregation or subclass relationships.
|
114
|
+
@stack.traverse do |klass|
|
115
|
+
klass.extend(::Xumlidot::Diagram::Xmi::Klass)
|
116
|
+
klass.superklass.extend(::Xumlidot::Diagram::Xmi::Superklass) unless klass.superklass.name.nil?
|
117
|
+
|
118
|
+
klass.definition.inherited_modules.each do |m|
|
119
|
+
next if m.empty?
|
120
|
+
m.extend(::Xumlidot::Diagram::Xmi::Superklass)
|
121
|
+
end #unless klass.definition.inherited_modules.empty?
|
122
|
+
|
123
|
+
next if @namespace_to_id.has?(klass.draw_identifier)
|
124
|
+
@namespace_to_id[klass.draw_identifier] = klass.id
|
125
|
+
end
|
126
|
+
|
127
|
+
# Second traversal we are drawing everything
|
128
|
+
@stack.traverse do |klass|
|
129
|
+
# resolve the superclass id to that of an existing class with an id
|
130
|
+
# we do this in the second loop so that all the classes are present
|
131
|
+
unless klass.superklass.name.nil?
|
132
|
+
id = @namespace_to_id[klass.superklass.draw_identifier]
|
133
|
+
klass.superklass.force_id(id)
|
134
|
+
end
|
135
|
+
|
136
|
+
# do the same with the inherited modules
|
137
|
+
klass.definition.inherited_modules.each do |m|
|
138
|
+
next if m.empty?
|
139
|
+
id = @namespace_to_id[m.draw_identifier]
|
140
|
+
m.force_id(id)
|
141
|
+
end #unless klass.definition.inherited_modules.empty?
|
142
|
+
|
143
|
+
# if we have not added an id for this element it is likely a duplicate
|
144
|
+
# so do not draw it.
|
145
|
+
next unless @namespace_to_id.has_value?(klass.id)
|
146
|
+
|
147
|
+
@model << klass.draw_klass(@options)
|
148
|
+
@diagram << klass.draw_diagram(@options)
|
149
|
+
|
150
|
+
if @options.composition
|
151
|
+
klass.constants.each do |k|
|
152
|
+
# Likewise to the above, unless we have an id for the class
|
153
|
+
# we don't want to draw the relationships
|
154
|
+
next unless @namespace_to_id.has_value?(k.id)
|
155
|
+
@model_associations << klass.draw_model_composition(k)
|
156
|
+
@diagram_associations << klass.draw_diagram_composition(k)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
xml = header
|
162
|
+
xml += @model.draw { @model_associations.draw }
|
163
|
+
xml += @diagram.draw { @diagram_associations.draw }
|
164
|
+
xml += footer
|
165
|
+
|
166
|
+
pretty_print(xml)
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
def header
|
172
|
+
%(<?xml version="1.0" encoding="UTF-8"?>
|
173
|
+
<xmi:XMI xmi:version="2.1" xmlns:uml="http://schema.omg.org/spec/UML/2.0" xmlns:xmi="http://schema.omg.org/spec/XMI/2.1">
|
174
|
+
<xmi:Documentation exporter="Visual Paradigm" exporterVersion="7.0.2">
|
175
|
+
</xmi:Documentation>)
|
176
|
+
end
|
177
|
+
|
178
|
+
def footer
|
179
|
+
%(</xmi:XMI>)
|
180
|
+
end
|
181
|
+
|
182
|
+
def pretty_print(xml)
|
183
|
+
doc = Document.new(xml)
|
184
|
+
formatter = REXML::Formatters::Pretty.new
|
185
|
+
formatter.compact = true
|
186
|
+
formatter.write(doc, $stdout)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'id'
|
4
|
+
|
5
|
+
module Xumlidot
|
6
|
+
class Diagram
|
7
|
+
class Xmi
|
8
|
+
module Argument
|
9
|
+
include ::Xumlidot::Diagram::Xmi::ID
|
10
|
+
|
11
|
+
def draw
|
12
|
+
xmi = "<ownedParameter kind=\"inout\" name=\"#{name_to_xmi}\" xmi:id=\"#{id}\" xmi:type=\"uml:Parameter\">"
|
13
|
+
xmi += "<defaultValue value=\"#{default_to_xmi}\" xmi:id=\"#{default_id}\" xmi:type=\"uml:LiteralString\"/>" if default
|
14
|
+
xmi += "</ownedParameter>"
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def name_to_xmi
|
20
|
+
name.encode(:xml => :text) if name
|
21
|
+
end
|
22
|
+
|
23
|
+
def default_to_xmi
|
24
|
+
return default unless default.is_a?(String)
|
25
|
+
default.encode(:xml => :text)
|
26
|
+
end
|
27
|
+
|
28
|
+
def default_id
|
29
|
+
@_default_id ||= new_id
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|