ruby-uml 0.2.2
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.
- data/README +61 -0
- data/additional/aspectr-0-4-0.patch +53 -0
- data/bin/sequence_diagram_diff +183 -0
- data/examples/class_diagram_example.rb +69 -0
- data/examples/highlevel_backtracer_example.rb +47 -0
- data/examples/sequence_diagram_example +13 -0
- data/examples/sequence_diagram_generator.rb +41 -0
- data/examples/temperature_new.rb +25 -0
- data/examples/temperature_old.rb +24 -0
- data/lib/uml/class_diagram.rb +208 -0
- data/lib/uml/class_diagram_helper.rb +61 -0
- data/lib/uml/graphviz_helper.rb +90 -0
- data/lib/uml/highlevel_backtracer.rb +73 -0
- data/lib/uml/lowlevel_backtracer.rb +56 -0
- data/lib/uml/method_helper.rb +138 -0
- data/lib/uml/sequence_diagram.rb +157 -0
- data/lib/uml/sequence_diagram_helper.rb +100 -0
- data/tests/ts_all.rb +5 -0
- metadata +75 -0
data/README
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
= Goal
|
2
|
+
|
3
|
+
<tt>ruby-uml</tt> tries to trace different aspects of an existing application,
|
4
|
+
which is intended to provide support for refactorisations by generating
|
5
|
+
UML-graphs.
|
6
|
+
|
7
|
+
<tt>ruby-uml</tt> is able to generate textual representations of the gathered
|
8
|
+
informations.
|
9
|
+
|
10
|
+
These representations include pic- or dot-code that can be converted to images
|
11
|
+
by their corresponding applications.
|
12
|
+
|
13
|
+
At this time <tt>ruby-uml</tt> is able to generate sequence- and class-diagrams.
|
14
|
+
|
15
|
+
As you won't get that much information out of the trace, the resulting dot- or
|
16
|
+
pic-files are intended to be as readable as possible, so that manual
|
17
|
+
corrections/additions are made as easy as possible.
|
18
|
+
|
19
|
+
= Dependencies
|
20
|
+
== Gem dependencies:
|
21
|
+
|
22
|
+
diff-lcs:: >= 1.1.2
|
23
|
+
|
24
|
+
== Other dependencies:
|
25
|
+
|
26
|
+
ruby:: ruby-uml is tested with 1.8.5
|
27
|
+
|
28
|
+
aspectr:: There are three aspectr projects known to me:
|
29
|
+
|
30
|
+
0.3.5:: http://sourceforge.net/projects/aspectr/
|
31
|
+
|
32
|
+
0.3.6:: http://rubyforge.org/projects/aspectr/
|
33
|
+
|
34
|
+
0.4.0:: http://sourceforge.net/projects/aspectr-fork/
|
35
|
+
|
36
|
+
- None of them seem to be maintained anymore.
|
37
|
+
- Everyone provides the functionality needed for ruby-uml.
|
38
|
+
- Everyone seems to have issues on ruby 1.8.5.
|
39
|
+
In the additional folder i provide a patch for 0.4.0
|
40
|
+
which resolved all issues for me on linux and cygwin.
|
41
|
+
|
42
|
+
UMLGraph:: http://www.spinellis.gr/sw/umlgraph/
|
43
|
+
|
44
|
+
ruby-uml uses the sequence.pic file from the UMLGraph Project
|
45
|
+
for generating sequence-diagrams out of pic files.
|
46
|
+
|
47
|
+
ruby-uml is tested with 4.6.
|
48
|
+
|
49
|
+
GNU Plotutils:: http://www.gnu.org/software/plotutils/
|
50
|
+
|
51
|
+
Plotutils 'pic2plot' is used for generating
|
52
|
+
sequence-diagram-images out of pic files.
|
53
|
+
|
54
|
+
ruby-uml is tested with 2.4.1.
|
55
|
+
|
56
|
+
Graphviz:: http://graphviz.org/
|
57
|
+
|
58
|
+
Grapphviz 'dot' command is used to generate class-diagram-images
|
59
|
+
out of dot files.
|
60
|
+
|
61
|
+
ruby-uml is tested with 2.12.
|
@@ -0,0 +1,53 @@
|
|
1
|
+
diff -uNr aspectr-0-4-0-orig/install.rb aspectr-0-4-0/install.rb
|
2
|
+
--- aspectr-0-4-0-orig/install.rb 2007-02-10 15:36:50.229995653 +0100
|
3
|
+
+++ aspectr-0-4-0/install.rb 2007-02-10 16:02:55.456951649 +0100
|
4
|
+
@@ -8,7 +8,7 @@
|
5
|
+
$libdir = File.join(CONFIG["libdir"], "ruby", $version)
|
6
|
+
|
7
|
+
$bindir = CONFIG["bindir"]
|
8
|
+
-$sitedir = CONFIG["sitedir"]
|
9
|
+
+$sitedir = CONFIG["sitelibdir"]
|
10
|
+
|
11
|
+
installdir = "" # Use top-level! Is this good?
|
12
|
+
|
13
|
+
diff -uNr aspectr-0-4-0-orig/lib/aspectr.rb aspectr-0-4-0/lib/aspectr.rb
|
14
|
+
--- aspectr-0-4-0-orig/lib/aspectr.rb 2007-02-10 15:36:50.238995384 +0100
|
15
|
+
+++ aspectr-0-4-0/lib/aspectr.rb 2007-02-10 15:38:00.062553460 +0100
|
16
|
+
@@ -27,7 +27,7 @@
|
17
|
+
AROUND = :AROUND
|
18
|
+
|
19
|
+
def initialize(never_wrap = "^$ ")
|
20
|
+
- @never_wrap = /^__|^send$|^id$|^class$|#{never_wrap}/
|
21
|
+
+ @never_wrap = /^__|^send$|^id$|^object_id$|^class$|#{never_wrap}/
|
22
|
+
end
|
23
|
+
|
24
|
+
def wrap(target, pre, post, *args, &condition)
|
25
|
+
@@ -146,7 +146,7 @@
|
26
|
+
aspect.send(advice, method, *args)
|
27
|
+
rescue Exception
|
28
|
+
a = $!
|
29
|
+
- raise AspectRException, "#{a.type} '#{a}' in advice #{advice}"
|
30
|
+
+ raise AspectRException, "#{a.class} '#{a}' in advice #{advice}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
@@ -201,8 +201,8 @@
|
35
|
+
begin
|
36
|
+
exit_status = nil
|
37
|
+
#{__aop_advice_call_syntax(PRE, method, args)}
|
38
|
+
- exit_status = []
|
39
|
+
- return (exit_status.push(#{__aop_around_advice_call_syntax(method, mangled_method, args)}).last)
|
40
|
+
+ exit_status = #{__aop_around_advice_call_syntax(method, mangled_method, args)}
|
41
|
+
+ return exit_status
|
42
|
+
rescue Exception
|
43
|
+
puts \"got exception: \#{$!}\"
|
44
|
+
exit_status = true
|
45
|
+
@@ -246,7 +246,7 @@
|
46
|
+
end
|
47
|
+
|
48
|
+
def __aop_mangle(method)
|
49
|
+
- "__aop__#{self.object_id}_#{method.object_id}"['-'] = '_'
|
50
|
+
+ "__aop__#{self.object_id}_#{method.object_id}".gsub(/-/, '_')
|
51
|
+
end
|
52
|
+
|
53
|
+
def __aop_alias(new, old, private = true)
|
@@ -0,0 +1,183 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
gem 'diff-lcs'
|
7
|
+
require 'diff/lcs'
|
8
|
+
|
9
|
+
# limitations:
|
10
|
+
# only one command per line
|
11
|
+
|
12
|
+
# finds used object_names in array
|
13
|
+
def find_used_object_names(array)
|
14
|
+
result = []
|
15
|
+
array.each do |line|
|
16
|
+
case line.strip
|
17
|
+
when /^(object|placeholder_object|pobject|actor|complete|active|inactive|delete|lifeline_constraint|lconstraint|lconstraint_below|begin_frame|end_frame|comment|connect_to_comment)[ ]*\(([^\), ]+)/
|
18
|
+
result << $2
|
19
|
+
when /^(message|return_message|rmessage|create_message|cmessage|destroy_message|dmessage)[ ]*\(([^, ]+),([^\), ]+)/
|
20
|
+
result << $2
|
21
|
+
result << $3
|
22
|
+
end
|
23
|
+
end
|
24
|
+
result.uniq
|
25
|
+
end
|
26
|
+
|
27
|
+
# find left- and rightmost object from used in objects
|
28
|
+
# objects is array with order
|
29
|
+
def find_frame_objects(used, objects)
|
30
|
+
indexes = used.collect { |u| objects.index(u) }
|
31
|
+
return objects[indexes.min], objects[indexes.max]
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_lines(array, regexp)
|
35
|
+
result = []
|
36
|
+
array.each do |line|
|
37
|
+
case line.strip
|
38
|
+
when regexp
|
39
|
+
result << line
|
40
|
+
end
|
41
|
+
end
|
42
|
+
result
|
43
|
+
end
|
44
|
+
|
45
|
+
# finds lines with object_definitions
|
46
|
+
def find_object_definitions(array)
|
47
|
+
find_lines array, /^(object|placeholder_object|pobject|actor)[ ]*\(/
|
48
|
+
end
|
49
|
+
|
50
|
+
def find_copies(array)
|
51
|
+
find_lines array, /^copy/
|
52
|
+
end
|
53
|
+
|
54
|
+
def find_object_completions(array)
|
55
|
+
find_lines array, /^complete[ ]*\(/
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_messages(array)
|
59
|
+
find_lines array, /^(message|return_message|rmessage|create_message|cmessage|destroy_message|dmessage|active|inactive|delete|lifeline_constraint|lconstraint|lconstraint_below|begin_frame|end_frame|comment|connect_to_comment)[ ]*\(/
|
60
|
+
end
|
61
|
+
|
62
|
+
def find_variables(array)
|
63
|
+
find_lines array, /=/
|
64
|
+
end
|
65
|
+
|
66
|
+
def read_file(name)
|
67
|
+
IO.readlines(name)
|
68
|
+
end
|
69
|
+
|
70
|
+
def write_file(name, content)
|
71
|
+
open(name, 'w') do |f|
|
72
|
+
f.write content
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def make_frame(content, all, name, label)
|
77
|
+
result = []
|
78
|
+
unless content.empty?
|
79
|
+
bonds = find_frame_objects(find_used_object_names(content), all)
|
80
|
+
name = "#{name}_#{$frame_count += 1}"
|
81
|
+
result << "step();\n"
|
82
|
+
result << "begin_frame(#{bonds.first}, #{name}, \"#{label}\");\n"
|
83
|
+
result << "step();\n"
|
84
|
+
result += content
|
85
|
+
result << "step();\n"
|
86
|
+
result << "end_frame(#{bonds.last}, #{name});\n"
|
87
|
+
result << "step();\n"
|
88
|
+
end
|
89
|
+
result
|
90
|
+
end
|
91
|
+
|
92
|
+
def prepare_messages(left, right, all_objects)
|
93
|
+
result = []
|
94
|
+
eins = []
|
95
|
+
zwei = []
|
96
|
+
Diff::LCS.sdiff(left, right).each do |line|
|
97
|
+
case line.action
|
98
|
+
when '='
|
99
|
+
result << make_frame(eins, all_objects, 'Eins', $options[:left_title])
|
100
|
+
result << make_frame(zwei, all_objects, 'Zwei', $options[:right_title])
|
101
|
+
eins = []
|
102
|
+
zwei = []
|
103
|
+
result << line.old_element
|
104
|
+
when '!'
|
105
|
+
eins << line.old_element
|
106
|
+
zwei << line.new_element
|
107
|
+
when '+'
|
108
|
+
zwei << line.new_element
|
109
|
+
when '-'
|
110
|
+
eins << line.old_element
|
111
|
+
end
|
112
|
+
end
|
113
|
+
result << make_frame(eins, all_objects, 'Eins', $options[:left_title])
|
114
|
+
result << make_frame(zwei, all_objects, 'Zwei', $options[:right_title])
|
115
|
+
result
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
|
120
|
+
|
121
|
+
|
122
|
+
$options = {
|
123
|
+
:left_title => 'before',
|
124
|
+
:right_title => 'after'
|
125
|
+
}
|
126
|
+
|
127
|
+
OptionParser.new do |opts|
|
128
|
+
opts.banner = "Usage: #$0 [options] file1 file2"
|
129
|
+
|
130
|
+
opts.on('--left-title MANDATORY', 'Title of left file') do |string|
|
131
|
+
$options[:left_title] = string
|
132
|
+
end
|
133
|
+
|
134
|
+
opts.on('--right-title MANDATORY', 'Title of right file') do |string|
|
135
|
+
$options[:right_title] = string
|
136
|
+
end
|
137
|
+
|
138
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
139
|
+
puts opts
|
140
|
+
exit
|
141
|
+
end
|
142
|
+
|
143
|
+
end.parse!
|
144
|
+
|
145
|
+
if ARGV.length != 2
|
146
|
+
puts 'Too view Files given'
|
147
|
+
exit
|
148
|
+
end
|
149
|
+
|
150
|
+
$frame_count = 0
|
151
|
+
|
152
|
+
f1 = read_file(ARGV[0])
|
153
|
+
v1 = find_variables(f1)
|
154
|
+
c1 = find_copies(f1)
|
155
|
+
od1 = find_object_definitions(f1)
|
156
|
+
m1 = find_messages(f1)
|
157
|
+
oc1 = find_object_completions(f1)
|
158
|
+
|
159
|
+
f2 = read_file(ARGV[1])
|
160
|
+
v2 = find_variables(f2)
|
161
|
+
c2 = find_copies(f2)
|
162
|
+
od2 = find_object_definitions(f2)
|
163
|
+
m2 = find_messages(f2)
|
164
|
+
oc2 = find_object_completions(f2)
|
165
|
+
|
166
|
+
# so werden neue zeilen von File 2 an erste angehängt
|
167
|
+
c = (c1 + c2).uniq
|
168
|
+
v = (v1 + v2).uniq
|
169
|
+
od = (od1 + od2).uniq
|
170
|
+
o = find_used_object_names(od)
|
171
|
+
oc = (oc1 + oc2).uniq
|
172
|
+
|
173
|
+
result = []
|
174
|
+
result << ".PS\n"
|
175
|
+
result += c
|
176
|
+
result += v
|
177
|
+
result += od
|
178
|
+
result << "step();\n"
|
179
|
+
result += prepare_messages(m1, m2, o)
|
180
|
+
result << "step();\n"
|
181
|
+
result += oc
|
182
|
+
result << "\n.PE"
|
183
|
+
puts result
|
@@ -0,0 +1,69 @@
|
|
1
|
+
$:.push '../lib'
|
2
|
+
|
3
|
+
require 'uml/class_diagram'
|
4
|
+
|
5
|
+
module Automotive
|
6
|
+
|
7
|
+
class Car
|
8
|
+
def drive
|
9
|
+
2
|
10
|
+
end
|
11
|
+
|
12
|
+
def steer
|
13
|
+
'hard'
|
14
|
+
end
|
15
|
+
|
16
|
+
def honk
|
17
|
+
'toot'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module FourWheelDrive
|
22
|
+
def drive
|
23
|
+
4
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module ServoSteering
|
28
|
+
def steer
|
29
|
+
'easy'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Rover < Car
|
34
|
+
include FourWheelDrive
|
35
|
+
include ServoSteering
|
36
|
+
|
37
|
+
def honk
|
38
|
+
'roar'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Mini < Car
|
43
|
+
end
|
44
|
+
|
45
|
+
class Garage < Array
|
46
|
+
def impress_neighbours
|
47
|
+
each do |car|
|
48
|
+
car.honk
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
cd = UML::ClassDiagram.new :show_private_methods => false,
|
56
|
+
:show_protected_methods => false,
|
57
|
+
:show_public_methods => true,
|
58
|
+
:cluster_packages => true,
|
59
|
+
:include => [/^Automotive/]
|
60
|
+
|
61
|
+
cd.include Automotive::Mini
|
62
|
+
|
63
|
+
garage = Automotive::Garage.new
|
64
|
+
garage << Automotive::Rover.new
|
65
|
+
garage.impress_neighbours
|
66
|
+
|
67
|
+
File.open('class_diagram_example.dot', 'w') { |file|
|
68
|
+
file.write cd.to_dot
|
69
|
+
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
$:.push '../lib'
|
2
|
+
|
3
|
+
require 'uml/highlevel_backtracer'
|
4
|
+
require 'temperature'
|
5
|
+
|
6
|
+
ts = TempSensor.new
|
7
|
+
ta = TempAlarm.new(ts)
|
8
|
+
|
9
|
+
class HighlevelTest
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@tracer = UML::HighlevelBacktracer.new
|
13
|
+
@tracer.add_observer self
|
14
|
+
end
|
15
|
+
|
16
|
+
def include(*args)
|
17
|
+
@tracer.include(*args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def update(event, tracer)
|
21
|
+
right = tracer.call_stack[-1]
|
22
|
+
left = tracer.call_stack[-2]
|
23
|
+
case event
|
24
|
+
when :call
|
25
|
+
if left
|
26
|
+
print "#{left[:real_klass]}.#{left[:method_symbol]}"
|
27
|
+
end
|
28
|
+
print " -> "
|
29
|
+
print "#{right[:real_klass]}.#{right[:method_symbol]}"
|
30
|
+
puts "(#{right[:args].join(',')})"
|
31
|
+
when :return
|
32
|
+
if left
|
33
|
+
print "#{left[:real_klass]}.#{left[:method_symbol]}"
|
34
|
+
end
|
35
|
+
print " <- "
|
36
|
+
print "#{right[:real_klass]}.#{right[:method_symbol]}"
|
37
|
+
puts
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
tracer = HighlevelTest.new
|
44
|
+
tracer.include ts, :run, :changed, :notify_observers
|
45
|
+
tracer.include ta, :update
|
46
|
+
|
47
|
+
ts.run(25)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
# generate sequence-diagram with old version of a library
|
4
|
+
ruby -rtemperature_old sequence_diagram_generator.rb >sequence_diagram_example_old.pic
|
5
|
+
pic2plot -Tps sequence_diagram_example_old.pic >sequence_diagram_example_old.ps
|
6
|
+
|
7
|
+
# generate sequence-diagram with new version of a library
|
8
|
+
ruby -rtemperature_new sequence_diagram_generator.rb >sequence_diagram_example_new.pic
|
9
|
+
pic2plot -Tps sequence_diagram_example_new.pic >sequence_diagram_example_new.ps
|
10
|
+
|
11
|
+
# generate combined sequence_diagram out the two
|
12
|
+
../bin/sequence_diagram_diff sequence_diagram_example_old.pic sequence_diagram_example_new.pic >sequence_diagram_example.pic
|
13
|
+
pic2plot -Tps sequence_diagram_example.pic >sequence_diagram_example.ps
|
@@ -0,0 +1,41 @@
|
|
1
|
+
$:.push '../lib'
|
2
|
+
|
3
|
+
require 'uml/sequence_diagram'
|
4
|
+
|
5
|
+
def callback(p)
|
6
|
+
case p
|
7
|
+
when Fixnum, TrueClass, FalseClass
|
8
|
+
p.to_s
|
9
|
+
when NilClass
|
10
|
+
'nil'
|
11
|
+
when Array
|
12
|
+
'[' << p.collect(&method(:callback)).join(',') << ']'
|
13
|
+
else
|
14
|
+
p.class
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
sd = UML::SequenceDiagram.new(
|
19
|
+
:split_inherited => true,
|
20
|
+
:multiple_activation => true,
|
21
|
+
:use_create_message => true,
|
22
|
+
:object_sequence => ['Actor', 'TempSensor', 'Observable', 'Dummy', 'TempAlarm'],
|
23
|
+
:pic_variables => {
|
24
|
+
:boxwid => 0.85,
|
25
|
+
:movewid => 0.85,
|
26
|
+
:maxpswid => 50,
|
27
|
+
:maxpsht => 50,
|
28
|
+
},
|
29
|
+
:parameter_callback => method(:callback)
|
30
|
+
)
|
31
|
+
|
32
|
+
ts = TempSensor.new
|
33
|
+
|
34
|
+
sd.include ts, :run, :changed, :notify_observers, :add_observer
|
35
|
+
sd.include TempAlarm, :update, :initialize
|
36
|
+
sd.include IO, :puts
|
37
|
+
|
38
|
+
ta = TempAlarm.new ts
|
39
|
+
ts.run 27
|
40
|
+
|
41
|
+
puts sd.to_pic
|