line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# vim:set ft=perl ts=4 sw=4 et fdm=marker: |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
package UML::Class::Simple; |
4
|
|
|
|
|
|
|
|
5
|
2
|
|
|
2
|
|
16640
|
use strict; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
50
|
|
6
|
2
|
|
|
2
|
|
6
|
use warnings; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
56
|
|
7
|
2
|
|
|
2
|
|
5
|
no warnings 'redefine'; |
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
88
|
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
our $VERSION = '0.21'; |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
#use Smart::Comments; |
12
|
2
|
|
|
2
|
|
7
|
use Carp qw(carp confess); |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
154
|
|
13
|
2
|
|
|
2
|
|
1071
|
use Class::Inspector; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
use Devel::Peek (); |
15
|
|
|
|
|
|
|
use File::Spec; |
16
|
|
|
|
|
|
|
use IPC::Run3; |
17
|
|
|
|
|
|
|
use List::MoreUtils 'any'; |
18
|
|
|
|
|
|
|
use Template; |
19
|
|
|
|
|
|
|
use XML::LibXML (); |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
require Exporter; |
22
|
|
|
|
|
|
|
our @ISA = qw(Exporter); |
23
|
|
|
|
|
|
|
our @EXPORT = qw( |
24
|
|
|
|
|
|
|
classes_from_runtime classes_from_files |
25
|
|
|
|
|
|
|
exclude_by_paths grep_by_paths |
26
|
|
|
|
|
|
|
); |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
my $tt = Template->new; |
29
|
|
|
|
|
|
|
my $dot_template; |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
sub classes_from_runtime { |
32
|
|
|
|
|
|
|
my ($modules, $pattern) = @_; |
33
|
|
|
|
|
|
|
$modules = [$modules] if $modules and !ref $modules; |
34
|
|
|
|
|
|
|
$pattern = '' if !defined $pattern; |
35
|
|
|
|
|
|
|
for (@$modules) { |
36
|
|
|
|
|
|
|
eval "use $_;"; |
37
|
|
|
|
|
|
|
if ($@) { carp $@; return (); } |
38
|
|
|
|
|
|
|
} |
39
|
|
|
|
|
|
|
grep { /$pattern/ } _runtime_packages(); |
40
|
|
|
|
|
|
|
} |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
sub _normalize_path ($) { |
43
|
|
|
|
|
|
|
my $path = shift; |
44
|
|
|
|
|
|
|
$path = File::Spec->rel2abs($path); |
45
|
|
|
|
|
|
|
if (File::Spec->case_tolerant()) { |
46
|
|
|
|
|
|
|
$path = lc($path); |
47
|
|
|
|
|
|
|
} else { |
48
|
|
|
|
|
|
|
$path; |
49
|
|
|
|
|
|
|
} |
50
|
|
|
|
|
|
|
} |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
sub exclude_by_paths ($@) { |
53
|
|
|
|
|
|
|
my $rclasses = shift; |
54
|
|
|
|
|
|
|
my @paths = map { _normalize_path($_) } @_; |
55
|
|
|
|
|
|
|
my @res; |
56
|
|
|
|
|
|
|
#_extend_INC(); |
57
|
|
|
|
|
|
|
for my $class (@$rclasses) { |
58
|
|
|
|
|
|
|
#warn $class; |
59
|
|
|
|
|
|
|
my $filename = Class::Inspector->resolved_filename($class); |
60
|
|
|
|
|
|
|
#warn "[0] ", $filename, "\n"; |
61
|
|
|
|
|
|
|
if (!$filename && $INC{$class}) { |
62
|
|
|
|
|
|
|
$filename = Class::Inspector->loaded_filename($class); |
63
|
|
|
|
|
|
|
} |
64
|
|
|
|
|
|
|
if (!$filename) { next; } |
65
|
|
|
|
|
|
|
#warn "[1] ", $filename, "\n"; |
66
|
|
|
|
|
|
|
$filename = _normalize_path($filename); |
67
|
|
|
|
|
|
|
#warn "[2] ", $filename, "\n"; |
68
|
|
|
|
|
|
|
#my $value = $INC{$key}; |
69
|
|
|
|
|
|
|
if (any { substr($filename, 0, length) eq $_ } @paths) { |
70
|
|
|
|
|
|
|
#warn "!!! ignoring $filename\n"; |
71
|
|
|
|
|
|
|
next; |
72
|
|
|
|
|
|
|
} |
73
|
|
|
|
|
|
|
#warn "adding $filename <=> @paths\n"; |
74
|
|
|
|
|
|
|
push @res, $class; |
75
|
|
|
|
|
|
|
} |
76
|
|
|
|
|
|
|
@res; |
77
|
|
|
|
|
|
|
} |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
sub grep_by_paths ($@) { |
80
|
|
|
|
|
|
|
my $rclasses = shift; |
81
|
|
|
|
|
|
|
my @paths = map { _normalize_path($_) } @_; |
82
|
|
|
|
|
|
|
my @res; |
83
|
|
|
|
|
|
|
#_extend_INC(); |
84
|
|
|
|
|
|
|
for my $class (@$rclasses) { |
85
|
|
|
|
|
|
|
my $filename = Class::Inspector->resolved_filename($class); |
86
|
|
|
|
|
|
|
if (!$filename && $INC{$class}) { |
87
|
|
|
|
|
|
|
$filename = Class::Inspector->loaded_filename($class); |
88
|
|
|
|
|
|
|
} |
89
|
|
|
|
|
|
|
if (!$filename) { next; } |
90
|
|
|
|
|
|
|
$filename = _normalize_path($filename); |
91
|
|
|
|
|
|
|
#my $value = $INC{$key}; |
92
|
|
|
|
|
|
|
if (any { substr($filename, 0, length) eq $_ } @paths) { |
93
|
|
|
|
|
|
|
#warn "adding $filename <=> @paths\n"; |
94
|
|
|
|
|
|
|
push @res, $class; |
95
|
|
|
|
|
|
|
next; |
96
|
|
|
|
|
|
|
} |
97
|
|
|
|
|
|
|
#warn "!!! ignoring $filename\n"; |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
@res; |
100
|
|
|
|
|
|
|
} |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
sub _runtime_packages { |
103
|
|
|
|
|
|
|
no strict 'refs'; |
104
|
|
|
|
|
|
|
my $pkg_name = shift || '::'; |
105
|
|
|
|
|
|
|
my $cache = shift || {}; |
106
|
|
|
|
|
|
|
return if $cache->{$pkg_name}; |
107
|
|
|
|
|
|
|
$cache->{$pkg_name} = 1; |
108
|
|
|
|
|
|
|
for my $entry (keys %$pkg_name) { |
109
|
|
|
|
|
|
|
next if $entry !~ /\:\:$/ or $entry eq 'main::'; |
110
|
|
|
|
|
|
|
my $subpkg_name = $pkg_name.$entry; |
111
|
|
|
|
|
|
|
#warn $subpkg_name; |
112
|
|
|
|
|
|
|
_runtime_packages($subpkg_name, $cache); |
113
|
|
|
|
|
|
|
$cache->{$subpkg_name} = 1; |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
map { s/^::|::$//g; $_ } keys %$cache; |
116
|
|
|
|
|
|
|
} |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
sub classes_from_files { |
119
|
|
|
|
|
|
|
require PPI; |
120
|
|
|
|
|
|
|
my ($list, $pattern, $read_only) = @_; |
121
|
|
|
|
|
|
|
$list = [$list] if $list and !ref $list; |
122
|
|
|
|
|
|
|
$pattern = '' if !defined $pattern; |
123
|
|
|
|
|
|
|
my @classes; |
124
|
|
|
|
|
|
|
my $cache = {}; |
125
|
|
|
|
|
|
|
for my $file (@$list) { |
126
|
|
|
|
|
|
|
_gen_paths($file, $cache); |
127
|
|
|
|
|
|
|
my $doc = PPI::Document->new( $file ); |
128
|
|
|
|
|
|
|
if (!$doc) { |
129
|
|
|
|
|
|
|
carp "warning: Can't parse $file: ", PPI::Document->errstr; |
130
|
|
|
|
|
|
|
next; |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
my $res = $doc->find('PPI::Statement::Package'); |
133
|
|
|
|
|
|
|
next if !$res; |
134
|
|
|
|
|
|
|
push @classes, map { $_->namespace } @$res; |
135
|
|
|
|
|
|
|
_load_file($file) if !$read_only; |
136
|
|
|
|
|
|
|
} |
137
|
|
|
|
|
|
|
@classes = grep { /$pattern/ } @classes; |
138
|
|
|
|
|
|
|
#@classes = sort @classes; |
139
|
|
|
|
|
|
|
wantarray ? @classes : \@classes; |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
sub _gen_paths { |
143
|
|
|
|
|
|
|
my ($file, $cache) = @_; |
144
|
|
|
|
|
|
|
$file =~ s{\\+}{/}g; |
145
|
|
|
|
|
|
|
my $dir; |
146
|
|
|
|
|
|
|
while ($file =~ m{(?x) \G .+? /+ }gc) { |
147
|
|
|
|
|
|
|
$dir .= $&; |
148
|
|
|
|
|
|
|
next if $cache->{$dir}; |
149
|
|
|
|
|
|
|
$cache->{$dir} = 1; |
150
|
|
|
|
|
|
|
#warn "pushing ~~~ $dir\n"; |
151
|
|
|
|
|
|
|
unshift @INC, $dir; |
152
|
|
|
|
|
|
|
} |
153
|
|
|
|
|
|
|
} |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
sub new { |
156
|
|
|
|
|
|
|
my $class = ref $_[0] ? ref shift : shift; |
157
|
|
|
|
|
|
|
my $rclasses = shift || []; |
158
|
|
|
|
|
|
|
my $self = bless { |
159
|
|
|
|
|
|
|
class_names => $rclasses, |
160
|
|
|
|
|
|
|
node_color => '#f1e1f4', |
161
|
|
|
|
|
|
|
display_inheritance => 1, |
162
|
|
|
|
|
|
|
display_methods => 1, |
163
|
|
|
|
|
|
|
}, $class; |
164
|
|
|
|
|
|
|
$self->{inherited_methods} = 1; |
165
|
|
|
|
|
|
|
my $options = shift; |
166
|
|
|
|
|
|
|
if (ref($options) eq 'HASH') { |
167
|
|
|
|
|
|
|
$self->{inherited_methods} = $options->{inherited_methods}; |
168
|
|
|
|
|
|
|
if (defined $options->{xmi_model}) { |
169
|
|
|
|
|
|
|
$self->_xmi_load_model($options->{xmi_model}); |
170
|
|
|
|
|
|
|
} |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
#$self->_build_dom; |
173
|
|
|
|
|
|
|
$self; |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
sub size { |
177
|
|
|
|
|
|
|
my $self = shift; |
178
|
|
|
|
|
|
|
if (@_) { |
179
|
|
|
|
|
|
|
my ($width, $height) = @_; |
180
|
|
|
|
|
|
|
if (!$width || !$height || ($width . $height) !~ /^[\.\d]+$/) { |
181
|
|
|
|
|
|
|
carp "invalid width and height"; |
182
|
|
|
|
|
|
|
return undef; |
183
|
|
|
|
|
|
|
} else { |
184
|
|
|
|
|
|
|
$self->{width} = $width; |
185
|
|
|
|
|
|
|
$self->{height} = $height; |
186
|
|
|
|
|
|
|
return 1; |
187
|
|
|
|
|
|
|
} |
188
|
|
|
|
|
|
|
} else { |
189
|
|
|
|
|
|
|
return ($self->{width}, $self->{height}); |
190
|
|
|
|
|
|
|
} |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
sub node_color { |
194
|
|
|
|
|
|
|
my $self = shift; |
195
|
|
|
|
|
|
|
if (@_) { |
196
|
|
|
|
|
|
|
$self->{node_color} = shift; |
197
|
|
|
|
|
|
|
} else { |
198
|
|
|
|
|
|
|
$self->{node_color}; |
199
|
|
|
|
|
|
|
} |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
sub dot_prog { |
203
|
|
|
|
|
|
|
my $self = shift; |
204
|
|
|
|
|
|
|
if (@_) { |
205
|
|
|
|
|
|
|
my $cmd = shift; |
206
|
|
|
|
|
|
|
can_run($cmd) or die "ERROR: The dot program ($cmd) cannot be found or be run.\n"; |
207
|
|
|
|
|
|
|
$self->{dot_prog} = $cmd; |
208
|
|
|
|
|
|
|
} else { |
209
|
|
|
|
|
|
|
$self->{dot_prog} || 'dot'; |
210
|
|
|
|
|
|
|
} |
211
|
|
|
|
|
|
|
} |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
# copied from IPC::Cmd. Copyright by IPC::Cmd's author. |
214
|
|
|
|
|
|
|
sub can_run { |
215
|
|
|
|
|
|
|
my $command = shift; |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
# a lot of VMS executables have a symbol defined |
218
|
|
|
|
|
|
|
# check those first |
219
|
|
|
|
|
|
|
if ( $^O eq 'VMS' ) { |
220
|
|
|
|
|
|
|
require VMS::DCLsym; |
221
|
|
|
|
|
|
|
my $syms = VMS::DCLsym->new; |
222
|
|
|
|
|
|
|
return $command if scalar $syms->getsym( uc $command ); |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
require Config; |
226
|
|
|
|
|
|
|
require File::Spec; |
227
|
|
|
|
|
|
|
require ExtUtils::MakeMaker; |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
if( File::Spec->file_name_is_absolute($command) ) { |
230
|
|
|
|
|
|
|
return MM->maybe_command($command); |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
} else { |
233
|
|
|
|
|
|
|
for my $dir ( |
234
|
|
|
|
|
|
|
(split /\Q$Config::Config{path_sep}\E/, $ENV{PATH}), |
235
|
|
|
|
|
|
|
File::Spec->curdir |
236
|
|
|
|
|
|
|
) { |
237
|
|
|
|
|
|
|
my $abs = File::Spec->catfile($dir, $command); |
238
|
|
|
|
|
|
|
return $abs if $abs = MM->maybe_command($abs); |
239
|
|
|
|
|
|
|
} |
240
|
|
|
|
|
|
|
} |
241
|
|
|
|
|
|
|
} |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
sub _property { |
244
|
|
|
|
|
|
|
my $self = shift; |
245
|
|
|
|
|
|
|
my $property_name = shift; |
246
|
|
|
|
|
|
|
if (@_) { |
247
|
|
|
|
|
|
|
$self->{$property_name} = shift; |
248
|
|
|
|
|
|
|
$self->_build_dom(1); |
249
|
|
|
|
|
|
|
} else { |
250
|
|
|
|
|
|
|
$self->{$property_name}; |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
} |
253
|
|
|
|
|
|
|
} |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
sub public_only { |
256
|
|
|
|
|
|
|
my $self = shift; |
257
|
|
|
|
|
|
|
$self->_property('public_only', @_); |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
sub inherited_methods { |
261
|
|
|
|
|
|
|
my $self = shift; |
262
|
|
|
|
|
|
|
$self->_property('inherited_methods', @_); |
263
|
|
|
|
|
|
|
} |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
sub as_png { |
266
|
|
|
|
|
|
|
my $self = shift; |
267
|
|
|
|
|
|
|
$self->_as_image('png', @_); |
268
|
|
|
|
|
|
|
} |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
sub as_gif { |
271
|
|
|
|
|
|
|
my $self = shift; |
272
|
|
|
|
|
|
|
$self->_as_image('gif', @_); |
273
|
|
|
|
|
|
|
} |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
sub as_svg { |
276
|
|
|
|
|
|
|
my $self = shift; |
277
|
|
|
|
|
|
|
$self->_as_image('svg', @_); |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
sub _as_image { |
281
|
|
|
|
|
|
|
my ($self, $type, $fname) = @_; |
282
|
|
|
|
|
|
|
my $dot = $self->as_dot; |
283
|
|
|
|
|
|
|
#if ($fname eq 'fast00.png') { |
284
|
|
|
|
|
|
|
#warn "==== $fname\n"; |
285
|
|
|
|
|
|
|
#warn $dot; |
286
|
|
|
|
|
|
|
#use YAML::Syck; |
287
|
|
|
|
|
|
|
#$self->_build_dom(1); |
288
|
|
|
|
|
|
|
#warn Dump($self->as_dom); |
289
|
|
|
|
|
|
|
#} |
290
|
|
|
|
|
|
|
my @cmd = ($self->dot_prog(), '-T', $type); |
291
|
|
|
|
|
|
|
#my @cmd = ('dot', '-T', $type); |
292
|
|
|
|
|
|
|
if ($fname) { |
293
|
|
|
|
|
|
|
push @cmd, '-o', $fname; |
294
|
|
|
|
|
|
|
} |
295
|
|
|
|
|
|
|
my ($img_data, $stderr); |
296
|
|
|
|
|
|
|
my $success = run3 \@cmd, \$dot, \$img_data, \$stderr; |
297
|
|
|
|
|
|
|
if ($stderr) { |
298
|
|
|
|
|
|
|
if ($? == 0) { |
299
|
|
|
|
|
|
|
carp $stderr; |
300
|
|
|
|
|
|
|
} else { |
301
|
|
|
|
|
|
|
Carp::croak $stderr; |
302
|
|
|
|
|
|
|
} |
303
|
|
|
|
|
|
|
} |
304
|
|
|
|
|
|
|
if (!$fname) { |
305
|
|
|
|
|
|
|
return $img_data; |
306
|
|
|
|
|
|
|
} |
307
|
|
|
|
|
|
|
} |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
sub as_dom { |
310
|
|
|
|
|
|
|
my $self = shift; |
311
|
|
|
|
|
|
|
$self->_build_dom; |
312
|
|
|
|
|
|
|
{ classes => $self->{classes} }; |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
sub set_dom ($$) { |
316
|
|
|
|
|
|
|
my $self = shift; |
317
|
|
|
|
|
|
|
$self->{classes} = shift->{classes}; |
318
|
|
|
|
|
|
|
1; |
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
sub moose_roles ($) { |
322
|
|
|
|
|
|
|
my $self = shift; |
323
|
|
|
|
|
|
|
$self->{'moose_roles'} = shift; |
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
sub display_methods ($) { |
327
|
|
|
|
|
|
|
my $self = shift; |
328
|
|
|
|
|
|
|
$self->{'display_methods'} = shift; |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
sub display_inheritance ($) { |
332
|
|
|
|
|
|
|
my $self = shift; |
333
|
|
|
|
|
|
|
$self->{'display_inheritance'} = shift; |
334
|
|
|
|
|
|
|
} |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
sub _build_dom { |
337
|
|
|
|
|
|
|
my ($self, $force) = @_; |
338
|
|
|
|
|
|
|
# avoid unnecessary evaluation: |
339
|
|
|
|
|
|
|
return if $self->{classes} && !$force || !$self->{class_names}; |
340
|
|
|
|
|
|
|
#warn "HERE"; |
341
|
|
|
|
|
|
|
my @pkg = @{ $self->{class_names} }; |
342
|
|
|
|
|
|
|
my @classes; |
343
|
|
|
|
|
|
|
$self->{classes} = \@classes; |
344
|
|
|
|
|
|
|
my $public_only = $self->{public_only}; |
345
|
|
|
|
|
|
|
my %visited; # used to eliminate potential repetitions |
346
|
|
|
|
|
|
|
for my $pkg (@pkg) { |
347
|
|
|
|
|
|
|
#warn $pkg; |
348
|
|
|
|
|
|
|
$pkg =~ s/::::/::/g; |
349
|
|
|
|
|
|
|
if ($visited{$pkg}) { next; } |
350
|
|
|
|
|
|
|
$visited{$pkg} = 1; |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
if (!Class::Inspector->loaded($pkg)) { |
353
|
|
|
|
|
|
|
#my $pmfile = Class::Inspector->filename($pkg); |
354
|
|
|
|
|
|
|
#warn $pmfile; |
355
|
|
|
|
|
|
|
#if ($pmfile) { |
356
|
|
|
|
|
|
|
# if (! _load_file($pmfile)) { |
357
|
|
|
|
|
|
|
# next; |
358
|
|
|
|
|
|
|
# } |
359
|
|
|
|
|
|
|
#} else { next } |
360
|
|
|
|
|
|
|
next; |
361
|
|
|
|
|
|
|
} |
362
|
|
|
|
|
|
|
push @classes, { |
363
|
|
|
|
|
|
|
name => $pkg, methods => [], |
364
|
|
|
|
|
|
|
properties => [], subclasses => [], |
365
|
|
|
|
|
|
|
}; |
366
|
|
|
|
|
|
|
my $from_class_accessor = |
367
|
|
|
|
|
|
|
$pkg->isa('Class::Accessor') || |
368
|
|
|
|
|
|
|
$pkg->isa('Class::Accessor::Fast') || |
369
|
|
|
|
|
|
|
$pkg->isa('Class::Accessor::Grouped'); |
370
|
|
|
|
|
|
|
#accessor_name_for |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
# If you want to gather only the functions defined in |
373
|
|
|
|
|
|
|
# the current class only (w/o those inherited from ancestors), |
374
|
|
|
|
|
|
|
# set inherited_methods property to false (default value is true). |
375
|
|
|
|
|
|
|
my $methods = Class::Inspector->methods($pkg, 'expanded'); |
376
|
|
|
|
|
|
|
if ($methods and ref($methods) eq 'ARRAY') { |
377
|
|
|
|
|
|
|
if ($from_class_accessor) { |
378
|
|
|
|
|
|
|
my $i = 0; |
379
|
|
|
|
|
|
|
my %functions = map { $_->[2] => $i++ } @$methods; # create hash from array |
380
|
|
|
|
|
|
|
### %functions |
381
|
|
|
|
|
|
|
#my @accessors = map { /^_(.*)_accessor$/; $1 } keys %functions; |
382
|
|
|
|
|
|
|
### @accessors |
383
|
|
|
|
|
|
|
my $use_best_practice = delete $functions{'accessor_name_for'} && delete $functions{'mutator_name_for'}; |
384
|
|
|
|
|
|
|
my %accessors; |
385
|
|
|
|
|
|
|
foreach my $meth (keys %functions) { |
386
|
|
|
|
|
|
|
next unless $meth; |
387
|
|
|
|
|
|
|
if ($meth =~ /^_(.*)_accessor$/) { |
388
|
|
|
|
|
|
|
my $accessor = $1; |
389
|
|
|
|
|
|
|
if (exists $functions{$accessor}) { |
390
|
|
|
|
|
|
|
if ($self->{inherited_methods} or |
391
|
|
|
|
|
|
|
$methods->[$functions{$accessor}]->[1] eq $pkg) { |
392
|
|
|
|
|
|
|
push @{ $classes[-1]->{properties} }, $accessor; |
393
|
|
|
|
|
|
|
} |
394
|
|
|
|
|
|
|
delete $functions{$accessor}; |
395
|
|
|
|
|
|
|
delete $functions{"_${accessor}_accessor"}; |
396
|
|
|
|
|
|
|
#push @{ $classes[-1]->{properties} }, $accessor; |
397
|
|
|
|
|
|
|
} |
398
|
|
|
|
|
|
|
next; |
399
|
|
|
|
|
|
|
} |
400
|
|
|
|
|
|
|
if ($use_best_practice) { |
401
|
|
|
|
|
|
|
if ($meth =~ /^(?:get|set)_(.+)/) { |
402
|
|
|
|
|
|
|
my $accessor = $1; |
403
|
|
|
|
|
|
|
delete $functions{$meth}; |
404
|
|
|
|
|
|
|
if (!$accessors{$accessor}) { |
405
|
|
|
|
|
|
|
#push @{ $classes[-1]->{properties} }, $accessor; |
406
|
|
|
|
|
|
|
if ($self->{inherited_methods} or |
407
|
|
|
|
|
|
|
$methods->[$functions{$accessor}]->[1] eq $pkg) { |
408
|
|
|
|
|
|
|
push @{ $classes[-1]->{properties} }, $accessor; |
409
|
|
|
|
|
|
|
} |
410
|
|
|
|
|
|
|
$accessors{$accessor} = 1; |
411
|
|
|
|
|
|
|
} |
412
|
|
|
|
|
|
|
} |
413
|
|
|
|
|
|
|
} |
414
|
|
|
|
|
|
|
} |
415
|
|
|
|
|
|
|
@$methods = grep { exists $functions{$_->[2]} } @$methods; |
416
|
|
|
|
|
|
|
} |
417
|
|
|
|
|
|
|
@{ $classes[-1]->{properties} } = sort @{ $classes[-1]->{properties} }; |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
foreach my $method (@$methods) { |
420
|
|
|
|
|
|
|
next if $method->[1] ne $pkg; |
421
|
|
|
|
|
|
|
if (! $self->{inherited_methods}) { |
422
|
|
|
|
|
|
|
my $source_name = Devel::Peek::CvGV($method->[3]); |
423
|
|
|
|
|
|
|
$source_name =~ s/^\*//; |
424
|
|
|
|
|
|
|
next if $method->[0] ne $source_name; |
425
|
|
|
|
|
|
|
} |
426
|
|
|
|
|
|
|
$method = $method->[2]; |
427
|
|
|
|
|
|
|
next if $public_only && $method =~ /^_/o; |
428
|
|
|
|
|
|
|
push @{$classes[-1]->{methods}}, $method; |
429
|
|
|
|
|
|
|
} |
430
|
|
|
|
|
|
|
} |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
my $subclasses = Class::Inspector->subclasses($pkg); |
435
|
|
|
|
|
|
|
if ($subclasses) { |
436
|
|
|
|
|
|
|
no strict 'refs'; |
437
|
|
|
|
|
|
|
my @child = grep { |
438
|
|
|
|
|
|
|
#warn "!!!! ", join ' ', @{"${_}::ISA"}; |
439
|
|
|
|
|
|
|
any { $_ eq $pkg } @{"${_}::ISA"}; |
440
|
|
|
|
|
|
|
} @$subclasses; |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
if (@child) { |
443
|
|
|
|
|
|
|
$classes[-1]->{subclasses} = \@child; |
444
|
|
|
|
|
|
|
} |
445
|
|
|
|
|
|
|
} |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
if (Class::Inspector->function_exists($pkg, 'meta')) { |
448
|
|
|
|
|
|
|
# at least Class::MOP |
449
|
|
|
|
|
|
|
my $meta = $pkg->meta(); |
450
|
|
|
|
|
|
|
if ($meta->can('consumers')) { |
451
|
|
|
|
|
|
|
# Something like Moose::Meta::Role |
452
|
|
|
|
|
|
|
my @consumers = $meta->consumers(); |
453
|
|
|
|
|
|
|
if (@consumers) { |
454
|
|
|
|
|
|
|
$classes[-1]->{'consumers'} = [ @consumers ]; |
455
|
|
|
|
|
|
|
} |
456
|
|
|
|
|
|
|
} |
457
|
|
|
|
|
|
|
} |
458
|
|
|
|
|
|
|
} |
459
|
|
|
|
|
|
|
#warn "@classes"; |
460
|
|
|
|
|
|
|
} |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
sub _load_file ($) { |
463
|
|
|
|
|
|
|
my $file = shift; |
464
|
|
|
|
|
|
|
my $path = _normalize_path($file); |
465
|
|
|
|
|
|
|
#warn "!!! >>>> $path\n"; |
466
|
|
|
|
|
|
|
if ( any { |
467
|
|
|
|
|
|
|
#warn "<<<<< ", _normalize_path($_), "\n"; |
468
|
|
|
|
|
|
|
$path eq _normalize_path($_); |
469
|
|
|
|
|
|
|
} values %INC ) { |
470
|
|
|
|
|
|
|
#carp "!!! Caught duplicate module files: $file ($path)"; |
471
|
|
|
|
|
|
|
return 1; |
472
|
|
|
|
|
|
|
} |
473
|
|
|
|
|
|
|
#my @a = values %INC; |
474
|
|
|
|
|
|
|
#warn "\n@a\n"; |
475
|
|
|
|
|
|
|
#warn "!!! Loading $path...\n"; |
476
|
|
|
|
|
|
|
eval { |
477
|
|
|
|
|
|
|
require $path; |
478
|
|
|
|
|
|
|
}; |
479
|
|
|
|
|
|
|
carp $@ if $@; |
480
|
|
|
|
|
|
|
!$@; |
481
|
|
|
|
|
|
|
} |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
sub _xmi_get_new_id { |
484
|
|
|
|
|
|
|
my $self = shift; |
485
|
|
|
|
|
|
|
return 'xmi.' . $self->{_xmi}->{_id_counter}++; |
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
sub _xmi_create_inheritance { |
489
|
|
|
|
|
|
|
my ($self, $class, $subclass_name) = @_; |
490
|
|
|
|
|
|
|
my $child_id = $self->{_xmi}->{_name2id}->{$subclass_name}; |
491
|
|
|
|
|
|
|
my $id = $self->_xmi_get_new_id(); |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
my $element = XML::LibXML::Element->new('UML:Generalization'); |
494
|
|
|
|
|
|
|
$self->{_xmi}->{_classes_root}->appendChild($element); |
495
|
|
|
|
|
|
|
$self->_xmi_set_default_attribute($element, 'isSpecification', 'false'); |
496
|
|
|
|
|
|
|
$element->setAttribute('xmi.id', $id); |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
my $child = XML::LibXML::Element->new('UML:Generalization.child'); |
499
|
|
|
|
|
|
|
$element->appendChild($child); |
500
|
|
|
|
|
|
|
my $child_xml_class = XML::LibXML::Element->new('UML:Class'); |
501
|
|
|
|
|
|
|
$child->appendChild($child_xml_class); |
502
|
|
|
|
|
|
|
$child_xml_class->setAttribute('xmi.idref', $child_id); |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
my $parent = XML::LibXML::Element->new('UML:Generalization.parent'); |
505
|
|
|
|
|
|
|
$element->appendChild($parent); |
506
|
|
|
|
|
|
|
$child_xml_class = XML::LibXML::Element->new('UML:Class'); |
507
|
|
|
|
|
|
|
$parent->appendChild($child_xml_class); |
508
|
|
|
|
|
|
|
$child_xml_class->setAttribute('xmi.idref', $class->{xmi_id}); |
509
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
my $xml_class = $self->{_xmi}->{_classes_hash}->{$subclass_name}; |
511
|
|
|
|
|
|
|
return unless defined $xml_class; |
512
|
|
|
|
|
|
|
my $generalization = XML::LibXML::Element->new('UML:Generalization'); |
513
|
|
|
|
|
|
|
$generalization->setAttribute('xmi.idref', $id); |
514
|
|
|
|
|
|
|
my $generalizableElement = XML::LibXML::Element->new('UML:GeneralizableElement.generalization'); |
515
|
|
|
|
|
|
|
$generalizableElement->appendChild($generalization); |
516
|
|
|
|
|
|
|
$xml_class->appendChild($generalizableElement); |
517
|
|
|
|
|
|
|
} |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
sub _xmi_write_method { |
520
|
|
|
|
|
|
|
my ($self, $parent_node, $class, $method) = @_; |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
my $id = $self->_xmi_get_new_id(); |
523
|
|
|
|
|
|
|
my $visibility = 'public'; |
524
|
|
|
|
|
|
|
$visibility = 'private' if substr($method, 0, 1) eq '_'; |
525
|
|
|
|
|
|
|
my $ownerScope = 'instance'; |
526
|
|
|
|
|
|
|
$ownerScope = 'classifier' if $method =~ /^[A-Z]/o; |
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
my $xml_method = $self->_xmi_add_element($parent_node, 'UML:Operation', $method); |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
$xml_method->setAttribute('xmi.id', $id); |
531
|
|
|
|
|
|
|
$xml_method->setAttribute('visibility', $visibility); |
532
|
|
|
|
|
|
|
$xml_method->setAttribute('ownerScope', $ownerScope); |
533
|
|
|
|
|
|
|
$self->_xmi_set_default_attribute($xml_method, 'concurrency', 'sequential'); |
534
|
|
|
|
|
|
|
$self->_xmi_set_default_attribute($xml_method, $_, 'false') foreach qw(isSpecification isQuery isRoot isLeaf isAbstract); |
535
|
|
|
|
|
|
|
} |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
sub _xmi_write_class { |
538
|
|
|
|
|
|
|
my ($self, $class) = @_; |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
my $xml_class = $self->_xmi_add_element($self->{_xmi}->{_classes_root}, 'UML:Class', $class->{name}); |
541
|
|
|
|
|
|
|
$self->{_xmi}->{_classes_hash}->{$class->{name}} = $xml_class; |
542
|
|
|
|
|
|
|
$xml_class->setAttribute('xmi.id', $class->{xmi_id}); |
543
|
|
|
|
|
|
|
$xml_class->setAttribute('visibility', 'public'); |
544
|
|
|
|
|
|
|
$self->_xmi_set_default_attribute($xml_class, $_, 'false') foreach qw(isSpecification isRoot isLeaf isAbstract isActive); |
545
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
my $uml_classifier = XML::LibXML::Element->new('UML:Classifier.feature'); |
547
|
|
|
|
|
|
|
$xml_class->appendChild($uml_classifier); |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
$self->_xmi_write_method($uml_classifier, $class, $_) foreach @{$class->{methods}}; |
550
|
|
|
|
|
|
|
$self->_xmi_create_inheritance($class, $_) foreach @{$class->{subclasses}}; |
551
|
|
|
|
|
|
|
} |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
sub _xmi_set_id { |
554
|
|
|
|
|
|
|
my ($self, $class) = @_; |
555
|
|
|
|
|
|
|
$class->{xmi_id} = $self->_xmi_get_new_id(); |
556
|
|
|
|
|
|
|
$self->{_xmi}->{_name2id}->{$class->{name}} = $class->{xmi_id}; |
557
|
|
|
|
|
|
|
} |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
sub _xmi_add_element { |
560
|
|
|
|
|
|
|
my ($self, $parent, $class, $name) = @_; |
561
|
|
|
|
|
|
|
my $node; |
562
|
|
|
|
|
|
|
if (defined $name) { |
563
|
|
|
|
|
|
|
foreach $node ($parent->getElementsByTagName($class)) { |
564
|
|
|
|
|
|
|
if ($node->getAttribute('name') eq $name) { |
565
|
|
|
|
|
|
|
return $node; |
566
|
|
|
|
|
|
|
} |
567
|
|
|
|
|
|
|
} |
568
|
|
|
|
|
|
|
} |
569
|
|
|
|
|
|
|
$node = $self->{_xmi}->{_document}->createElement($class); |
570
|
|
|
|
|
|
|
$node->setAttribute('name', $name); |
571
|
|
|
|
|
|
|
$parent->appendChild($node); |
572
|
|
|
|
|
|
|
return $node; |
573
|
|
|
|
|
|
|
} |
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
sub _xmi_set_default_attribute { |
576
|
|
|
|
|
|
|
my ($self, $node, $name, $value) = @_; |
577
|
|
|
|
|
|
|
return if defined $node->getAttribute($name); |
578
|
|
|
|
|
|
|
$node->setAttribute($name, $value); |
579
|
|
|
|
|
|
|
} |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
sub _xmi_load_model { |
582
|
|
|
|
|
|
|
my ($self, $fname) = @_; |
583
|
|
|
|
|
|
|
$self->{_xmi}->{_document} = XML::LibXML->new()->parse_file($fname); |
584
|
|
|
|
|
|
|
} |
585
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
sub _xmi_init_xml { |
587
|
|
|
|
|
|
|
my ($self, $fname) = @_; |
588
|
|
|
|
|
|
|
unless (defined $self->{_xmi}->{_document}) { |
589
|
|
|
|
|
|
|
$self->{_xmi}->{_document} = XML::LibXML::Document->new('1.0', 'UTF-8'); |
590
|
|
|
|
|
|
|
} |
591
|
|
|
|
|
|
|
my $doc = $self->{_xmi}->{_document}; |
592
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
my $xmi_root = $doc->createElement('XMI'); |
594
|
|
|
|
|
|
|
$xmi_root->setAttribute('xmi.version', '1.2'); |
595
|
|
|
|
|
|
|
$xmi_root->setAttribute('xmlns:UML', 'org.omg.xmi.namespace.UML'); |
596
|
|
|
|
|
|
|
my $generate_time = POSIX::asctime(localtime(time())); |
597
|
|
|
|
|
|
|
chomp($generate_time); |
598
|
|
|
|
|
|
|
$xmi_root->setAttribute('timestamp', $generate_time); |
599
|
|
|
|
|
|
|
$doc->setDocumentElement($xmi_root); |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
my $xmi_content = $doc->createElement('XMI.content'); |
602
|
|
|
|
|
|
|
$xmi_root->appendChild($xmi_content); |
603
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
my $uml_model = $self->_xmi_add_element($xmi_content, 'UML:Model', $fname || ''); |
605
|
|
|
|
|
|
|
$uml_model->setAttribute('xmi.id', $self->_xmi_get_new_id()); |
606
|
|
|
|
|
|
|
$self->_xmi_set_default_attribute($uml_model, $_, 'false') foreach qw(isSpecification isRoot isLeaf isAbstract); |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
$self->{_xmi}->{_classes_root} = $doc->createElement('UML:Namespace.ownedElement'); |
609
|
|
|
|
|
|
|
$uml_model->appendChild($self->{_xmi}->{_classes_root}); |
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
return $doc; |
612
|
|
|
|
|
|
|
} |
613
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
sub as_xmi { |
615
|
|
|
|
|
|
|
my ($self, $fname) = @_; |
616
|
|
|
|
|
|
|
$self->_build_dom; |
617
|
|
|
|
|
|
|
$self->{_xmi} ||= {}; |
618
|
|
|
|
|
|
|
$self->{_xmi}->{_id_counter} = 1; |
619
|
|
|
|
|
|
|
$self->{_xmi}->{_name2id} = {}; |
620
|
|
|
|
|
|
|
$self->_xmi_set_id($_) foreach @{$self->{classes}}; |
621
|
|
|
|
|
|
|
my $doc = $self->_xmi_init_xml($fname); |
622
|
|
|
|
|
|
|
$self->_xmi_write_class($_) foreach @{$self->{classes}}; |
623
|
|
|
|
|
|
|
if ($fname) { |
624
|
|
|
|
|
|
|
$doc->toFile($fname, 2); |
625
|
|
|
|
|
|
|
} else { |
626
|
|
|
|
|
|
|
return $doc; |
627
|
|
|
|
|
|
|
} |
628
|
|
|
|
|
|
|
} |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
sub as_dot { |
631
|
|
|
|
|
|
|
my ($self, $fname) = @_; |
632
|
|
|
|
|
|
|
$self->_build_dom; |
633
|
|
|
|
|
|
|
if ($fname) { |
634
|
|
|
|
|
|
|
$tt->process(\$dot_template, $self, $fname) |
635
|
|
|
|
|
|
|
|| carp $tt->error(); |
636
|
|
|
|
|
|
|
} else { |
637
|
|
|
|
|
|
|
my $dot; |
638
|
|
|
|
|
|
|
$tt->process(\$dot_template, $self, \$dot) |
639
|
|
|
|
|
|
|
|| carp $tt->error(); |
640
|
|
|
|
|
|
|
$dot; |
641
|
|
|
|
|
|
|
} |
642
|
|
|
|
|
|
|
} |
643
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
sub set_dot ($$) { |
645
|
|
|
|
|
|
|
my $self = shift; |
646
|
|
|
|
|
|
|
$self->{dot} = shift; |
647
|
|
|
|
|
|
|
} |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
$dot_template = <<'_EOC_'; |
650
|
|
|
|
|
|
|
digraph uml_class_diagram { |
651
|
|
|
|
|
|
|
[%- IF width && height %] |
652
|
|
|
|
|
|
|
size="[% width %],[% height %]"; |
653
|
|
|
|
|
|
|
[%- END %] |
654
|
|
|
|
|
|
|
node [shape=record, style="filled"]; |
655
|
|
|
|
|
|
|
edge [color=red, dir=none]; |
656
|
|
|
|
|
|
|
|
657
|
|
|
|
|
|
|
[%- name2id = {} %] |
658
|
|
|
|
|
|
|
[%- id = 1 %] |
659
|
|
|
|
|
|
|
[%- FOREACH class = classes %] |
660
|
|
|
|
|
|
|
[%- name = class.name %] |
661
|
|
|
|
|
|
|
[%- name2id.$name = id %] |
662
|
|
|
|
|
|
|
class_[% id %] [shape=plaintext, style="", label=< |
663
|
|
|
|
|
|
|
664
|
|
|
|
|
|
|
| [% name %] |
665
|
|
|
|
|
|
|
|
666
|
|
|
|
|
|
|
| |
667
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
| [% IF class.properties.size > 0 %] |
670
|
|
|
|
|
|
|
[%- FOREACH property = class.properties %] |
671
|
|
|
|
|
|
|
[%- property.match("^_") ? "-" : "+" %] |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
[%- END %][% END %] |
674
|
|
|
|
|
|
|
| |
675
|
|
|
|
|
|
|
[%- FOREACH property = class.properties %] |
676
|
|
|
|
|
|
|
[%- property %] |
677
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
[%- END %] |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
| |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
| |
685
|
|
|
|
|
|
|
[%- IF display_methods %] |
686
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
| [% IF class.methods.size > 0 %] |
689
|
|
|
|
|
|
|
[%- FOREACH method = class.methods %] |
690
|
|
|
|
|
|
|
[%- method.match("^_") ? "-" : "+" %] |
691
|
|
|
|
|
|
|
|
692
|
|
|
|
|
|
|
[%- END %][% END %] |
693
|
|
|
|
|
|
|
| |
694
|
|
|
|
|
|
|
[%- FOREACH method = class.methods %] |
695
|
|
|
|
|
|
|
[%- method %] |
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
[%- END %] |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
| |
700
|
|
|
|
|
|
|
[%- END %] |
701
|
|
|
|
|
|
|
|
702
|
|
|
|
|
|
|
|
703
|
|
|
|
|
|
|
| >]; |
704
|
|
|
|
|
|
|
[%- id = id + 1 %] |
705
|
|
|
|
|
|
|
[% END %] |
706
|
|
|
|
|
|
|
[%- class_id = id %] |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
[%- first = 1 %] |
709
|
|
|
|
|
|
|
[%- id = 0 %] |
710
|
|
|
|
|
|
|
[%- IF display_inheritance %] |
711
|
|
|
|
|
|
|
[%- FOREACH class = classes %] |
712
|
|
|
|
|
|
|
[%- id = id + 1 %] |
713
|
|
|
|
|
|
|
[%- super = class.name %] |
714
|
|
|
|
|
|
|
[%- NEXT IF !class.subclasses.size -%] |
715
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
[%- IF first -%] |
717
|
|
|
|
|
|
|
node [shape="triangle", fillcolor=yellow, height=0.3, width=0.3]; |
718
|
|
|
|
|
|
|
[%- first = 0 %] |
719
|
|
|
|
|
|
|
[%- END -%] |
720
|
|
|
|
|
|
|
|
721
|
|
|
|
|
|
|
angle_[% id %] [label=""]; |
722
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
[%- super_id = name2id.$super %] |
724
|
|
|
|
|
|
|
class_[% super_id %]:methods -> angle_[% id %] |
725
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
[%- FOREACH child = class.subclasses %] |
727
|
|
|
|
|
|
|
[%- child_id = name2id.$child %] |
728
|
|
|
|
|
|
|
[%- IF !child_id %] |
729
|
|
|
|
|
|
|
class_[% class_id %] [shape=record, label="[% child %]" fillcolor="#f1e1f4", style="filled"]; |
730
|
|
|
|
|
|
|
angle_[% id %] -> class_[% class_id %] |
731
|
|
|
|
|
|
|
[%- class_id = class_id + 1 %] |
732
|
|
|
|
|
|
|
[%- ELSE %] |
733
|
|
|
|
|
|
|
angle_[% id %] -> class_[% child_id %]:title |
734
|
|
|
|
|
|
|
[%- END %] |
735
|
|
|
|
|
|
|
[%- END %] |
736
|
|
|
|
|
|
|
[%- END %] |
737
|
|
|
|
|
|
|
[%- END %] |
738
|
|
|
|
|
|
|
|
739
|
|
|
|
|
|
|
[%- IF moose_roles %] |
740
|
|
|
|
|
|
|
[%- first = 1 %] |
741
|
|
|
|
|
|
|
edge [color=blue, dir=none]; |
742
|
|
|
|
|
|
|
[%- FOREACH class = classes %] |
743
|
|
|
|
|
|
|
[%- id = id + 1 %] |
744
|
|
|
|
|
|
|
[%- NEXT IF !class.consumers.size -%] |
745
|
|
|
|
|
|
|
[%- role = class.name %] |
746
|
|
|
|
|
|
|
[%- role_id = name2id.$role %] |
747
|
|
|
|
|
|
|
[%- IF first %] |
748
|
|
|
|
|
|
|
node [shape="triangle", fillcolor=orange, height=0.3, width=0.3]; |
749
|
|
|
|
|
|
|
[%- first = 0 %] |
750
|
|
|
|
|
|
|
[%- END %] |
751
|
|
|
|
|
|
|
|
752
|
|
|
|
|
|
|
angle_[% id %] [label=""]; |
753
|
|
|
|
|
|
|
class_[% role_id %]:methods -> angle_[% id %] |
754
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
[%- FOREACH consumer = class.consumers %] |
756
|
|
|
|
|
|
|
[%- consumer_id = name2id.$consumer %] |
757
|
|
|
|
|
|
|
angle_[% id %] -> class_[% consumer_id %]:title |
758
|
|
|
|
|
|
|
[%- END %] |
759
|
|
|
|
|
|
|
[%- END %] |
760
|
|
|
|
|
|
|
[%- END %] |
761
|
|
|
|
|
|
|
|
762
|
|
|
|
|
|
|
} |
763
|
|
|
|
|
|
|
_EOC_ |
764
|
|
|
|
|
|
|
|
765
|
|
|
|
|
|
|
1; |
766
|
|
|
|
|
|
|
__END__ |