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
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'observer'
|
3
|
+
|
4
|
+
module UML
|
5
|
+
|
6
|
+
class LowlevelBacktracer
|
7
|
+
include Singleton
|
8
|
+
include Observable
|
9
|
+
|
10
|
+
attr_reader :call_stack
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@call_stack = []
|
14
|
+
set_trace_func method(:tracer).to_proc
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def tracer(event, filename, linenumber, method_symbol, binding, klass)
|
20
|
+
begin
|
21
|
+
|
22
|
+
case event
|
23
|
+
when 'call', 'c-call'
|
24
|
+
# args describe the endpoint of call
|
25
|
+
|
26
|
+
actual_information = {
|
27
|
+
:filename => filename,
|
28
|
+
:linenumber => linenumber,
|
29
|
+
:method_symbol => method_symbol,
|
30
|
+
:binding => binding,
|
31
|
+
:klass => klass,
|
32
|
+
}
|
33
|
+
@call_stack.push actual_information
|
34
|
+
|
35
|
+
changed
|
36
|
+
notify_observers :call, self
|
37
|
+
|
38
|
+
when 'return', 'c-return'
|
39
|
+
# args describe the startingpoint of return,
|
40
|
+
# should be equal to last stack-element
|
41
|
+
|
42
|
+
changed
|
43
|
+
notify_observers :return, self
|
44
|
+
|
45
|
+
@call_stack.pop
|
46
|
+
|
47
|
+
end
|
48
|
+
rescue Exception => exception
|
49
|
+
puts exception
|
50
|
+
puts exception.backtrace
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end # class Backtracer
|
56
|
+
end # module UML
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module UML
|
2
|
+
|
3
|
+
# Contains helper methods to work with receivers and methods.
|
4
|
+
#
|
5
|
+
# These methods would better match in class Method, if it had access to its receiver.
|
6
|
+
# (Waiting for Ruby v1.9)
|
7
|
+
module MethodHelper
|
8
|
+
|
9
|
+
# :section: Receiver
|
10
|
+
|
11
|
+
# Find out, if +receiver+ is an instance of some class.
|
12
|
+
#
|
13
|
+
# :call-seq:
|
14
|
+
# is_receiver_instance_of_class?(receiver) -> true or false
|
15
|
+
#
|
16
|
+
def is_receiver_instance_of_class?(receiver)
|
17
|
+
not(is_receiver_class?(receiver) or is_receiver_module?(receiver))
|
18
|
+
end
|
19
|
+
|
20
|
+
# Find out, if +receiver is a Class
|
21
|
+
#
|
22
|
+
# :call-seq:
|
23
|
+
# is_receiver_class?(receiver) -> true or false
|
24
|
+
#
|
25
|
+
def is_receiver_class?(receiver)
|
26
|
+
receiver.instance_of? Class
|
27
|
+
end
|
28
|
+
|
29
|
+
# Find out, if +receiver is a Module
|
30
|
+
#
|
31
|
+
# :call-seq:
|
32
|
+
# is_receiver_module?(receiver) -> true | false
|
33
|
+
#
|
34
|
+
def is_receiver_module?(receiver)
|
35
|
+
receiver.instance_of? Module
|
36
|
+
end
|
37
|
+
|
38
|
+
# Find real receiver of method.
|
39
|
+
#
|
40
|
+
# Searches included and extended Modules and Superclasses.
|
41
|
+
#
|
42
|
+
# :call-seq:
|
43
|
+
# get_real_receiver_of_method(top_receiver, method_symbol) -> class | module | nil
|
44
|
+
#
|
45
|
+
def get_real_receiver_of_method(*args)
|
46
|
+
klass, message = prepare_args(*args)
|
47
|
+
|
48
|
+
# As Module::instance_methods just returns public instance methods, all three
|
49
|
+
# visibilities have to be tested
|
50
|
+
|
51
|
+
# test all included modules in topmost class
|
52
|
+
klass.included_modules.each do |mod|
|
53
|
+
return mod if mod.singleton_methods(false).include? message or
|
54
|
+
mod.public_instance_methods(false).include? message or
|
55
|
+
mod.protected_instance_methods(false).include? message or
|
56
|
+
mod.private_instance_methods(false).include? message
|
57
|
+
end
|
58
|
+
|
59
|
+
# also check extended modules
|
60
|
+
class << klass; included_modules; end.each do |mod|
|
61
|
+
return mod if mod.singleton_methods(false).include? message or
|
62
|
+
mod.public_instance_methods(false).include? message or
|
63
|
+
mod.protected_instance_methods(false).include? message or
|
64
|
+
mod.private_instance_methods(false).include? message
|
65
|
+
end
|
66
|
+
|
67
|
+
# then check class and superclasses
|
68
|
+
while klass
|
69
|
+
return klass if klass.singleton_methods(false).include? message or
|
70
|
+
klass.public_instance_methods(false).include? message or
|
71
|
+
klass.protected_instance_methods(false).include? message or
|
72
|
+
klass.private_instance_methods(false).include? message
|
73
|
+
|
74
|
+
klass = klass.respond_to?(:superclass) ? klass.superclass : nil
|
75
|
+
end
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
|
79
|
+
# :section: Method
|
80
|
+
|
81
|
+
# Find out if method is singleton
|
82
|
+
#
|
83
|
+
# :call-seq:
|
84
|
+
# is_method_singleton?(top_receiver, method_symbol) -> true | false
|
85
|
+
#
|
86
|
+
def is_method_singleton?(*args)
|
87
|
+
klass, message = prepare_args(*args)
|
88
|
+
klass.singleton_methods.include? message
|
89
|
+
end
|
90
|
+
|
91
|
+
# Get visibility of an instance method
|
92
|
+
#
|
93
|
+
# :call-seq:
|
94
|
+
# get_visibility_of_instance_method(top_receiver, method_symbol) -> :public | :protected | :private
|
95
|
+
#
|
96
|
+
def get_visibility_of_instance_method(*args)
|
97
|
+
klass, message = prepare_args(*args)
|
98
|
+
case
|
99
|
+
when klass.public_instance_methods.include?(message) : :public
|
100
|
+
when klass.protected_instance_methods.include?(message) : :protected
|
101
|
+
when klass.private_instance_methods.include?(message) : :private
|
102
|
+
else nil
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Get visibility of a singleton method
|
107
|
+
#
|
108
|
+
# :call-seq:
|
109
|
+
# get_visibility_of_singleton_method(top_receiver, method_symbol) -> :public | :protected | :private
|
110
|
+
#
|
111
|
+
def get_visibility_of_singleton_method(*args)
|
112
|
+
klass, message = prepare_args(*args)
|
113
|
+
case
|
114
|
+
when klass.public_methods.include?(message) : :public
|
115
|
+
when klass.protected_methods.include?(message) : :protected
|
116
|
+
when klass.private_methods.include?(message) : :private
|
117
|
+
else nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Get visibility of a method.
|
122
|
+
#
|
123
|
+
# :call-seq:
|
124
|
+
# get_visibility_of_method(top_receiver, method_symbol) -> :public | :protected | :private
|
125
|
+
#
|
126
|
+
def get_visibility_of_method(*args)
|
127
|
+
is_method_singleton?(*args)? get_visibility_of_singleton_method(*args) : get_visibility_of_instance_method(*args)
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def prepare_args(klass, method_symbol)
|
133
|
+
return [is_receiver_instance_of_class?(klass) ? klass.class : klass, method_symbol.to_s]
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'uml/highlevel_backtracer'
|
2
|
+
require 'uml/sequence_diagram_helper'
|
3
|
+
|
4
|
+
module UML
|
5
|
+
|
6
|
+
# Generates pic representation of program execution.
|
7
|
+
class SequenceDiagram
|
8
|
+
include SequenceDiagramHelper
|
9
|
+
|
10
|
+
# Options:
|
11
|
+
# basename:: You can give the actor a name. Default is 'Actor'.
|
12
|
+
# multiple_activation:: If +true+, and an activated object calls method with receiver +self+,
|
13
|
+
# object can be activated multiple times. Default is +false+.
|
14
|
+
# object_sequence:: You can sort the sequence of objects in output, if you give their names here.
|
15
|
+
#
|
16
|
+
# Every object not included, will be printed after given ones in random order.
|
17
|
+
#
|
18
|
+
# Defaults to <tt>['Actor']</tt>, which ensures that 'Actor' is always the leftmost object.
|
19
|
+
# pic_variables:: Hash with variables for sequence.pic. See UMLGraph's documentation
|
20
|
+
# for valid variables. Default +{}+.
|
21
|
+
# sequence_pic:: give the position and name of UMLGraph's sequence.pic file.
|
22
|
+
#
|
23
|
+
# Default is 'sequence.pic', which means that the file has to
|
24
|
+
# be in the directory where pic2plot is called.
|
25
|
+
# split_inherited:: If +true+ included modules and superclasses are printed as discrete objects.
|
26
|
+
#
|
27
|
+
# Default is +false+.
|
28
|
+
# use_create_message:: If method initialize is caught, a create_message is built
|
29
|
+
# instead of a normal call. Have in mind, that initialize is
|
30
|
+
# not allways used as constructor.
|
31
|
+
#
|
32
|
+
# Default is +false+.
|
33
|
+
# parameter_callback:: To get a pretty print of method args and returnvalues,
|
34
|
+
# you can give a proc here, that gets called for each argument
|
35
|
+
# or returnvalue.
|
36
|
+
#
|
37
|
+
# Default is <tt>lambda { |p| p.to_s }</tt>, but have a look
|
38
|
+
# at method callback in examples/sequence_diagram_generator.rb for a
|
39
|
+
# more sophisticated and recursive example.
|
40
|
+
def initialize(config = nil)
|
41
|
+
@config = {
|
42
|
+
:basename => 'Actor',
|
43
|
+
:split_inherited => false,
|
44
|
+
:use_create_message => false,
|
45
|
+
:multiple_activation => false,
|
46
|
+
:sequence_pic => 'sequence.pic',
|
47
|
+
:pic_variables => {},
|
48
|
+
:object_sequence => ['Actor'],
|
49
|
+
:parameter_callback => lambda { |p| p.to_s }
|
50
|
+
}
|
51
|
+
@config.update(config) if config
|
52
|
+
|
53
|
+
@object_definitions = {}
|
54
|
+
@object_definitions[@config[:basename]] = ObjectDefinition.new(@config[:basename], :actor)
|
55
|
+
@messages = ''
|
56
|
+
|
57
|
+
@tracer = HighlevelBacktracer.new
|
58
|
+
@tracer.add_observer self
|
59
|
+
end
|
60
|
+
|
61
|
+
# Include methods of class or instance.
|
62
|
+
#
|
63
|
+
# The parameters are handed on to <tt>AspectR::wrap</tt>, so refer to the according
|
64
|
+
# documentation for how you can e.g. use Regexp.
|
65
|
+
#
|
66
|
+
# :call-seq: include(klass, *methods)
|
67
|
+
#
|
68
|
+
def include(*args)
|
69
|
+
@tracer.include(*args)
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_pic
|
73
|
+
result = ''
|
74
|
+
result << ".PS\n"
|
75
|
+
result << "copy \"#{@config[:sequence_pic]}\";\n"
|
76
|
+
result << pic_variable_definitions(@config[:pic_variables])
|
77
|
+
result << pic_object_definitions(object_definitions)
|
78
|
+
result << pic_message_exchange(@messages)
|
79
|
+
result << pic_object_completions(@object_definitions.values)
|
80
|
+
result << '.PE'
|
81
|
+
result
|
82
|
+
end
|
83
|
+
|
84
|
+
def update(event, tracer) # :nodoc: #
|
85
|
+
begin
|
86
|
+
right = tracer.call_stack[-1]
|
87
|
+
left = tracer.call_stack[-2]
|
88
|
+
|
89
|
+
used_object = @config[:split_inherited] ? :real_object : :object
|
90
|
+
right_label = right[used_object].name
|
91
|
+
left_label = (left) ? left[used_object].name : @config[:basename]
|
92
|
+
|
93
|
+
case event
|
94
|
+
when :call
|
95
|
+
|
96
|
+
if @config[:use_create_message] and right[:method_symbol] == :initialize
|
97
|
+
@object_definitions[right_label] ||= ObjectDefinition.new(right_label,
|
98
|
+
:placeholder_object
|
99
|
+
)
|
100
|
+
@messages << pic_create_message(@object_definitions[left_label],
|
101
|
+
@object_definitions[right_label]
|
102
|
+
)
|
103
|
+
else
|
104
|
+
@object_definitions[right_label] ||= ObjectDefinition.new(right_label)
|
105
|
+
@messages << pic_message(@object_definitions[left_label],
|
106
|
+
@object_definitions[right_label],
|
107
|
+
right[:method_symbol],
|
108
|
+
right[:args].collect(&@config[:parameter_callback]).join(',')
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
when :return
|
113
|
+
@messages << pic_return_message(@object_definitions[left_label],
|
114
|
+
@object_definitions[right_label],
|
115
|
+
@config[:parameter_callback].call(right[:returns])
|
116
|
+
)
|
117
|
+
end
|
118
|
+
rescue Exception => e
|
119
|
+
puts e
|
120
|
+
puts e.backtrace
|
121
|
+
exit 1
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def object_definitions
|
128
|
+
result = []
|
129
|
+
labels = @config[:object_sequence] + (@object_definitions.keys - @config[:object_sequence])
|
130
|
+
labels.each do |label|
|
131
|
+
result << @object_definitions[label] if @object_definitions[label]
|
132
|
+
end
|
133
|
+
result
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class UML::SequenceDiagram::ObjectDefinition # :nodoc:
|
140
|
+
|
141
|
+
attr_reader :type, :label
|
142
|
+
attr_accessor :active
|
143
|
+
|
144
|
+
def initialize(label, type = :object)
|
145
|
+
@label = label
|
146
|
+
@type = type
|
147
|
+
@active = 0
|
148
|
+
end
|
149
|
+
|
150
|
+
# does nothing more, than ensure that label can be used as name for pic
|
151
|
+
def name
|
152
|
+
@label.gsub(/::/, '_')
|
153
|
+
end
|
154
|
+
|
155
|
+
alias to_s name
|
156
|
+
|
157
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module UML
|
2
|
+
module SequenceDiagramHelper
|
3
|
+
|
4
|
+
def pic_object_definitions(definitions)
|
5
|
+
result = "\n# Object definition\n"
|
6
|
+
definitions.each do |definition|
|
7
|
+
result << pic_object_definition(definition)
|
8
|
+
end
|
9
|
+
result << pic_step
|
10
|
+
end
|
11
|
+
|
12
|
+
def pic_message_exchange(string)
|
13
|
+
result = "\n# Message exchange\n"
|
14
|
+
result << string
|
15
|
+
result << pic_step
|
16
|
+
end
|
17
|
+
|
18
|
+
def pic_object_completions(definitions)
|
19
|
+
result = "\n# Object lifeline completion\n"
|
20
|
+
definitions.each do |definition|
|
21
|
+
result << pic_complete(definition)
|
22
|
+
end
|
23
|
+
result
|
24
|
+
end
|
25
|
+
|
26
|
+
def pic_object_definition(definition)
|
27
|
+
case definition.type
|
28
|
+
when :placeholder_object
|
29
|
+
pic_placeholder_object(definition)
|
30
|
+
when :actor
|
31
|
+
pic_actor(definition)
|
32
|
+
else
|
33
|
+
pic_object(definition)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def pic_actor(definition)
|
38
|
+
"actor(#{definition},\"#{definition.label}\");\n"
|
39
|
+
end
|
40
|
+
|
41
|
+
def pic_complete(definition)
|
42
|
+
"complete(#{definition});\n"
|
43
|
+
end
|
44
|
+
|
45
|
+
def pic_object(definition)
|
46
|
+
"object(#{definition},\"#{definition.label}\");\n"
|
47
|
+
end
|
48
|
+
|
49
|
+
def pic_placeholder_object(definition)
|
50
|
+
"placeholder_object(#{definition});\n"
|
51
|
+
end
|
52
|
+
|
53
|
+
def pic_step
|
54
|
+
"step();\n"
|
55
|
+
end
|
56
|
+
|
57
|
+
def pic_variable(variable, value)
|
58
|
+
"#{variable} = #{value};\n"
|
59
|
+
end
|
60
|
+
|
61
|
+
# hash format :variable, value
|
62
|
+
def pic_variable_definitions(hash)
|
63
|
+
result = "\n# Variable definitions\n"
|
64
|
+
hash.each do |variable, value|
|
65
|
+
result << pic_variable(variable, value)
|
66
|
+
end
|
67
|
+
result
|
68
|
+
end
|
69
|
+
|
70
|
+
def pic_active(definition)
|
71
|
+
definition.active += 1
|
72
|
+
(@config[:multiple_activation] or definition.active == 1) ? "active(#{definition});\n" : ''
|
73
|
+
end
|
74
|
+
|
75
|
+
def pic_inactive(definition)
|
76
|
+
definition.active -= 1
|
77
|
+
(@config[:multiple_activation] or definition.active == 0) ? "inactive(#{definition});\n" : ''
|
78
|
+
end
|
79
|
+
|
80
|
+
def pic_message(from, to, message, value)
|
81
|
+
result = "message(#{from},#{to},\"#{message}(#{value})\");\n"
|
82
|
+
result << pic_active(to)
|
83
|
+
end
|
84
|
+
|
85
|
+
def pic_return_message(left, right, value)
|
86
|
+
result = "return_message(#{right},#{left},\"#{value}\");\n"
|
87
|
+
result << pic_inactive(right)
|
88
|
+
end
|
89
|
+
|
90
|
+
def pic_create_message(from, to)
|
91
|
+
result = "create_message(#{from},#{to},\"#{to.label}\");\n"
|
92
|
+
result << pic_active(to)
|
93
|
+
end
|
94
|
+
|
95
|
+
# def escape(string)
|
96
|
+
# string.gsub(/(["])/, '\\\\\1')
|
97
|
+
# end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|