File Coverage

blib/lib/CPANfile/Parse/PPI.pm
Criterion Covered Total %
statement 94 95 98.9
branch 52 58 89.6
condition 11 12 91.6
subroutine 12 12 100.0
pod 1 1 100.0
total 170 178 95.5


line stmt bran cond sub pod time code
1             package CPANfile::Parse::PPI;
2             $CPANfile::Parse::PPI::VERSION = '0.05';
3             # ABSTRACT: Parse Is with PPI
4              
5 11     11   4538 use strict;
  11         72  
  11         283  
6 11     11   47 use warnings;
  11         18  
  11         303  
7              
8 11     11   47 use Carp qw(carp croak);
  11         17  
  11         981  
9 11     11   61 use List::Util qw(first);
  11         18  
  11         1411  
10 11     11   5423 use Moo;
  11         123811  
  11         53  
11 11     11   36975 use PPI;
  11         1065824  
  11         8551  
12              
13             my $strict;
14              
15             has meta => (
16             is => 'ro',
17             default => sub { +{} },
18             isa => sub {
19             die if 'HASH' ne ref $_[0];
20             }
21             );
22              
23             has modules => (
24             is => 'ro',
25             isa => sub {
26             die if 'ARRAY' ne ref $_[0];
27             }
28             );
29              
30              
31             sub BUILDARGS {
32 30     30 1 224662 my ($class, $file_or_code) = @_;
33              
34 30         107 my ($meta, @modules) = _parse( $file_or_code );
35              
36             return {
37 27         17751 modules => \@modules,
38             meta => $meta,
39             };
40             }
41              
42             sub import {
43 11 100   11   86 $strict = 1 if grep{ $_ eq '-strict' }@_;
  14         365  
44             }
45              
46             sub _parse {
47 30     30   64 my ($file_or_code) = @_;
48              
49 30         210 my $doc = PPI::Document->new( $file_or_code );
50              
51             # 'feature' and 'on' are handled separately
52 30         333142 my @bindings = qw(
53             mirror osname
54             requires recommends conflicts suggests
55             test_requires author_requires configure_requires build_requires
56             );
57              
58             my $requires = $doc->find(
59             sub {
60 3592 100   3592   32160 $_[1]->isa('PPI::Token::Word') and do {
61 409         664 my $content = $_[1]->content;
62 409         1809 first { $content eq $_ } @bindings;
  2035         2852  
63             }
64             }
65 30         312 );
66              
67 30 50       398 return if !$requires;
68              
69 30         53 my @modules;
70 30         180 my $meta = {};
71              
72             REQUIRED:
73 30 50       78 for my $required ( @{ $requires || [] } ) {
  30         109  
74             # 'mirror' can be an attribute for "requires" as well as a keyword
75             # _scan_attrs should have removed all 'mirrors' that are used as
76             # an attribute for 'requires'. So skip those PPI nodes...
77 297 50       739 next REQUIRED if !$required;
78              
79 297         628 my $value = $required->snext_sibling;
80              
81 297         4919 my $stage = '';
82 297         540 my $type = $required->content;
83              
84 297 100 66     1498 if ( $type eq 'mirror' or $type eq 'osname' ) {
85 2 50       16 push @{ $meta->{$type} }, $value->content if $value;
  0         0  
86             }
87              
88 297 100       705 if ( -1 != index $type, '_' ) {
89 2         7 ($stage, $type) = split /_/, $type, 2;
90 2 100       6 $stage = 'develop' if $stage eq 'author';
91             }
92              
93 297         495 my %attr = _scan_attrs( $required, $type );
94              
95 297 100       745 next REQUIRED if !$value;
96              
97 295 100       788 my $can_string = $value->can('string') ? 1 : 0;
98 295 100       695 my $prereq = $can_string ?
99             $value->string :
100             $value->content;
101              
102             #next REQUIRED if $prereq eq 'perl';
103              
104 295 100 100     2647 if (
105             $value->isa('PPI::Token::Symbol') ||
106             $prereq =~ m{\A[^A-Za-z]}
107             ) {
108 6 100       100 carp 'Cannot handle dynamic code' if !$strict;
109 6 100       2301 croak 'Cannot handle dynamic code' if $strict;
110              
111 3         12 next REQUIRED;
112             }
113              
114 289         366 my $parent_node = $value;
115              
116             PARENT:
117 289         326 while ( 1 ) {
118 579         967 $parent_node = $parent_node->parent;
119 579 50       2150 last PARENT if !$parent_node;
120 579 100       1363 last PARENT if $parent_node->isa('PPI::Document');
121              
122 421 100       886 if ( $parent_node->isa('PPI::Structure::Block') ) {
123 132         213 $parent_node = $parent_node->parent;
124              
125 132 100   154   652 my ($on) = $parent_node->find_first( sub { $_[1]->isa('PPI::Token::Word') && $_[1]->content eq 'on' } );
  154         2620  
126              
127 132 100       1715 next PARENT if !$on;
128              
129             # TODO: Check for "feature"
130              
131 131         263 my $word = $on->snext_sibling;
132 131 100       2353 $stage = $word->can('string') ? $word->string : $word->content;
133            
134 131         632 last PARENT;
135             }
136             }
137              
138 289         347 my $version = '';
139 289         450 my $sibling = $value->snext_sibling;
140             SIBLING:
141 289         4733 while ( 1 ) {
142 357 100       710 last SIBLING if !$sibling;
143              
144 289 100       642 do { $sibling = $sibling->snext_sibling; next SIBLING } if !$sibling->isa('PPI::Token::Operator');
  68         129  
  68         1185  
145              
146 221         386 my $value = $sibling->snext_sibling;
147 221 100       4087 last SIBLING if !$value;
148              
149 217 100       665 $version = $value->can('string') ? $value->string : $value->content;
150              
151 217         1010 last SIBLING;
152             }
153              
154 289         1276 push @modules, {
155             name => $prereq,
156             version => $version,
157             type => $type,
158             stage => $stage,
159             %attr,
160             };
161             }
162              
163 27         196 return $meta, @modules;
164             }
165              
166             sub _scan_attrs {
167 297     297   494 my ($required, $type) = @_;
168              
169 297 100 100     586 return if $type ne 'requires' && $type ne 'recommends';
170              
171 294         519 my $sibling = $required->snext_sibling;
172              
173 294         4935 my %attr;
174             my @to_delete;
175 294         0 my $delete;
176              
177 294         638 while ( $sibling ) {
178 1092         15983 my $content = $sibling->content;
179 1092 100 100     4706 if ( $content eq 'mirror' or $content eq 'dist' ) {
180 8         11 $delete = 1;
181 8         16 my $value_node = $sibling->snext_sibling->snext_sibling;
182 8 50       373 $attr{$content} = $value_node->can('string') ?
183             $value_node->string :
184             $value_node->content;
185             }
186              
187 1092 100       1573 push @to_delete, $sibling if $delete;
188 1092         1863 $sibling = $sibling->snext_sibling;
189             }
190              
191 294         6317 $_->remove for @to_delete;
192              
193 294         1605 return %attr;
194             }
195              
196             1;
197              
198             __END__