xumlidot 0.1.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 +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
|