File Coverage

blib/lib/Perl/Critic/Policy/PreferredModules.pm
Criterion Covered Total %
statement 77 80 96.2
branch 15 22 68.1
condition 4 8 50.0
subroutine 16 16 100.0
pod 2 3 66.6
total 114 129 88.3


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::PreferredModules;
2              
3 2     2   279771 use strict;
  2         12  
  2         60  
4 2     2   11 use warnings;
  2         5  
  2         62  
5              
6 2     2   1357 use Perl::Critic::Utils qw{ :severities :classification :ppi $SEVERITY_MEDIUM $TRUE $FALSE };
  2         270805  
  2         39  
7 2     2   2003 use parent 'Perl::Critic::Policy';
  2         5  
  2         13  
8              
9 2     2   100950 use Perl::Critic::Exception::AggregateConfiguration ();
  2         6  
  2         31  
10 2     2   985 use Perl::Critic::Exception::Configuration::Generic ();
  2         1400  
  2         48  
11              
12 2     2   1059 use Config::INI::Reader ();
  2         62248  
  2         122  
13              
14             sub supported_parameters {
15             return (
16             {
17 6     6 0 1479035 name => 'config',
18             description => 'Config::INI file listing recommendations.',
19             behavior => 'string',
20             },
21             );
22             }
23              
24 2     2   16 use constant default_severity => $SEVERITY_MEDIUM;
  2         6  
  2         209  
25 2     2   16 use constant default_themes => qw{ bugs };
  2         4  
  2         148  
26 2     2   15 use constant applies_to => 'PPI::Statement::Include';
  2         7  
  2         95  
27              
28 2     2   12 use constant optional_config_attributes => qw{ prefer reason };
  2         12  
  2         1136  
29              
30             sub initialize_if_enabled {
31 5     5 1 1316624 my ( $self, $config ) = @_;
32              
33 5         30 my $cfg_file = $config->get('config');
34 5         80 $cfg_file =~ s{^~}{$ENV{HOME}};
35              
36 5 50       23 return $self->_parse_config($cfg_file) ? $TRUE : $FALSE;
37             }
38              
39             sub _add_exception {
40 2     2   10 my ( $self, $msg ) = @_;
41              
42 2   50     7 $msg //= q[Unknown Error];
43              
44 2         9 $msg = __PACKAGE__ . ' ' . $msg;
45              
46 2         15 my $errors = Perl::Critic::Exception::AggregateConfiguration->new();
47              
48 2         2182 $errors->add_exception( Perl::Critic::Exception::Configuration::Generic->throw( message => $msg ) );
49              
50 0         0 return;
51             }
52              
53             sub _parse_config {
54 5     5   17 my ( $self, $cfg_file ) = @_;
55              
56 5 50       21 if ( !length $cfg_file ) {
57 0         0 return $self->_add_exception(q['config' is not set for policy.]);
58             }
59              
60 5 100       61 if ( !-e $cfg_file ) {
61 1         308 return $self->_add_exception(qq[config file '$cfg_file' does not exist.]);
62             }
63              
64 4 50 33     1689 return unless $cfg_file && -e $cfg_file;
65              
66             # slurp the file rather than using `read_file` for compat with Test::MockFile
67 4         1035 my $content;
68             {
69 4         9 local $/;
  4         30  
70 4 50       30 open my $fh, '<', $cfg_file or return;
71 4         1305 $content = <$fh>;
72             }
73              
74 4         282 my $preferred_cfg;
75 4 50       8 eval { $preferred_cfg = Config::INI::Reader->read_string($content); 1 } or do {
  4         93  
  4         4087  
76 0         0 return $self->_add_exception(qq[Invalid configuration file $cfg_file]);
77             };
78              
79 4         23 my %valid_opts = map { $_ => 1 } optional_config_attributes();
  8         35  
80              
81 4         31 foreach my $pkg ( keys %$preferred_cfg ) {
82 10         23 my $setup = $preferred_cfg->{$pkg};
83              
84 10         32 my @has_opts = keys %$setup;
85 10         34 foreach my $opt (@has_opts) {
86 10 100       35 next if $valid_opts{$opt};
87 1         7 $self->_add_exception("Invalid configuration - Package '$pkg' is using an unknown setting '$opt'");
88             }
89             }
90              
91 3         15 $self->{_cfg_preferred_modules} = $preferred_cfg;
92              
93 3         22 return 1;
94             }
95              
96             sub violates {
97 7     7 1 28870 my ( $self, $elem ) = @_;
98              
99 7 50       28 return () unless $elem;
100              
101 7         25 my $module = $elem->module;
102              
103 7 50       192 return () unless defined $module;
104 7 100       29 return () unless my $setup = $self->{_cfg_preferred_modules}->{$module};
105              
106 5         20 my $desc = qq[Using module $module is not recommended];
107 5   66     22 my $expl = $setup->{reason} // $desc;
108              
109 5 100       15 if ( my $prefer = $setup->{prefer} ) {
110 4         13 $desc = "Prefer using module module $prefer over $module";
111             }
112              
113 5         23 return $self->violation( $desc, $expl, $elem );
114             }
115              
116             1;
117              
118             __END__
119              
120             =pod
121              
122             =encoding UTF-8
123              
124             =head1 NAME
125              
126             Perl::Critic::Policy::PreferredModules
127              
128             =head1 VERSION
129              
130             version 0.002
131              
132             =head1 DESCRIPTION
133              
134             Every project could have its own rules for preferring some specific packages
135             over some others.
136              
137             This Policy tries to be `non opiniated` and let the use customize the modules
138             preference list and provide a reason at the same time.
139              
140             =head1 NAME
141              
142             Perl::Critic::Policy::PreferredModules - Custom package recommendations
143              
144             =head1 MODULES
145              
146             =head1 CONFIGURATION
147              
148             To use L<Perl::Critic::Policy::PreferredModules> you have first to enable itin your
149             F<.perlcriticrc> file by providing a F<preferred_modules.ini> configuration file:
150              
151             [PreferredModules]
152             config = /path/to/preferred_modules.ini
153             # you can also use '~' in the path for $HOME
154             #config = ~/.preferred_modules
155              
156             The F<preferred_modules.ini> file is using the L<Config::INI> format and can looks like this
157              
158             [Do::Not::Recommend]
159             prefer = Another::Package
160             reason = Please prefer using Another::Package rather than package Do::Not::Recommend
161              
162             [Avoid::Using::This]
163             [And::Also::That]
164            
165             [No:Reasons]
166             prefer=X
167            
168             [OnlyReason]
169             reason="I'm trying to tell you to do not use it..."
170              
171             =head1 SEE ALSO
172              
173             L<Perl::Critic>
174              
175             =head1 AUTHOR
176              
177             Nicolas R <atoomic@cpan.org>
178              
179             =head1 COPYRIGHT AND LICENSE
180              
181             This software is copyright (c) 2022 by cPanel, L.L.C.
182              
183             This is free software; you can redistribute it and/or modify it under
184             the same terms as the Perl 5 programming language system itself.
185              
186             =cut