File Coverage

blib/lib/Perl/PrereqScanner/Scanner/Catalyst.pm
Criterion Covered Total %
statement 83 83 100.0
branch 25 34 73.5
condition 12 18 66.6
subroutine 7 7 100.0
pod 0 1 0.0
total 127 143 88.8


line stmt bran cond sub pod time code
1             package Perl::PrereqScanner::Scanner::Catalyst;
2              
3             # ABSTRACT: Plugin for Perl::PrereqScanner looking for Catalyst plugin/action modules
4              
5 2     2   125076 use strict;
  2         5  
  2         56  
6 2     2   10 use warnings;
  2         4  
  2         76  
7              
8 2     2   11 use Moose;
  2         4  
  2         11  
9             with 'Perl::PrereqScanner::Scanner';
10              
11 2     2   11468 use String::RewritePrefix;
  2         6  
  2         17  
12              
13             # see Catalyst::Util::resolve_namespace
14             sub _full_plugin_name {
15 7     7   25 my ( $self, $catalyst_app_class, @plugin_classes ) = @_;
16              
17 7 50       19 my $appnamespace =
18             $catalyst_app_class ? "${catalyst_app_class}::Plugin" : undef;
19 7         12 my $namespace = 'Catalyst::Plugin';
20              
21 7 50       38 return String::RewritePrefix->rewrite(
22             {
23             q[] => qq[${namespace}::],
24             q[+] => q[],
25             (
26             defined $appnamespace
27             ? ( q[~] => qq[${appnamespace}::] )
28             : ()
29             ),
30             },
31             @plugin_classes
32             );
33             }
34              
35             sub scan_for_prereqs {
36 2     2 0 94820 my ( $self, $ppi_doc, $req ) = @_;
37              
38             # we store the Catalyst app namespace only if it's file scoped
39 2         5 my $catalyst_app_class;
40 2   50     10 my $packages = $ppi_doc->find('PPI::Statement::Package') || [];
41 2 50       6840 if ( @$packages == 1 ) {
42 2         14 $catalyst_app_class = $packages->[0]->namespace;
43             }
44              
45             # use Catalyst ...
46 2   50     61 my $includes = $ppi_doc->find('Statement::Include') || [];
47 2         6903 for my $node (@$includes) {
48              
49             # inheritance
50 6 100       102 if ( $node->module eq 'Catalyst' ) {
51             my @meat = grep {
52 1 50       20 $_->isa('PPI::Token::QuoteLike::Words')
  1         41  
53             || $_->isa('PPI::Token::Quote')
54             } $node->arguments;
55              
56 1         3 my @args = map { $self->_q_contents($_) } @meat;
  1         5  
57              
58 1         78 while (@args) {
59 4         71 my $arg = shift @args;
60              
61 4 100       96 if ( $arg !~ /^\-/ ) {
62 3         8 my $module =
63             $self->_full_plugin_name( $catalyst_app_class, $arg );
64 3         190 $req->add_minimum( $module => 0 );
65             }
66             }
67             }
68             }
69              
70             # It's also possible to specify plugins via Catalyat::setup(_plugins)?
71             # To cover this case, we would firstly make sure the package extends
72             # Catalyst, and we look for calls like __PACKAGE__->setup(_plugins)?
73              
74             # for "extends 'Catalyst';"
75              
76 2         68 my $inherits_catalyst = 0;
77             {
78             # from Perl::PrereqScanner::Moose
79 2         5 my @chunks =
80              
81             # PPI::Statement
82             # PPI::Token::Word
83             # PPI::Structure::List
84             # PPI::Statement::Expression
85             # PPI::Token::Quote::Single
86             # PPI::Token::Structure
87              
88 2         32 map { [ $_->schildren ] }
89 24         278 grep { $_->child(0)->literal =~ m{\Aextends\z} }
90 36         7674 grep { $_->child(0)->isa('PPI::Token::Word') }
91 2 50       17 @{ $ppi_doc->find('PPI::Statement') || [] };
  2         9  
92              
93 2         42 foreach my $hunk (@chunks) {
94             my @classes =
95 2         82 grep { Params::Util::_CLASS($_) }
96 2         12 map { $self->_q_contents($_) }
97             grep {
98 2 100       25 $_->isa('PPI::Token::Quote')
  5         34  
99             || $_->isa('PPI::Token::QuoteLike::Words')
100             } @$hunk;
101              
102 2 100       22 if ( grep { $_ eq 'Catalyst' } @classes ) {
  2         11  
103 1         3 $inherits_catalyst = 1;
104 1         3 last;
105             }
106             }
107             }
108              
109             # for __PACKAGE__->setup or __PACKAGE__->setup_plugins
110 2 100       6 if ($inherits_catalyst) {
111             my @meat =
112             grep {
113 6 100       33 $_->isa('PPI::Token::Quote')
114             || $_->isa('PPI::Token::QuoteLike::Words')
115             }
116 2         11 map { $_->schildren } # $_ isa PPI::Statement::Expression
117 2         21 grep { $_->isa('PPI::Statement::Expression') }
118 2         58 map { $_->schild(3)->schildren } # $_ isa PPI::Structure::List
119             grep {
120             # make sure it's calling Catalyst::setup or setup_plugins
121             (
122 3 50 33     129 $_->schild(0)->literal eq '__PACKAGE__'
123             or $_->schild(0)->literal eq "$catalyst_app_class"
124             )
125             and $_->schild(2)->literal =~ /^(?:setup|setup_plugins)$/
126             }
127             grep {
128             # make sure it's a method call
129 7 100 66     244 $_->schild(0)->isa('PPI::Token::Word')
      100        
      66        
130             and $_->schild(1)->isa('PPI::Token::Operator')
131             and $_->schild(1)->content eq '->'
132             and $_->schild(2)->isa('PPI::Token::Word')
133             and $_->schild(3)->isa('PPI::Structure::List')
134             }
135 14         2794 grep { $_->schildren > 3 }
136 1 50       2 @{ $ppi_doc->find('PPI::Statement') || [] };
  1         4  
137              
138 1         4 my @args = map { $self->_q_contents($_) } @meat;
  3         30  
139 1         71 while (@args) {
140 4         85 my $arg = shift @args;
141              
142 4 50       10 if ( $arg !~ /^\-/ ) {
143 4         8 my $module =
144             $self->_full_plugin_name( $catalyst_app_class, $arg );
145 4         189 $req->add_minimum( $module => 0 );
146             }
147             }
148             }
149              
150             # for ActionClass attributes
151 2   100     38 my $subs = $ppi_doc->find('PPI::Statement::Sub') || [];
152 2         7930 for my $sub_node (@$subs) {
153             my @attributes =
154 4         20 grep { $_->isa('PPI::Token::Attribute') } $sub_node->schildren();
  19         156  
155              
156 4         14 for my $attr_node (@attributes) {
157 4         17 my $attr_content = $attr_node->content;
158 4         29 $attr_content =~ s/\s+//g;
159 4 100       23 if ( $attr_content =~ /ActionClass\(([^\)]+)\)/ ) {
160 1         9 my $ppi_action_class = PPI::Document->new( \$1 );
161             my $quotes = $ppi_action_class->find(
162             sub {
163 2     2   33 my ( $doc, $node ) = @_;
164 2 50       18 $node->isa('PPI::Token::QuoteLike::Words')
165             || $node->isa('PPI::Token::Quote');
166             }
167 1         873 );
168             my @action_class_names =
169 1         13 map { $self->_q_contents($_) } @$quotes;
  1         7  
170 1         24 for (@action_class_names) {
171 1         3 my $module = "Catalyst::Action::$_";
172 1         8 $req->add_minimum( $module => 0 );
173             }
174             }
175             }
176             }
177             }
178              
179             1;
180              
181             __END__
182              
183             =pod
184              
185             =encoding UTF-8
186              
187             =head1 NAME
188              
189             Perl::PrereqScanner::Scanner::Catalyst - Plugin for Perl::PrereqScanner looking for Catalyst plugin/action modules
190              
191             =head1 VERSION
192              
193             version 0.002
194              
195             =head1 SYNOPSIS
196              
197             use Perl::PrereqScanner;
198             my $scanner = Perl::PrereqScanner->new(
199             { extra_scanners => [ qw(Catalyst) ] }
200             );
201             my $prereqs = $scanner->scan_file( $path );
202              
203             =head1 DESCRIPTION
204              
205             This module is a scanner plugin for Perl::PrereqScanner. It looks for
206             use of Catalyst plugin and action modules in the code.
207              
208             =head1 SEE ALSO
209              
210             L<Perl::PrereqScanner>
211              
212             L<Catalyst>
213              
214             =head1 AUTHOR
215              
216             Stephan Loyd <sloyd@cpan.org>
217              
218             =head1 COPYRIGHT AND LICENSE
219              
220             This software is copyright (c) 2017 by Stephan Loyd.
221              
222             This is free software; you can redistribute it and/or modify it under
223             the same terms as the Perl 5 programming language system itself.
224              
225             =cut