File Coverage

blib/lib/Perl/Critic/Policy/PreferredModules.pm
Criterion Covered Total %
statement 76 79 96.2
branch 15 22 68.1
condition 5 10 50.0
subroutine 15 15 100.0
pod 2 3 66.6
total 113 129 87.6


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