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 32     32   197 use strict;
  32         57  
  32         784  
3 32     32   139 use warnings;
  32         51  
  32         635  
4 32     32   150 use Data::Printer::Filter;
  32         56  
  32         185  
5 32     32   165 use Data::Printer::Common;
  32         64  
  32         587  
6 32     32   160 use Scalar::Util;
  32         65  
  32         62003  
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              
64             foreach my $parent (@superclasses) {
65             if ($parent eq 'Moo::Object') {
66             Data::Printer::Common::_tryme(sub {
67             my $moo_maker = 'Moo'->_constructor_maker_for($class_name);
68             if (defined $moo_maker) {
69             %attributes = %{ $moo_maker->all_attribute_specs };
70             }
71             });
72             last;
73             }
74             elsif ($parent eq 'Moose::Object') {
75             Data::Printer::Common::_tryme(sub {
76             my $class_meta = $class_name->meta;
77             %attributes = map {
78             $_->name => {
79             index => $_->insertion_order,
80             init_arg => $_->init_arg,
81             is => (defined $_->writer ? 'rw' : 'ro'),
82             reader => $_->reader,
83             required => $_->is_required,
84             }
85             } $class_meta->get_all_attributes();
86             foreach my $role ($class_meta->calculate_all_roles()) {
87             $roles{ $role->name } = 1;
88             }
89             });
90             last;
91             }
92             }
93             if (keys %roles) {
94             $string .= $ddp->newline . 'roles (' . scalar(keys %roles) . '): '
95             . join(', ' => map $ddp->maybe_colorize($_, 'class'), keys %roles)
96             ;
97             }
98              
99             if (keys %attributes) {
100             $string .= $ddp->newline . 'attributes (' . scalar(keys %attributes) . '): '
101             . join(', ' => map $ddp->maybe_colorize($_, 'method'), keys %attributes)
102             ;
103             }
104              
105             my $show_linear_isa = $ddp->class->linear_isa && (
106             ($ddp->class->linear_isa eq 'auto' and @superclasses > 1)
107             or ($ddp->class->linear_isa ne 'auto')
108             );
109              
110             if ($show_linear_isa && @$linear_ISA) {
111             $string .= $ddp->newline . 'linear @ISA: '
112             . join(', ' => map $ddp->maybe_colorize($_, 'class'), @$linear_ISA)
113             ;
114             }
115              
116             if ($ddp->class->show_methods && $ddp->class->show_methods ne 'none') {
117             $string .= _show_methods($class_name, $linear_ISA, \%attributes, $ddp);
118             }
119              
120             if ($ddp->class->show_overloads) {
121             my @overloads = _get_overloads($object);
122             if (@overloads) {
123             $string .= $ddp->newline . 'overloads: ' . join(', ' => @overloads);
124             }
125             }
126              
127             if ($show_internals) {
128             $string .= $ddp->newline
129             . 'internals: '
130             . $ddp->parse_as($reftype, $object)
131             ;
132             }
133              
134             $ddp->outdent;
135             $string .= $ddp->newline . $ddp->maybe_colorize('}', 'brackets');
136             }
137             $ddp->{_class_depth}--;
138              
139             if ($ddp->show_tied and my $tie = ref tied $object) {
140             $string .= " (tied to $tie)";
141             }
142             return $string;
143             };
144              
145             #######################################
146             ### Private auxiliary helpers below ###
147             #######################################
148              
149             sub _get_stringification {
150 50     50   110 my ($ddp, $object, $class_name) = @_;
151 50         218 require overload;
152 50 100 100     155 if (overload::Overloaded($object)
      100        
153             && (overload::Method($object, q(""))
154             || overload::Method($object, q(0+))
155             )
156             ) {
157 3         296 my $string;
158 3     3   15 my $error = Data::Printer::Common::_tryme(sub { $string = '' . $object });
  3         72  
159 3 50       11 if ($error) {
160 0         0 Data::Printer::Common::_warn(
161             $ddp,
162             "string/number overload error for object $class_name: $error"
163             );
164             }
165             else {
166 3         10 return $string;
167             }
168             }
169 47         2395 foreach my $method (qw(as_string stringify to_string)) {
170 138 100       457 if ($object->can($method)) {
171 2         3 my $string;
172 2     2   9 my $error = Data::Printer::Common::_tryme(sub { $string = $object->$method });
  2         8  
173 2 50       7 if ($error) {
174 0         0 Data::Printer::Common::_warn(
175             $ddp,
176             "error stringifying object $class_name with $method\(\): $error"
177             );
178             }
179             else {
180 2         5 return $string;
181             }
182             }
183             }
184 45         89 return;
185             }
186              
187             # returns array of all overloads in class;
188             sub _get_overloads {
189 37     37   61 my ($object) = @_;
190 37         149 require overload;
191 37 100       89 return () unless overload::Overloaded($object);
192 2         134 return sort grep overload::Method($object, $_),
193             map split(/\s+/), values %overload::ops;
194             }
195              
196             sub _show_methods {
197 37     37   76 my ($class_name, $linear_ISA, $attributes, $ddp) = @_;
198              
199 37         127 my %methods = ( public => {}, private => {} );
200 37         114 my @all_methods = map _methods_of(
201             $_, Data::Printer::Common::_get_namespace($_)
202             ), @$linear_ISA;
203 37         99 my $show_methods = $ddp->class->show_methods;
204 37         79 my $show_inherited = $ddp->class->inherited;
205 37         56 my %seen_method_name;
206 37         60 foreach my $method (@all_methods) {
207 224         316 my ($package_string, $method_string) = @$method;
208 224 100       344 next if exists $attributes->{$method_string};
209 222 100       382 next if $seen_method_name{$method_string}++;
210 196 100       285 next if $method_string eq '__ANON__'; # anonymous subs don't matter here.
211 195 100       372 my $type = substr($method_string, 0, 1) eq '_' ? 'private' : 'public';
212 195 100       281 if ($package_string eq $class_name) {
213 104 100 100     210 next unless $show_methods eq 'all' || $show_methods eq $type;
214 91         168 $methods{$type}{$method_string} = undef;
215             }
216             else {
217 91 100 100     247 next unless $show_inherited eq 'all' || $show_inherited eq $type;
218 47         90 $methods{$type}{$method_string} = $package_string;
219             }
220             }
221 37         57 my $string = '';
222 37         53 foreach my $type (qw(public private)) {
223 74 100 100     200 next unless $show_methods eq 'all' or $show_methods eq $type
      66        
      100        
224             or $show_inherited eq 'all' or $show_inherited eq $type
225             ;
226 71 100       153 if ($ddp->class->format_inheritance eq 'string') {
227 4         8 my @method_list = keys %{$methods{$type}};
  4         14  
228 4 100 66     9 @method_list = Data::Printer::Common::_nsort(@method_list)
229             if $ddp->class->sort_methods && @method_list;
230              
231 4         12 $string .= $ddp->newline . "$type methods (" . scalar(@method_list) . ')';
232 4 100       12 if (@method_list) {
233             $string .= ': '
234             . join(', ' => map {
235 3         8 $ddp->maybe_colorize(
236 12 100       34 $_ . (defined $methods{$type}{$_} ? " ($methods{$type}{$_})" : ''),
237             'method'
238             )
239             } @method_list)
240             ;
241             }
242             }
243             else { # 'lines'
244             # first we convert our hash to { pkg => [ @methods ] }
245 67         94 my %lined_methods;
246             my @base_methods;
247 67         96 my $total_methods = 0;
248 67         108 foreach my $method (keys %{$methods{$type}}) {
  67         189  
249 126         173 my $pkg_name = $methods{$type}{$method};
250 126 100       180 if (defined $pkg_name) {
251 41         44 push @{ $lined_methods{$pkg_name} }, $method;
  41         65  
252             }
253             else {
254 85         114 push @base_methods, $method;
255             }
256 126         187 $total_methods++;
257             }
258              
259             # then we print them, starting with our own methods:
260 67 100 66     156 @base_methods = Data::Printer::Common::_nsort(@base_methods)
261             if $ddp->class->sort_methods && @base_methods;
262              
263 67 100       153 $string .= $ddp->newline . "$type methods ($total_methods)"
264             . ($total_methods ? ':' : '')
265             ;
266 67 100       157 if (@base_methods) {
267             my $base_string = join(', ' => map {
268 39         66 $ddp->maybe_colorize($_, 'method')
  85         162  
269             } @base_methods);
270 39         116 $ddp->indent;
271             # newline only if we have parent methods to show:
272 39 100       106 $string .= (keys %lined_methods ? $ddp->newline : ' ') . $base_string;
273 39         92 $ddp->outdent;
274             }
275 67         181 foreach my $pkg (sort keys %lined_methods) {
276 22         58 $ddp->indent;
277 22         58 $string .= $ddp->newline . "$pkg:";
278 22 50       51 @{$lined_methods{$pkg}} = Data::Printer::Common::_nsort(@{$lined_methods{$pkg}})
  22         44  
  22         53  
279             if $ddp->class->sort_methods;
280 22         65 $ddp->indent;
281             $string .= $ddp->newline . join(', ' => map {
282 41         82 $ddp->maybe_colorize($_, 'method')
283 22         51 } @{$lined_methods{$pkg}}
  22         43  
284             );
285 22         58 $ddp->outdent;
286 22         34 $ddp->outdent;
287             }
288             }
289             }
290              
291 37         198 return $string;
292             }
293              
294             sub _methods_of {
295 70     70   245 require B;
296 70         114 my ($class_name, $namespace) = @_;
297 70         91 my @methods;
298 70         127 foreach my $subref (_get_all_subs_from($class_name, $namespace)) {
299 224 50       383 next unless $subref;
300 224         453 my $m = B::svref_2object($subref);
301 224 50 33     776 next unless $m && $m->isa('B::CV');
302 224         394 my $gv = $m->GV;
303 224 50 33     1105 next unless $gv && !$gv->isa('B::Special') && $gv->NAME;
      33        
304 224         881 push @methods, [ $gv->STASH->NAME, $gv->NAME ];
305             }
306 70         259 return @methods;
307             }
308              
309             sub _get_all_subs_from {
310 70     70   121 my ($class_name, $namespace) = @_;
311 70         94 my @subs;
312 70         205 foreach my $key (keys %$namespace) {
313             # perlsub says any sub starting with '(' is reserved for overload,
314             # so we skip those:
315 511 100       1789 next if substr($key, 0, 1) eq '(';
316 502 100 66     904 if (
317             # any non-typeglob in the symbol table is a constant or stub
318             ref(\$namespace->{$key}) ne 'GLOB'
319             # regular subs are stored in the CODE slot of the typeglob
320 502         1589 || defined(*{$namespace->{$key}}{CODE})
321             ) {
322 224         577 push @subs, $key;
323             }
324             }
325 70         129 my @symbols;
326 70         91 foreach my $sub (@subs) {
327 224         404 push @symbols, Data::Printer::Common::_get_symbol($class_name, $namespace, $sub, 'CODE');
328             }
329 70         149 return @symbols;
330             }
331              
332             1;