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