File Coverage

blib/lib/Data/Printer/Filter/GenericClass.pm
Criterion Covered Total %
statement 116 118 98.3
branch 50 56 89.2
condition 29 39 74.3
subroutine 12 12 100.0
pod n/a
total 207 225 92.0


line stmt bran cond sub pod time code
1             package Data::Printer::Filter::GenericClass;
2 34     34   230 use strict;
  34         74  
  34         933  
3 34     34   219 use warnings;
  34         69  
  34         797  
4 34     34   170 use Data::Printer::Filter;
  34         66  
  34         190  
5 34     34   168 use Data::Printer::Common;
  34         87  
  34         760  
6 34     34   204 use Scalar::Util;
  34         67  
  34         85173  
7              
8             filter '-class' => sub {
9             my ($object, $ddp) = @_;
10              
11             # if the class implements its own Data::Printer method, we use it!
12             if ($ddp->class_method and my $method = $object->can( $ddp->class_method )) {
13             return $method->($object, $ddp) if ref $method eq 'CODE';
14             }
15              
16             my $class_name = ref $object;
17              
18             # there are many parts of the class filter that require the object's
19             # linear ISA, so we declare it earlier and load it only once:
20             my $linear_ISA = Data::Printer::Common::_linear_ISA_for($class_name, $ddp);
21              
22             # if the object overloads stringification, use it!
23             # except for PDF::API2 which has a destructive stringify()
24             if ($ddp->class->stringify && $class_name ne 'PDF::API2') {
25             my $str = _get_stringification($ddp, $object, $class_name);
26             return $ddp->maybe_colorize("$str ($class_name)", 'class')
27             if defined $str;
28             }
29              
30             # otherwise, do our generic object representation:
31             my $show_reftype = $ddp->class->show_reftype;
32             my $show_internals = $ddp->class->internals;
33             my $reftype;
34             if ($show_reftype || $show_internals) {
35             $reftype = Scalar::Util::reftype($object);
36             $reftype = 'Regexp' if $reftype eq 'REGEXP';
37             }
38              
39             $ddp->{_class_depth}++;
40             my $string = $ddp->maybe_colorize( $class_name, 'class' );
41              
42             if ($show_reftype) {
43             $string .= ' '
44             . $ddp->maybe_colorize('(', 'brackets')
45             . $ddp->maybe_colorize( $reftype, 'class' )
46             . $ddp->maybe_colorize(')', 'brackets');
47             }
48              
49             if ($ddp->class->expand eq 'all' || $ddp->class->expand >= $ddp->{_class_depth}) {
50             $ddp->indent;
51             $string .= ' ' . $ddp->maybe_colorize('{', 'brackets');
52              
53             my @superclasses = Data::Printer::Common::_get_superclasses_for($class_name);
54             if (@superclasses && $ddp->class->parents) {
55             $string .= $ddp->newline . 'parents: '
56             . join(', ', map $ddp->maybe_colorize($_, 'class'), @superclasses)
57             ;
58             }
59             my (%roles, %attributes);
60             if ($INC{'Role/Tiny.pm'} && exists $Role::Tiny::APPLIED_TO{$class_name}) {
61             %roles = %{ $Role::Tiny::APPLIED_TO{$class_name} };
62             }
63             my $is_moose = 0;
64              
65             foreach my $parent (@$linear_ISA) {
66             if ($parent eq 'Moo::Object') {
67             Data::Printer::Common::_tryme(sub {
68             my $moo_maker = 'Moo'->_constructor_maker_for($class_name);
69             if (defined $moo_maker) {
70             %attributes = %{ $moo_maker->all_attribute_specs };
71             }
72             });
73             last;
74             }
75             elsif ($parent eq 'Moose::Object') {
76             Data::Printer::Common::_tryme(sub {
77             my $class_meta = $class_name->meta;
78             $is_moose = 1;
79             %attributes = map {
80             $_->name => {
81             index => $_->insertion_order,
82             init_arg => $_->init_arg,
83             is => (defined $_->writer ? 'rw' : 'ro'),
84             reader => $_->reader,
85             required => $_->is_required,
86             }
87             } $class_meta->get_all_attributes();
88             foreach my $role ($class_meta->calculate_all_roles()) {
89             $roles{ $role->name } = 1;
90             }
91             });
92             last;
93             }
94             elsif ($parent eq 'Object::Pad::UNIVERSAL') {
95             Data::Printer::Common::_tryme(sub {
96             my $meta = Object::Pad::MOP::Class->for_class( $class_name );
97             %attributes = map {
98             $_->name . $_->value($class_name) => {
99             }
100             } $meta->fields;
101             %roles = map { $_->name => 1 } $meta->direct_roles;
102             });
103             }
104             }
105             if ($ddp->class->show_methods ne 'none') {
106             if (my @role_list = keys %roles) {
107             @role_list = Data::Printer::Common::_nsort(@role_list)
108             if @role_list && $ddp->class->sort_methods;
109             $string .= $ddp->newline . 'roles (' . scalar(@role_list) . '): '
110             . join(', ' => map $ddp->maybe_colorize($_, 'class'), @role_list)
111             ;
112             }
113              
114             if (my @attr_list = keys %attributes) {
115             @attr_list = Data::Printer::Common::_nsort(@attr_list)
116             if @attr_list && $ddp->class->sort_methods;
117             $string .= $ddp->newline . 'attributes (' . scalar(@attr_list) . '): '
118             . join(', ' => map $ddp->maybe_colorize($_, 'method'), @attr_list)
119             ;
120             }
121             }
122              
123             my $show_linear_isa = $ddp->class->linear_isa && (
124             ($ddp->class->linear_isa eq 'auto' and @superclasses > 1)
125             or ($ddp->class->linear_isa ne 'auto')
126             );
127              
128             if ($show_linear_isa && @$linear_ISA) {
129             $string .= $ddp->newline . 'linear @ISA: '
130             . join(', ' => map $ddp->maybe_colorize($_, 'class'), @$linear_ISA)
131             ;
132             }
133              
134             if ($ddp->class->show_methods ne 'none') {
135             $string .= _show_methods($class_name, $linear_ISA, \%attributes, $ddp);
136             if ($is_moose && $ddp->class->show_wrapped) {
137             my $modified = '';
138             my $modified_count = 0;
139             $ddp->indent;
140             for my $method ($class_name->meta->get_all_methods) {
141             if (ref $method eq 'Class::MOP::Method::Wrapped') {
142             foreach my $kind (qw(before around after)) {
143             my $getter_method = $kind . '_modifiers';
144             if (my @modlist = $method->$getter_method) {
145             $modified .= $ddp->newline . $kind . ' ' . $method->name . ': '
146             . (@modlist > 1 ? $ddp->parse(\@modlist) : $ddp->parse($modlist[0]));
147             $modified_count++;
148             }
149             }
150             }
151             }
152             $ddp->outdent;
153             if ($modified_count) {
154             $string .= $ddp->newline . 'method modifiers (' . $modified_count . '):'
155             . $modified;
156             }
157             }
158             }
159              
160             if ($ddp->class->show_overloads) {
161             my @overloads = _get_overloads($object);
162             if (@overloads) {
163             $string .= $ddp->newline . 'overloads: ' . join(', ' => @overloads);
164             }
165             }
166              
167             if ($show_internals) {
168             $string .= $ddp->newline
169             . 'internals: '
170             . $ddp->parse_as($reftype, $object)
171             ;
172             }
173              
174             $ddp->outdent;
175             $string .= $ddp->newline . $ddp->maybe_colorize('}', 'brackets');
176             }
177             $ddp->{_class_depth}--;
178              
179             if ($ddp->show_tied and my $tie = ref tied $object) {
180             $string .= " (tied to $tie)";
181             }
182              
183             return $string;
184             };
185              
186             #######################################
187             ### Private auxiliary helpers below ###
188             #######################################
189              
190             sub _get_stringification {
191 50     50   110 my ($ddp, $object, $class_name) = @_;
192 50         319 require overload;
193 50 100 100     184 if (overload::Overloaded($object)
      100        
194             && (overload::Method($object, q(""))
195             || overload::Method($object, q(0+))
196             )
197             ) {
198 3         360 my $string;
199 3     3   17 my $error = Data::Printer::Common::_tryme(sub { $string = '' . $object });
  3         83  
200 3 50       12 if ($error) {
201 0         0 Data::Printer::Common::_warn(
202             $ddp,
203             "string/number overload error for object $class_name: $error"
204             );
205             }
206             else {
207 3         11 return $string;
208             }
209             }
210 47         3082 foreach my $method (qw(as_string stringify to_string)) {
211 138 100       581 if ($object->can($method)) {
212 2         5 my $string;
213 2     2   13 my $error = Data::Printer::Common::_tryme(sub { $string = $object->$method });
  2         7  
214 2 50       9 if ($error) {
215 0         0 Data::Printer::Common::_warn(
216             $ddp,
217             "error stringifying object $class_name with $method\(\): $error"
218             );
219             }
220             else {
221 2         6 return $string;
222             }
223             }
224             }
225 45         102 return;
226             }
227              
228             # returns array of all overloads in class;
229             sub _get_overloads {
230 37     37   81 my ($object) = @_;
231 37         175 require overload;
232 37 100       113 return () unless overload::Overloaded($object);
233 2         159 return sort grep overload::Method($object, $_),
234             map split(/\s+/), values %overload::ops;
235             }
236              
237             sub _show_methods {
238 37     37   93 my ($class_name, $linear_ISA, $attributes, $ddp) = @_;
239              
240 37         127 my %methods = ( public => {}, private => {} );
241 37         107 my @all_methods = map _methods_of(
242             $_, Data::Printer::Common::_get_namespace($_)
243             ), @$linear_ISA;
244 37         132 my $show_methods = $ddp->class->show_methods;
245 37         82 my $show_inherited = $ddp->class->inherited;
246 37         67 my %seen_method_name;
247 37         73 foreach my $method (@all_methods) {
248 224         413 my ($package_string, $method_string) = @$method;
249 224 100       421 next if exists $attributes->{$method_string};
250 222 100       509 next if $seen_method_name{$method_string}++;
251 196 100       366 next if $method_string eq '__ANON__'; # anonymous subs don't matter here.
252 195 100       392 my $type = substr($method_string, 0, 1) eq '_' ? 'private' : 'public';
253 195 100       384 if ($package_string eq $class_name) {
254 104 100 100     245 next unless $show_methods eq 'all' || $show_methods eq $type;
255 91         216 $methods{$type}{$method_string} = undef;
256             }
257             else {
258 91 100 100     346 next unless $show_inherited eq 'all' || $show_inherited eq $type;
259 47         119 $methods{$type}{$method_string} = $package_string;
260             }
261             }
262 37         73 my $string = '';
263 37         75 foreach my $type (qw(public private)) {
264 74 100 100     222 next unless $show_methods eq 'all' or $show_methods eq $type
      66        
      100        
265             or $show_inherited eq 'all' or $show_inherited eq $type
266             ;
267 71 100       173 if ($ddp->class->format_inheritance eq 'string') {
268 4         8 my @method_list = keys %{$methods{$type}};
  4         16  
269 4 100 66     19 @method_list = Data::Printer::Common::_nsort(@method_list)
270             if @method_list && $ddp->class->sort_methods;
271              
272 4         14 $string .= $ddp->newline . "$type methods (" . scalar(@method_list) . ')';
273 4 100       15 if (@method_list) {
274             $string .= ': '
275             . join(', ' => map {
276 3         9 $ddp->maybe_colorize(
277 12 100       45 $_ . (defined $methods{$type}{$_} ? " ($methods{$type}{$_})" : ''),
278             'method'
279             )
280             } @method_list)
281             ;
282             }
283             }
284             else { # 'lines'
285             # first we convert our hash to { pkg => [ @methods ] }
286 67         108 my %lined_methods;
287             my @base_methods;
288 67         112 my $total_methods = 0;
289 67         100 foreach my $method (keys %{$methods{$type}}) {
  67         221  
290 126         220 my $pkg_name = $methods{$type}{$method};
291 126 100       218 if (defined $pkg_name) {
292 41         56 push @{ $lined_methods{$pkg_name} }, $method;
  41         95  
293             }
294             else {
295 85         143 push @base_methods, $method;
296             }
297 126         221 $total_methods++;
298             }
299              
300             # then we print them, starting with our own methods:
301 67 100 66     211 @base_methods = Data::Printer::Common::_nsort(@base_methods)
302             if @base_methods && $ddp->class->sort_methods;
303              
304 67 100       193 $string .= $ddp->newline . "$type methods ($total_methods)"
305             . ($total_methods ? ':' : '')
306             ;
307 67 100       188 if (@base_methods) {
308             my $base_string = join(', ' => map {
309 39         69 $ddp->maybe_colorize($_, 'method')
  85         184  
310             } @base_methods);
311 39         126 $ddp->indent;
312             # newline only if we have parent methods to show:
313 39 100       119 $string .= (keys %lined_methods ? $ddp->newline : ' ') . $base_string;
314 39         94 $ddp->outdent;
315             }
316 67         227 foreach my $pkg (sort keys %lined_methods) {
317 22         63 $ddp->indent;
318 22         48 $string .= $ddp->newline . "$pkg:";
319 22 50       58 @{$lined_methods{$pkg}} = Data::Printer::Common::_nsort(@{$lined_methods{$pkg}})
  22         55  
  22         72  
320             if $ddp->class->sort_methods;
321 22         73 $ddp->indent;
322             $string .= $ddp->newline . join(', ' => map {
323 41         86 $ddp->maybe_colorize($_, 'method')
324 22         48 } @{$lined_methods{$pkg}}
  22         49  
325             );
326 22         70 $ddp->outdent;
327 22         46 $ddp->outdent;
328             }
329             }
330             }
331              
332 37         237 return $string;
333             }
334              
335             sub _methods_of {
336 70     70   310 require B;
337 70         173 my ($class_name, $namespace) = @_;
338 70         113 my @methods;
339 70         148 foreach my $subref (_get_all_subs_from($class_name, $namespace)) {
340 224 50       461 next unless $subref;
341 224         588 my $m = B::svref_2object($subref);
342 224 50 33     938 next unless $m && $m->isa('B::CV');
343 224         570 my $gv = $m->GV;
344 224 50 33     1376 next unless $gv && !$gv->isa('B::Special') && $gv->NAME;
      33        
345 224         1124 push @methods, [ $gv->STASH->NAME, $gv->NAME ];
346             }
347 70         261 return @methods;
348             }
349              
350             sub _get_all_subs_from {
351 70     70   128 my ($class_name, $namespace) = @_;
352 70         99 my @subs;
353 70         233 foreach my $key (keys %$namespace) {
354             # perlsub says any sub starting with '(' is reserved for overload,
355             # so we skip those:
356 511 100       1083 next if substr($key, 0, 1) eq '(';
357 502 100 66     1146 if (
358             # any non-typeglob in the symbol table is a constant or stub
359             ref(\$namespace->{$key}) ne 'GLOB'
360             # regular subs are stored in the CODE slot of the typeglob
361 502         2064 || defined(*{$namespace->{$key}}{CODE})
362             ) {
363 224         754 push @subs, $key;
364             }
365             }
366 70         134 my @symbols;
367 70         128 foreach my $sub (@subs) {
368 224         489 push @symbols, Data::Printer::Common::_get_symbol($class_name, $namespace, $sub, 'CODE');
369             }
370 70         180 return @symbols;
371             }
372              
373             1;