File Coverage

blib/lib/PPIx/QuoteLike/Dumper.pm
Criterion Covered Total %
statement 126 164 76.8
branch 44 94 46.8
condition 6 14 42.8
subroutine 26 29 89.6
pod 5 5 100.0
total 207 306 67.6


line stmt bran cond sub pod time code
1             package PPIx::QuoteLike::Dumper;
2              
3 2     2   1483 use 5.006;
  2         6  
4              
5 2     2   10 use strict;
  2         3  
  2         40  
6 2     2   7 use warnings;
  2         11  
  2         65  
7              
8 2     2   9 use Carp;
  2         4  
  2         101  
9 2     2   487 use PPI::Document;
  2         103496  
  2         64  
10 2     2   513 use PPIx::QuoteLike;
  2         3  
  2         55  
11 2     2   10 use PPIx::QuoteLike::Constant qw{ @CARP_NOT };
  2         19  
  2         191  
12 2     2   11 use PPIx::QuoteLike::Utils qw{ __instance };
  2         3  
  2         75  
13 2     2   10 use Scalar::Util ();
  2         2  
  2         65  
14              
15             our $VERSION = '0.023';
16              
17 2     2   8 use constant SCALAR_REF => ref \0;
  2         3  
  2         3795  
18              
19             {
20             my $default = {
21             encoding => undef,
22             file => undef,
23             indent => 2,
24             locations => 0,
25             margin => 0,
26             perl_version => 0,
27             ppi => 0,
28             short => 0,
29             significant => 0,
30             tokens => 0,
31             variables => 0,
32             };
33              
34             sub new {
35 60     60 1 519 my ( $class, $source, %arg ) = @_;
36              
37             my $self = {
38 60         68 %{ $default },
  60         265  
39             object => undef,
40             source => $source,
41             };
42              
43 60         95 foreach my $key ( keys %{ $default } ) {
  60         161  
44             defined $arg{$key}
45 660 100       960 and $self->{$key} = $arg{$key};
46             }
47              
48             $self->{object} = _isa( $source, 'PPIx::QuoteLike' ) ? $source :
49             PPIx::QuoteLike->new( $source,
50             __instance( $source, 'PPI::Element' ) ? () : (
51             location => [ 1, 1, 1, 1, -f $source ? $source :
52             undef ],
53             ),
54 60 50       105 map { $_ => $arg{$_} } qw{ encoding },
  60 100       157  
    50          
    100          
55             )
56             or return;
57              
58 8   33     44 return bless $self, ref $class || $class;
59             }
60             }
61              
62             sub dump : method { ## no critic (ProhibitBuiltinHomonyms)
63 2     2 1 86 my ( $class, $source, %arg ) = @_;
64 2         4 my $rslt;
65 2   50     11 my $margin = ' ' x ( $arg{margin} || 0 );
66 2         3 my $none = delete $arg{none};
67 2         8 foreach my $obj ( $class->_source_to_dumpers( $source, %arg ) ) {
68 7         22 my $src = $obj->{object}->source();
69 7         25 $rslt .= "\n$margin$src";
70 7 100 66     36 if ( _isa( $src, 'PPI::Element' ) and my $loc = $src->location() ) {
71             $rslt .= sprintf ' %s line %d column %d',
72 6         105 _dor( $loc->[4], $obj->{file}, '?' ),
73             $loc->[0], $loc->[1];
74             }
75 7         20 $rslt .= "\n" . $obj->string();
76             }
77 2 50       57 defined $rslt
78             and return $rslt;
79 0 0       0 defined $none
80             or return;
81 0         0 $none =~ s/ (?: \A | (?
82 0         0 return $none;
83             }
84              
85             sub list {
86 7     7 1 9 my ( $self ) = @_;
87 7         9 my $indent;
88 7         9 my $obj = $self->{object};
89 7         9 my @rslt;
90             my $selector;
91 7 50       13 if ( $self->{tokens} ) {
92 0         0 $indent = '';
93             $selector = sub { return @{
94 0 0   0   0 $obj->find( 'PPIx::QuoteLike::Token' ) || [] };
  0         0  
95 0         0 };
96             } else {
97 7         13 $indent = ' ' x $self->{indent};
98             my $string = sprintf '%s%s...%s',
99 7         14 map { _format_content( $obj, $_ ) }
  21         28  
100             qw{ type start finish };
101 7         17 push @rslt,
102             join "\t", $self->_class_name( $obj ), $string,
103             _format_attr( $obj, qw{ encoding failures interpolates
104             indentation } ),
105             $self->_perl_version( $obj ),
106             $self->_variables( $obj ),
107             ;
108 7     7   26 $selector = sub { return $obj->children() };
  7         14  
109             }
110 7         14 foreach my $elem ( $selector->() ) {
111             $self->{significant}
112 17 50 33     34 and not $elem->significant()
113             and next;
114             my $locn = $self->{locations} ?
115 17 0       30 __instance( $elem, 'PPIx::QuoteLike::Token' ) ?
    50          
116             sprintf '[ % 4d, % 3d, % 3d ] ',
117             $elem->logical_line_number(),
118             $elem->column_number(),
119             $elem->visual_column_number() :
120             ' ' x 19 :
121             '';
122 17         30 my @line = (
123             $self->_class_name( $elem ),
124             _quote( $elem->content() ),
125             $self->_perl_version( $elem ),
126             $self->_variables( $elem ),
127             );
128 17         28 my @ppi;
129 17 50       26 @ppi = $self->_ppi( $elem )
130             and shift @ppi; # Ignore PPI::Document
131 17         27 foreach ( @ppi ) {
132 0 0       0 if ( $self->{locations} ) {
133 0 0       0 s/ ( [0-9]+ \s+ \] ) /$1 /smxg
134             or substr $_, 0, 0, ' ';
135             } else {
136 0         0 substr $_, 0, 0, ' ';
137             }
138             }
139 17         26 my $leader = "$locn$indent";
140 17         41 foreach ( join( "\t", @line ), @ppi ) {
141 17         33 push @rslt, "$leader$_";
142             # $locn = $self->{locations} ? ' ' x 19 : '';
143 17         47 $leader = '';
144             }
145             }
146 7         26 return @rslt;
147             }
148              
149             sub print : method { ## no critic (ProhibitBuiltinHomonyms)
150 0     0 1 0 my ( $self ) = @_;
151 0         0 print $self->string();
152 0         0 return;
153             }
154              
155             sub string {
156 7     7 1 9 my ( $self ) = @_;
157 7         14 my $margin = ' ' x $self->{margin};
158 7         14 return join '', map { "$margin$_\n" } $self->list();
  24         75  
159             }
160              
161             sub _class_name {
162 24     24   32 my ( $self, $obj ) = @_;
163 24         38 my $class = ref $obj;
164             $self->{short}
165 24 50       37 and $class =~ s/ \A PPIx::QuoteLike:: //smx;
166 24         56 return $class;
167             }
168              
169             {
170             # We have to hold a reference to the PPI document until we're done
171             # with all its elements, otherwise they evaporate. Holding it here
172             # works as long as we actually format the dump for all elements
173             # before calling this again.
174             my $doc;
175              
176             sub _doc_to_dumper {
177 1     1   3 my ( $class, $path, %arg ) = @_;
178 1 50       7 $doc = PPI::Document->new( $path )
179             or return;
180             ref $path
181 1 50       7976 or $arg{file} = $path;
182 58         1597 return map { $class->new( $_, %arg ) }
183 1 50       2 @{ $doc->find( 'PPI::Token' ) || [] };
  1         5  
184             }
185             }
186              
187             sub _dor {
188 6     6   12 my @arg = @_;
189 6         9 foreach my $a ( @arg ) {
190 18 100       64 defined $a
191             and return $a;
192             }
193 0         0 return;
194             }
195              
196             sub _format_attr {
197 7     7   16 my ( $obj, @arg ) = @_;
198 7         8 my @rslt;
199 7         9 foreach my $attr ( @arg ) {
200 28 100       60 defined( my $val = $obj->$attr() )
201             or next;
202 14         28 push @rslt, sprintf '%s=%s', $attr, _quote( $val );
203             }
204 7         16 return @rslt;
205             }
206              
207             sub _format_content {
208 21     21   35 my ( $obj, $method, @arg ) = @_;
209 21         36 my @val = map { $_->content() }
210 21 50       59 grep { $_->significant() }
  24         50  
211             $obj->$method( @arg )
212             or return '?';
213 21         59 return join '', @val;
214             }
215              
216             sub _isa {
217 67     67   103 my ( $arg, $class ) = @_;
218 67 100       179 Scalar::Util::blessed( $arg )
219             or return 0;
220 64         257 return $arg->isa( $class );
221             }
222              
223             sub _perl_version {
224 24     24   38 my ( $self, $elem ) = @_;
225             $self->{perl_version}
226 24 100       42 or return;
227 21         42 my $intro = $elem->perl_version_introduced();
228 21         42 my $remov = $elem->perl_version_removed();
229 21 50       57 return defined $remov ? "$intro <= \$] < $remov" : "$intro <= \$]";
230             }
231              
232             sub _ppi {
233 17     17   27 my ( $self, $elem ) = @_;
234              
235             $self->{ppi}
236 17 50 33     47 and $elem->can( 'ppi' )
237             or return;
238              
239 0         0 require PPI::Dumper;
240              
241             # PPI::Dumper reports line_number(), but I want
242             # logical_line_number(). There is no configuration for this, but the
243             # interface is public, so I mung it to do what I want.
244 0         0 my $locn = PPI::Element->can( 'location' );
245             local *PPI::Element::location = sub {
246 0     0   0 my $loc = $locn->( @_ );
247 0         0 $loc->[0] = $loc->[3];
248 0         0 return $loc;
249 0         0 };
250              
251             my $dumper = PPI::Dumper->new( $elem->ppi(),
252 0         0 map { $_ => $self->{$_} } qw{ indent locations },
  0         0  
253             );
254              
255 0         0 return $dumper->list();
256             }
257              
258             sub _quote {
259 31     31   45 my ( $val ) = @_;
260 31 50       41 ref $val
261             and $val = $val->content();
262 31 50       52 defined $val
263             or return 'undef';
264 31 100       86 Scalar::Util::looks_like_number( $val )
265             and return $val;
266 17 50       32 if ( $val =~ m/ \A << /smx ) {
267 0         0 chomp $val;
268 0         0 return "<<'__END_OF_HERE_DOCUMENT'
269             $val
270             __END_OF_HERE_DOCUMENT
271             ";
272             }
273              
274 17         35 $val =~ s/ (?= [\\'] )/\\/smxg;
275 17         44 return "'$val'";
276             }
277              
278             sub _source_to_dumpers {
279 2     2   4 my ( $class, $path, %arg ) = @_;
280 2 50       10 if ( Scalar::Util::blessed( $path ) ) {
    100          
281 0 0       0 if ( _isa( $path, 'PPI::Node' ) ) {
    0          
282             return map {
283 0 0       0 PPIx::QuoteLike->handles( $_ ) ?
284             $class->new( $_, %arg ) : () }
285 0 0       0 @{ $path->find( 'PPI::Token' ) || [] };
  0         0  
286             } elsif ( _isa( $path, 'PPI::Element' ) ) {
287 0 0       0 PPIx::QuoteLike->handles( $path )
288             and return $class->new( $path, %arg );
289             }
290             } elsif ( my $ref = ref $path ) {
291 1 50       4 SCALAR_REF eq $ref
292             or return;
293 1         3 return $class->_doc_to_dumper( $path, %arg );
294             } else {
295 1 50       35 -f $path
296             or return $class->new( $path, %arg );
297 0 0       0 -T _
298             or return;
299 0 0       0 unless ( $path =~ m/ [.] (?: (?i: pl ) | pm | t ) \z /smx ) {
300 0 0       0 open my $fh, '<', $path
301             or return;
302 0 0       0 defined( local $_ = <$fh> )
303             or return;
304 0         0 close $fh;
305 0 0       0 m/ perl /smx
306             or return;
307             }
308 0         0 return $class->_doc_to_dumper( $path, %arg );
309             }
310 0         0 return;
311             }
312              
313             sub _variables {
314 24     24   35 my ( $self, $elem ) = @_;
315              
316             $self->{variables}
317 24 100       41 or return;
318              
319 21 100       48 my @var = $elem->variables()
320             or return;
321              
322 6         27 return join ',', sort @var;
323             }
324              
325             1;
326              
327             __END__