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