File Coverage

blib/lib/Dist/Zilla/Plugin/PkgVersion.pm
Criterion Covered Total %
statement 103 106 97.1
branch 60 70 85.7
condition 6 9 66.6
subroutine 9 9 100.0
pod 0 4 0.0
total 178 198 89.9


line stmt bran cond sub pod time code
1             package Dist::Zilla::Plugin::PkgVersion 6.030;
2             # ABSTRACT: add a $VERSION to your packages
3              
4 10     10   7603 use Moose;
  10         32  
  10         87  
5             with(
6             'Dist::Zilla::Role::FileMunger',
7             'Dist::Zilla::Role::FileFinderUser' => {
8             default_finders => [ ':InstallModules', ':ExecFiles' ],
9             },
10             'Dist::Zilla::Role::PPI',
11             );
12              
13 10     10   71303 use Dist::Zilla::Pragmas;
  10         27  
  10         97  
14              
15 10     10   92 use namespace::autoclean;
  10         33  
  10         97  
16              
17             #pod =head1 SYNOPSIS
18             #pod
19             #pod in dist.ini
20             #pod
21             #pod [PkgVersion]
22             #pod
23             #pod =head1 DESCRIPTION
24             #pod
25             #pod This plugin will add lines like the following to each package in each Perl
26             #pod module or program (more or less) within the distribution:
27             #pod
28             #pod $MyModule::VERSION = '0.001';
29             #pod
30             #pod or
31             #pod
32             #pod { our $VERSION = '0.001'; }
33             #pod
34             #pod ...where 0.001 is the version of the dist, and MyModule is the name of the
35             #pod package being given a version. (In other words, it always uses fully-qualified
36             #pod names to assign versions.)
37             #pod
38             #pod It will skip any package declaration that includes a newline between the
39             #pod C<package> keyword and the package name, like:
40             #pod
41             #pod package
42             #pod Foo::Bar;
43             #pod
44             #pod This sort of declaration is also ignored by the CPAN toolchain, and is
45             #pod typically used when doing monkey patching or other tricky things.
46             #pod
47             #pod =attr die_on_existing_version
48             #pod
49             #pod If true, then when PkgVersion sees an existing C<$VERSION> assignment, it will
50             #pod throw an exception rather than skip the file. This attribute defaults to
51             #pod false.
52             #pod
53             #pod =attr die_on_line_insertion
54             #pod
55             #pod By default, PkgVersion looks for a blank line after each C<package> statement.
56             #pod If it finds one, it inserts the C<$VERSION> assignment on that line. If it
57             #pod doesn't, it will insert a new line, which means the shipped copy of the module
58             #pod will have different line numbers (off by one) than the source. If
59             #pod C<die_on_line_insertion> is true, PkgVersion will raise an exception rather
60             #pod than insert a new line.
61             #pod
62             #pod =attr use_package
63             #pod
64             #pod This option, if true, will not insert an assignment to C<$VERSION> but will
65             #pod replace the existing C<package> declaration with one that includes a version
66             #pod like:
67             #pod
68             #pod package Module::Name 0.001;
69             #pod
70             #pod =attr use_our
71             #pod
72             #pod The idea here was to insert C<< { our $VERSION = '0.001'; } >> instead of C<<
73             #pod $Module::Name::VERSION = '0.001'; >>. It turns out that this causes problems
74             #pod with some analyzers. Use of this feature is deprecated.
75             #pod
76             #pod Something else will replace it in the future.
77             #pod
78             #pod =attr use_begin
79             #pod
80             #pod If true, the version assignment is wrapped in a BEGIN block. This may help in
81             #pod rare cases, such as when DynaLoader has to be called at BEGIN time, and
82             #pod requires VERSION. This option should be needed rarely.
83             #pod
84             #pod Also note that assigning to C<$VERSION> before the module has finished
85             #pod compiling can lead to confused behavior with attempts to determine whether a
86             #pod module was successfully loaded on perl v5.8.
87             #pod
88             #pod =attr finder
89             #pod
90             #pod =for stopwords FileFinder
91             #pod
92             #pod This is the name of a L<FileFinder|Dist::Zilla::Role::FileFinder> for finding
93             #pod modules to edit. The default value is C<:InstallModules> and C<:ExecFiles>;
94             #pod this option can be used more than once.
95             #pod
96             #pod Other predefined finders are listed in
97             #pod L<Dist::Zilla::Role::FileFinderUser/default_finders>.
98             #pod You can define your own with the
99             #pod L<[FileFinder::ByName]|Dist::Zilla::Plugin::FileFinder::ByName> and
100             #pod L<[FileFinder::Filter]|Dist::Zilla::Plugin::FileFinder::Filter> plugins.
101             #pod
102             #pod =cut
103              
104             sub BUILD {
105 22     22 0 73 my ($self) = @_;
106 22 100       825 $self->log("use_our option to PkgVersion is deprecated and will be removed")
107             if $self->use_our;
108              
109 22 50 33     1781 if ($self->use_package && ($self->use_our || $self->use_begin)) {
      66        
110 0         0 $self->log_fatal("use_package and (use_our or use_begin) are not compatible");
111             }
112             }
113              
114             sub munge_files {
115 22     22 0 414 my ($self) = @_;
116              
117 22         49 $self->munge_file($_) for @{ $self->found_files };
  22         124  
118             }
119              
120             sub munge_file {
121 55     55 0 6094 my ($self, $file) = @_;
122              
123 55 100       236 if ($file->is_bytes) {
124 1         10 $self->log_debug($file->name . " has 'bytes' encoding, skipping...");
125 1         328 return;
126             }
127              
128 54 50       235 if ($file->name =~ /\.pod$/) {
129 0         0 $self->log_debug($file->name . " is a pod file, skipping...");
130 0         0 return;
131             }
132              
133 54         234 return $self->munge_perl($file);
134             }
135              
136             has die_on_existing_version => (
137             is => 'ro',
138             isa => 'Bool',
139             default => 0,
140             );
141              
142             has die_on_line_insertion => (
143             is => 'ro',
144             isa => 'Bool',
145             default => 0,
146             );
147              
148             has use_package => (
149             is => 'ro',
150             isa => 'Bool',
151             default => 0,
152             );
153              
154             has use_our => (
155             is => 'ro',
156             isa => 'Bool',
157             default => 0,
158             );
159              
160             has use_begin => (
161             is => 'ro',
162             isa => 'Bool',
163             default => 0,
164             );
165              
166             sub _version_assignment {
167 44     44   128 my ($self, $package, $version) = @_;
168              
169             # the \x20 hack is here so that when we scan *this* document we don't find
170             # an assignment to version; it shouldn't be needed, but it's been annoying
171             # enough in the past that I'm keeping it here until tests are better
172 44 100       1331 my $perl = $self->use_our
173             ? "our \$VERSION\x20=\x20'$version';"
174             : "\$$package\::VERSION\x20=\x20'$version';";
175              
176             return
177 44 100       1348 $self->use_begin ? "BEGIN { $perl }"
    100          
178             : $self->use_our ? "{ $perl }"
179             : $perl;
180             }
181              
182             sub munge_perl {
183 54     54 0 141 my ($self, $file) = @_;
184              
185 54         1538 my $version = $self->zilla->version;
186              
187 54         332 require version;
188 54 50       244 Carp::croak("invalid characters in version")
189             unless version::is_lax($version);
190              
191 54         1220 my $document = $self->ppi_document_for_file($file);
192              
193 54         19520 my $package_stmts = $document->find('PPI::Statement::Package');
194 54 100       48473 unless ($package_stmts) {
195 4         30 $self->log_debug([ 'skipping %s: no package statement found', $file->name ]);
196 4         894 return;
197             }
198              
199 50 100       261 if ($self->document_assigns_to_variable($document, '$VERSION')) {
200 6 100       273 if ($self->die_on_existing_version) {
201 1         7 $self->log_fatal([ 'existing assignment to $VERSION in %s', $file->name ]);
202             }
203              
204 5         25 $self->log([ 'skipping %s: assigns to $VERSION', $file->name ]);
205 5         1400 return;
206             }
207              
208 44         109 my %seen_pkg;
209              
210 44         79 my $munged = 0;
211 44         127 STATEMENT: for my $stmt (@$package_stmts) {
212 53         205 my $package = $stmt->namespace;
213 53 100       1513 if ($seen_pkg{ $package }++) {
214 1         8 $self->log([ 'skipping package re-declaration for %s', $package ]);
215 1         293 next;
216             }
217              
218 52 100       162 if ($stmt->content =~ /package\s*(?:#.*)?\n\s*\Q$package/) {
219 2         124 $self->log([ 'skipping private package %s in %s', $package, $file->name ]);
220 2         588 next;
221             }
222              
223 50 50       2846 $self->log("non-ASCII package name is likely to cause problems")
224             if $package =~ /\P{ASCII}/;
225              
226 50 50       188 $self->log("non-ASCII version is likely to cause problems")
227             if $version =~ /\P{ASCII}/;
228              
229 50 100       1935 if ($self->use_package) {
230 12 100       184 my $version_token = $version =~ m/\.\d+\./
231             ? PPI::Token::Number::Version->new(version->parse($version)->normal)
232             : PPI::Token::Number->new($version)
233             ;
234              
235 12 100       102 if (my ($block) = grep {; $_->isa('PPI::Structure::Block') } $stmt->schildren) {
  40         292  
236             # Okay, we've encountered `package NAME BLOCK` and want to turn it into
237             # `package NAME VERSION BLOCK` but, to quote the PPI documentation,
238             # "we're on our own here".
239             #
240             # This will also preclude us from adding "# TRIAL" because where would
241             # it go? Look, a block package should (in my opinion) not be the only
242             # or top-level package in a file, so the TRIAL comment can be
243             # elsewhere. -- rjbs, 2021-06-12
244             #
245             # First off, let's make sure we do not already have a version. If the
246             # "version" has a "{" in it, it's just the block, and we're good.
247             # Otherwise, it's going to be a real version and we need to skip.
248 4 100       15 if ($stmt->version !~ /\{/) {
249 2         65 $self->log([
250             "skipping package %s with version %s declared",
251             $stmt->namespace,
252             $stmt->version,
253             ]);
254 2         576 next STATEMENT;
255             }
256              
257             # Okay, there's a block (which we have in $block) but no version. So,
258             # we stick a Number / Number::Version in front of the block, then a
259             # space between them.
260 2         193 $block->insert_before( $version_token );
261 2         240 $block->insert_before( PPI::Token::Whitespace->new(q{ }) );
262 2         97 $munged = 1;
263 2         8 next STATEMENT;
264             }
265              
266             # Now, it's not got a block, but does it already have a version?
267 8 100       30 if (length $stmt->version) {
268 2         66 $self->log([
269             "skipping package %s with version %s declared",
270             $stmt->namespace,
271             $stmt->version,
272             ]);
273 2         718 next STATEMENT;
274             }
275              
276             # Oh, good! It's just a normal `package NAME` and we are going to add
277             # VERSION to it. This is stupid, but gets the job done.
278 6         159 my $perl = sprintf 'package %s %s;', $package, $version_token->content;
279 6 50       219 $perl .= ' # TRIAL' if $self->zilla->is_trial;
280              
281 6         32 my $newstmt = PPI::Token::Unknown->new($perl);
282 6 50       51 Carp::carp("error inserting version in " . $file->name)
283             unless $stmt->parent->__replace_child($stmt, $newstmt);
284 6         237 $munged = 1;
285 6         26 next STATEMENT;
286             }
287              
288             # the \x20 hack is here so that when we scan *this* document we don't find
289             # an assignment to version; it shouldn't be needed, but it's been annoying
290             # enough in the past that I'm keeping it here until tests are better
291 38         159 my $perl = $self->_version_assignment($package, $version);
292 38 100       1155 $self->zilla->is_trial
293             and $perl .= ' # TRIAL';
294              
295 38         196 my $clean_version = $version =~ tr/_//dr;
296 38 100       120 if ($version ne $clean_version) {
297 6         25 $perl .= "\n" . $self->_version_assignment($package, $clean_version);
298             }
299              
300             $self->log_debug([
301 38         193 'adding $VERSION assignment to %s in %s',
302             $package,
303             $file->name,
304             ]);
305              
306 38         2295 my $blank;
307              
308             {
309 38         86 my $curr = $stmt;
  38         76  
310 38         66 while (1) {
311             # avoid bogus locations due to insert_after
312 44 100       141 $document->flush_locations if $munged;
313 44         821 my $curr_line_number = $curr->line_number + 1;
314             my $find = $document->find(sub {
315 788     788   8115 my $line = $_[1]->line_number;
316 788 100       12057 return $line > $curr_line_number ? undef : $line == $curr_line_number;
317 44         33738 });
318              
319 44 100 100     793 last unless $find and @$find == 1;
320              
321 35 100       185 if ($find->[0]->isa('PPI::Token::Comment')) {
322 6         15 $curr = $find->[0];
323 6         19 next;
324             }
325              
326 29 100       105 if ("$find->[0]" =~ /\A\s*\z/) {
327 28         318 $blank = $find->[0];
328             }
329              
330 29         88 last;
331             }
332             }
333              
334 38 100       206 $perl = $blank ? "$perl\n" : "\n$perl";
335              
336             # Why can't I use PPI::Token::Unknown? -- rjbs, 2014-01-11
337 38         194 my $bogus_token = PPI::Token::Comment->new($perl);
338              
339 38 100       306 if ($blank) {
340 28 50       155 Carp::carp("error inserting version in " . $file->name)
341             unless $blank->insert_after($bogus_token);
342 28         2157 $blank->delete;
343             } else {
344 10 50       416 my $method = $self->die_on_line_insertion ? 'log_fatal' : 'log';
345 10         42 $self->$method([
346             'no blank line for $VERSION after package %s statement in %s line %s',
347             $stmt->namespace,
348             $file->name,
349             $stmt->line_number,
350             ]);
351              
352 10 50       3107 Carp::carp("error inserting version in " . $file->name)
353             unless $stmt->insert_after($bogus_token);
354             }
355              
356 38         2331 $munged = 1;
357             }
358              
359             # the document is no longer correct; it must be reparsed before it can be
360             # used again, so we can't just save_ppi_document_to_file
361             # Maybe we want a way to clear the cache for the old form, though...
362             # -- rjbs, 2016-04-24
363 44 100       394 $file->content($document->serialize) if $munged;
364 44         279 return;
365             }
366              
367             __PACKAGE__->meta->make_immutable;
368             1;
369              
370             #pod =head1 SEE ALSO
371             #pod
372             #pod Core Dist::Zilla plugins:
373             #pod L<PodVersion|Dist::Zilla::Plugin::PodVersion>,
374             #pod L<AutoVersion|Dist::Zilla::Plugin::AutoVersion>,
375             #pod L<NextRelease|Dist::Zilla::Plugin::NextRelease>.
376             #pod
377             #pod Other Dist::Zilla plugins:
378             #pod L<OurPkgVersion|Dist::Zilla::Plugin::OurPkgVersion> inserts version
379             #pod numbers using C<our $VERSION = '...';> and without changing line numbers
380             #pod
381             #pod =cut
382              
383             __END__
384              
385             =pod
386              
387             =encoding UTF-8
388              
389             =head1 NAME
390              
391             Dist::Zilla::Plugin::PkgVersion - add a $VERSION to your packages
392              
393             =head1 VERSION
394              
395             version 6.030
396              
397             =head1 SYNOPSIS
398              
399             in dist.ini
400              
401             [PkgVersion]
402              
403             =head1 DESCRIPTION
404              
405             This plugin will add lines like the following to each package in each Perl
406             module or program (more or less) within the distribution:
407              
408             $MyModule::VERSION = '0.001';
409              
410             or
411              
412             { our $VERSION = '0.001'; }
413              
414             ...where 0.001 is the version of the dist, and MyModule is the name of the
415             package being given a version. (In other words, it always uses fully-qualified
416             names to assign versions.)
417              
418             It will skip any package declaration that includes a newline between the
419             C<package> keyword and the package name, like:
420              
421             package
422             Foo::Bar;
423              
424             This sort of declaration is also ignored by the CPAN toolchain, and is
425             typically used when doing monkey patching or other tricky things.
426              
427             =head1 PERL VERSION
428              
429             This module should work on any version of perl still receiving updates from
430             the Perl 5 Porters. This means it should work on any version of perl released
431             in the last two to three years. (That is, if the most recently released
432             version is v5.40, then this module should work on both v5.40 and v5.38.)
433              
434             Although it may work on older versions of perl, no guarantee is made that the
435             minimum required version will not be increased. The version may be increased
436             for any reason, and there is no promise that patches will be accepted to lower
437             the minimum required perl.
438              
439             =head1 ATTRIBUTES
440              
441             =head2 die_on_existing_version
442              
443             If true, then when PkgVersion sees an existing C<$VERSION> assignment, it will
444             throw an exception rather than skip the file. This attribute defaults to
445             false.
446              
447             =head2 die_on_line_insertion
448              
449             By default, PkgVersion looks for a blank line after each C<package> statement.
450             If it finds one, it inserts the C<$VERSION> assignment on that line. If it
451             doesn't, it will insert a new line, which means the shipped copy of the module
452             will have different line numbers (off by one) than the source. If
453             C<die_on_line_insertion> is true, PkgVersion will raise an exception rather
454             than insert a new line.
455              
456             =head2 use_package
457              
458             This option, if true, will not insert an assignment to C<$VERSION> but will
459             replace the existing C<package> declaration with one that includes a version
460             like:
461              
462             package Module::Name 0.001;
463              
464             =head2 use_our
465              
466             The idea here was to insert C<< { our $VERSION = '0.001'; } >> instead of C<<
467             $Module::Name::VERSION = '0.001'; >>. It turns out that this causes problems
468             with some analyzers. Use of this feature is deprecated.
469              
470             Something else will replace it in the future.
471              
472             =head2 use_begin
473              
474             If true, the version assignment is wrapped in a BEGIN block. This may help in
475             rare cases, such as when DynaLoader has to be called at BEGIN time, and
476             requires VERSION. This option should be needed rarely.
477              
478             Also note that assigning to C<$VERSION> before the module has finished
479             compiling can lead to confused behavior with attempts to determine whether a
480             module was successfully loaded on perl v5.8.
481              
482             =head2 finder
483              
484             =for stopwords FileFinder
485              
486             This is the name of a L<FileFinder|Dist::Zilla::Role::FileFinder> for finding
487             modules to edit. The default value is C<:InstallModules> and C<:ExecFiles>;
488             this option can be used more than once.
489              
490             Other predefined finders are listed in
491             L<Dist::Zilla::Role::FileFinderUser/default_finders>.
492             You can define your own with the
493             L<[FileFinder::ByName]|Dist::Zilla::Plugin::FileFinder::ByName> and
494             L<[FileFinder::Filter]|Dist::Zilla::Plugin::FileFinder::Filter> plugins.
495              
496             =head1 SEE ALSO
497              
498             Core Dist::Zilla plugins:
499             L<PodVersion|Dist::Zilla::Plugin::PodVersion>,
500             L<AutoVersion|Dist::Zilla::Plugin::AutoVersion>,
501             L<NextRelease|Dist::Zilla::Plugin::NextRelease>.
502              
503             Other Dist::Zilla plugins:
504             L<OurPkgVersion|Dist::Zilla::Plugin::OurPkgVersion> inserts version
505             numbers using C<our $VERSION = '...';> and without changing line numbers
506              
507             =head1 AUTHOR
508              
509             Ricardo SIGNES 😏 <cpan@semiotic.systems>
510              
511             =head1 COPYRIGHT AND LICENSE
512              
513             This software is copyright (c) 2023 by Ricardo SIGNES.
514              
515             This is free software; you can redistribute it and/or modify it under
516             the same terms as the Perl 5 programming language system itself.
517              
518             =cut