File Coverage

blib/lib/App/GitHooks/Plugin/ValidateChangelogFormat.pm
Criterion Covered Total %
statement 60 61 98.3
branch 18 22 81.8
condition 1 3 33.3
subroutine 17 18 94.4
pod 3 3 100.0
total 99 107 92.5


line stmt bran cond sub pod time code
1             package App::GitHooks::Plugin::ValidateChangelogFormat;
2              
3 23     23   4562761 use strict;
  23         38  
  23         646  
4 23     23   95 use warnings;
  23         29  
  23         609  
5              
6 23     23   93 use base 'App::GitHooks::Plugin';
  23         32  
  23         2940  
7              
8             # Internal dependencies.
9 23     23   2531 use App::GitHooks::Constants qw( :PLUGIN_RETURN_CODES );
  23         12603  
  23         2941  
10              
11             # External dependencies.
12 23     23   11243 use CPAN::Changes;
  23         348710  
  23         889  
13 23     23   1771 use Try::Tiny;
  23         5594  
  23         1566  
14 23     23   814 use version qw();
  23         34  
  23         16066  
15              
16              
17             =head1 NAME
18              
19             App::GitHooks::Plugin::ValidateChangelogFormat - Validate the format of changelog files.
20              
21              
22             =head1 DESCRIPTION
23              
24             This plugin verifies that the changes log conforms to the specifications
25             outlined in C.
26              
27              
28             =head1 VERSION
29              
30             Version 1.1.0
31              
32             =cut
33              
34             our $VERSION = '1.1.0';
35              
36              
37             =head1 CONFIGURATION OPTIONS
38              
39             This plugin supports the following options in the C<[ValidateChangelogFormat]>
40             section of your C<.githooksrc> file.
41              
42             [ValidateChangelogFormat]
43             version_format_regex = /^v\d+\.\d+\.\d+$/
44             date_format_regex = /^\d{4}-\d{2}-\d{2}$/
45              
46              
47             =head2 version_format_regex
48              
49             A regular expression that will be checked against the version number for each
50             release listed in the change log.
51              
52             version_format_regex = /^v\d+\.\d+\.\d+$/
53              
54             By default, this plugin allows the versioning schemes described in
55             L.
56              
57              
58             =head2 date_format_regex
59              
60             A regular expression that will be checked against the date for each release
61             listed in the change log.
62              
63             date_format_regex = /^\d{4}-\d{2}-\d{2}$/
64              
65             By default, this plugin allows the date formats listed in
66             L.
67              
68              
69             =head1 METHODS
70              
71             =head2 get_file_pattern()
72              
73             Return a pattern to filter the files this plugin should analyze.
74              
75             my $file_pattern = App::GitHooks::Plugin::ValidateChangelogFormat->get_file_pattern(
76             app => $app,
77             );
78              
79             By default, this catches files named changes or changelog, with an optional
80             extension of .md or .pod. The name of the files is not case sensitive.
81              
82             =cut
83              
84             sub get_file_pattern
85             {
86 21     21 1 1512675 return qr/^(?:changes|changelog)(?:\.(?:md|pod))?$/ix;
87             }
88              
89              
90             =head2 get_file_check_description()
91              
92             Return a description of the check performed on files by the plugin and that
93             will be displayed to the user, if applicable, along with an indication of the
94             success or failure of the plugin.
95              
96             my $description = App::GitHooks::Plugin::ValidateChangelogFormat->get_file_check_description();
97              
98             =cut
99              
100             sub get_file_check_description
101             {
102 21     21 1 18767 return 'The changelog format matches CPAN::Changes::Spec.';
103             }
104              
105             =head2 run_pre_commit_file()
106              
107             Code to execute for each file as part of the pre-commit hook.
108              
109             my $success = App::GitHooks::Plugin::ValidateChangelogFormat->run_pre_commit_file();
110              
111             The code in this subroutine is mostly adapted from L.
112              
113             =cut
114              
115             sub run_pre_commit_file
116             {
117 10     10 1 21211 my ( $class, %args ) = @_;
118 10         156 my $file = delete( $args{'file'} );
119 10         857 my $git_action = delete( $args{'git_action'} );
120 10         85 my $app = delete( $args{'app'} );
121 10         295 my $repository = $app->get_repository();
122 10         343 my $config = $app->get_config();
123              
124             # Ignore deleted files.
125 10 50       745 return $PLUGIN_RETURN_SKIPPED
126             if $git_action eq 'D';
127              
128             # Determine the required date format.
129 10         208 my $date_format_regex = $config->get_regex( 'ValidateChangelogFormat', 'date_format_regex' );
130 10 100       2749 $date_format_regex = defined( $date_format_regex )
131             ? qr/^$date_format_regex$/
132             : qr/^(?:${CPAN::Changes::W3CDTF_REGEX}|${CPAN::Changes::UNKNOWN_VALS})$/x;
133              
134             # Determine the required version format.
135 10         109 my $version_format_regex = $config->get_regex( 'ValidateChangelogFormat', 'version_format_regex' );
136              
137             # Parse the changelog file.
138             my $changes =
139             try
140             {
141 10     10   910 return CPAN::Changes->load( $repository->work_tree() . '/' . $file );
142             }
143             catch
144             {
145 0     0   0 die "Unable to parse the change log\n";
146 10         706 };
147              
148             # Verify that the changelog contains releases.
149 10         14102 my @releases = $changes->releases();
150 10 100       533 die "The change log does not contain any releases\n"
151             if scalar( @releases ) == 0;
152              
153 5         25 my @errors = ();
154 5         15 my $count = 0;
155 5         35 foreach my $release ( @releases )
156             {
157             # Prefix to identify which release has issues in the error messages.
158 5         9 $count++;
159 5         51 my $error_prefix = sprintf(
160             "Release %s/%s",
161             $count,
162             scalar( @releases ),
163             );
164              
165             # Validate the release date.
166             try
167             {
168 5     5   213 my $date = $release->date();
169              
170 5 50 33     114 die "the release date is missing.\n"
171             if !defined( $date ) || ( $date eq '' );
172              
173 5 100       77 die "date '$date' is not in the recommended format.\n"
174             if $date !~ $date_format_regex;
175             }
176             catch
177             {
178 1     1   26 push( @errors, "$error_prefix: $_" );
179 5         70 };
180              
181             # Validate the release version.
182             try
183             {
184 5     5   175 my $version = $release->version();
185              
186 5 50       42 die "the version number is missing.\n"
187             if $version eq '';
188              
189 5 100       23 if ( defined( $version_format_regex ) )
190             {
191 2 100       66 die "version '$version' is not a valid version number.\n"
192             if $version !~ qr/^$version_format_regex$/x;
193             }
194             else
195             {
196 3 50       156 die "version '$version' is not a valid version number.\n"
197             if !version::is_lax($version);
198             }
199             }
200             catch
201             {
202 1     1   26 push( @errors, "$error_prefix: $_" );
203 5         177 };
204              
205             # Verify that a list of changes is present.
206             try
207             {
208 5     5   183 my $description = $release->changes();
209              
210 5 100       129 die "the release does not contain a description of changes.\n"
211             if scalar( keys %$description ) == 0;
212             }
213             catch
214             {
215 1     1   20 push( @errors, "$error_prefix: $_" );
216 5         405 };
217             }
218              
219             # Raise an exception with all the errors found, if any.
220 5 100       256 die join( '', @errors ) . "\n"
221             if scalar( @errors ) != 0;
222              
223 2         165 return $PLUGIN_RETURN_PASSED;
224             }
225              
226              
227             =head1 SEE ALSO
228              
229             =over 4
230              
231             =item * L
232              
233             =item * L
234              
235             =item * L
236              
237             =back
238              
239              
240             =head1 BUGS
241              
242             Please report any bugs or feature requests through the web interface at
243             L.
244             I will be notified, and then you'll automatically be notified of progress on
245             your bug as I make changes.
246              
247              
248             =head1 SUPPORT
249              
250             You can find documentation for this module with the perldoc command.
251              
252             perldoc App::GitHooks::Plugin::ValidateChangelogFormat
253              
254              
255             You can also look for information at:
256              
257             =over
258              
259             =item * GitHub's request tracker
260              
261             L
262              
263             =item * AnnoCPAN: Annotated CPAN documentation
264              
265             l
266              
267             =item * CPAN Ratings
268              
269             L
270              
271             =item * MetaCPAN
272              
273             L
274              
275             =back
276              
277              
278             =head1 AUTHOR
279              
280             L,
281             C<< >>.
282              
283              
284             =head1 COPYRIGHT & LICENSE
285              
286             Copyright 2015-2017 Guillaume Aubert.
287              
288             This code is free software; you can redistribute it and/or modify it under the
289             same terms as Perl 5 itself.
290              
291             This program is distributed in the hope that it will be useful, but WITHOUT ANY
292             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
293             PARTICULAR PURPOSE. See the LICENSE file for more details.
294              
295             =cut
296              
297             1;