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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/bin/xumlidot +39 -0
  3. data/lib/xumlidot.rb +10 -0
  4. data/lib/xumlidot/diagram.rb +18 -0
  5. data/lib/xumlidot/diagram/dot.rb +55 -0
  6. data/lib/xumlidot/diagram/dot/klass.rb +75 -0
  7. data/lib/xumlidot/diagram/dot/module.rb +11 -0
  8. data/lib/xumlidot/diagram/shared/naming.rb +19 -0
  9. data/lib/xumlidot/diagram/xmi.rb +190 -0
  10. data/lib/xumlidot/diagram/xmi/argument.rb +34 -0
  11. data/lib/xumlidot/diagram/xmi/attribute.rb +24 -0
  12. data/lib/xumlidot/diagram/xmi/constant.rb +16 -0
  13. data/lib/xumlidot/diagram/xmi/id.rb +39 -0
  14. data/lib/xumlidot/diagram/xmi/klass.rb +133 -0
  15. data/lib/xumlidot/diagram/xmi/method.rb +38 -0
  16. data/lib/xumlidot/diagram/xmi/superklass.rb +18 -0
  17. data/lib/xumlidot/directory_tree.rb +24 -0
  18. data/lib/xumlidot/options.rb +104 -0
  19. data/lib/xumlidot/parsers.rb +14 -0
  20. data/lib/xumlidot/parsers/args.rb +159 -0
  21. data/lib/xumlidot/parsers/call.rb +92 -0
  22. data/lib/xumlidot/parsers/file.rb +15 -0
  23. data/lib/xumlidot/parsers/generic.rb +106 -0
  24. data/lib/xumlidot/parsers/klass_definition.rb +76 -0
  25. data/lib/xumlidot/parsers/method_signature.rb +95 -0
  26. data/lib/xumlidot/parsers/module_definition.rb +55 -0
  27. data/lib/xumlidot/parsers/scope.rb +54 -0
  28. data/lib/xumlidot/parsers/stack.rb +96 -0
  29. data/lib/xumlidot/types.rb +18 -0
  30. data/lib/xumlidot/types/argument.rb +61 -0
  31. data/lib/xumlidot/types/arguments.rb +14 -0
  32. data/lib/xumlidot/types/attribute.rb +34 -0
  33. data/lib/xumlidot/types/attributes.rb +11 -0
  34. data/lib/xumlidot/types/constant.rb +49 -0
  35. data/lib/xumlidot/types/constants.rb +57 -0
  36. data/lib/xumlidot/types/inherited_module.rb +24 -0
  37. data/lib/xumlidot/types/instance_methods.rb +10 -0
  38. data/lib/xumlidot/types/klass.rb +60 -0
  39. data/lib/xumlidot/types/klass_definition.rb +61 -0
  40. data/lib/xumlidot/types/klass_methods.rb +10 -0
  41. data/lib/xumlidot/types/method.rb +10 -0
  42. data/lib/xumlidot/types/method_signature.rb +64 -0
  43. data/lib/xumlidot/types/methods.rb +10 -0
  44. data/lib/xumlidot/types/module.rb +11 -0
  45. data/lib/xumlidot/types/module_definition.rb +14 -0
  46. data/lib/xumlidot/types/superklass.rb +32 -0
  47. data/spec/spec_helper.rb +2 -0
  48. metadata +175 -0
@@ -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
@@ -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
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'fileutils'
3
+
4
+ require_relative 'xumlidot/directory_tree'
5
+ require_relative 'xumlidot/parsers'
6
+ require_relative 'xumlidot/diagram'
7
+ require_relative 'xumlidot/options'
8
+
9
+ module Xumlidot
10
+ end
@@ -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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xumlidot
4
+ class Diagram
5
+ class Dot
6
+ module Module
7
+ include ::Xumlidot::Diagram::Dot::Klass
8
+ end
9
+ end
10
+ end
11
+ 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