xumlidot 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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