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.037;
2             # ABSTRACT: add a $VERSION to your packages
3              
4 10     10   7834 use Moose;
  10         23  
  10         104  
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   71055 use Dist::Zilla::Pragmas;
  10         25  
  10         89  
14              
15 10     10   65 use namespace::autoclean;
  10         22  
  10         122  
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 55 my ($self) = @_;
106 22 100       835 $self->log("use_our option to PkgVersion is deprecated and will be removed")
107             if $self->use_our;
108              
109 22 50 33     2142 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 522 my ($self) = @_;
116              
117 22         44 $self->munge_file($_) for @{ $self->found_files };
  22         140  
118             }
119              
120             sub munge_file {
121 55     55 0 6546 my ($self, $file) = @_;
122              
123 55 100       260 if ($file->is_bytes) {
124 1         7 $self->log_debug($file->name . " has 'bytes' encoding, skipping...");
125 1         255 return;
126             }
127              
128 54 50       203 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         242 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   157 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       1377 my $perl = $self->use_our
173             ? "our \$VERSION\x20=\x20'$version';"
174             : "\$$package\::VERSION\x20=\x20'$version';";
175              
176             return
177 44 100       1377 $self->use_begin ? "BEGIN { $perl }"
    100          
178             : $self->use_our ? "{ $perl }"
179             : $perl;
180             }
181              
182             sub munge_perl {
183 54     54 0 130 my ($self, $file) = @_;
184              
185 54         1591 my $version = $self->zilla->version;
186              
187 54         358 require version;
188 54 50       253 Carp::croak("invalid characters in version")
189             unless version::is_lax($version);
190              
191 54         1297 my $document = $self->ppi_document_for_file($file);
192              
193 54         23162 my $package_stmts = $document->find('PPI::Statement::Package');
194 54 100       45130 unless ($package_stmts) {
195 4         38 $self->log_debug([ 'skipping %s: no package statement found', $file->name ]);
196 4         1273 return;
197             }
198              
199 50 100       271 if ($self->document_assigns_to_variable($document, '$VERSION')) {
200 6 100       266 if ($self->die_on_existing_version) {
201 1         9 $self->log_fatal([ 'existing assignment to $VERSION in %s', $file->name ]);
202             }
203              
204 5         23 $self->log([ 'skipping %s: assigns to $VERSION', $file->name ]);
205 5         1659 return;
206             }
207              
208 44         82 my %seen_pkg;
209              
210 44         77 my $munged = 0;
211 44         104 STATEMENT: for my $stmt (@$package_stmts) {
212 53         226 my $package = $stmt->namespace;
213 53 100       1465 if ($seen_pkg{ $package }++) {
214 1         7 $self->log([ 'skipping package re-declaration for %s', $package ]);
215 1         424 next;
216             }
217              
218 52 100       154 if ($stmt->content =~ /package\s*(?:#.*)?\n\s*\Q$package/) {
219 2         167 $self->log([ 'skipping private package %s in %s', $package, $file->name ]);
220 2         552 next;
221             }
222              
223 50 50       3384 $self->log("non-ASCII package name is likely to cause problems")
224             if $package =~ /\P{ASCII}/;
225              
226 50 50       157 $self->log("non-ASCII version is likely to cause problems")
227             if $version =~ /\P{ASCII}/;
228              
229 50 100       2240 if ($self->use_package) {
230 12 100       210 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       120 if (my ($block) = grep {; $_->isa('PPI::Structure::Block') } $stmt->schildren) {
  40         352  
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       18 if ($stmt->version !~ /\{/) {
249 2         67 $self->log([
250             "skipping package %s with version %s declared",
251             $stmt->namespace,
252             $stmt->version,
253             ]);
254 2         747 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         187 $block->insert_before( $version_token );
261 2         242 $block->insert_before( PPI::Token::Whitespace->new(q{ }) );
262 2         97 $munged = 1;
263 2         9 next STATEMENT;
264             }
265              
266             # Now, it's not got a block, but does it already have a version?
267 8 100       32 if (length $stmt->version) {
268 2         74 $self->log([
269             "skipping package %s with version %s declared",
270             $stmt->namespace,
271             $stmt->version,
272             ]);
273 2         896 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         163 my $perl = sprintf 'package %s %s;', $package, $version_token->content;
279 6 50       310 $perl .= ' # TRIAL' if $self->zilla->is_trial;
280              
281 6         40 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         292 $munged = 1;
285 6         30 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         189 my $perl = $self->_version_assignment($package, $version);
292 38 100       1131 $self->zilla->is_trial
293             and $perl .= ' # TRIAL';
294              
295 38         128 my $clean_version = $version =~ tr/_//dr;
296 38 100       102 if ($version ne $clean_version) {
297 6         20 $perl .= "\n" . $self->_version_assignment($package, $clean_version);
298             }
299              
300             $self->log_debug([
301 38         209 'adding $VERSION assignment to %s in %s',
302             $package,
303             $file->name,
304             ]);
305              
306 38         2466 my $blank;
307              
308             {
309 38         78 my $curr = $stmt;
  38         68  
310 38         60 while (1) {
311             # avoid bogus locations due to insert_after
312 44 100       142 $document->flush_locations if $munged;
313 44         733 my $curr_line_number = $curr->line_number + 1;
314             my $find = $document->find(sub {
315 788     788   7223 my $line = $_[1]->line_number;
316 788 100       10370 return $line > $curr_line_number ? undef : $line == $curr_line_number;
317 44         29351 });
318              
319 44 100 100     721 last unless $find and @$find == 1;
320              
321 35 100       166 if ($find->[0]->isa('PPI::Token::Comment')) {
322 6         16 $curr = $find->[0];
323 6         22 next;
324             }
325              
326 29 100       128 if ("$find->[0]" =~ /\A\s*\z/) {
327 28         318 $blank = $find->[0];
328             }
329              
330 29         105 last;
331             }
332             }
333              
334 38 100       173 $perl = $blank ? "$perl\n" : "\n$perl";
335              
336             # Why can't I use PPI::Token::Unknown? -- rjbs, 2014-01-11
337 38         174 my $bogus_token = PPI::Token::Comment->new($perl);
338              
339 38 100       332 if ($blank) {
340 28 50       120 Carp::carp("error inserting version in " . $file->name)
341             unless $blank->insert_after($bogus_token);
342 28         2197 $blank->delete;
343             } else {
344 10 50       466 my $method = $self->die_on_line_insertion ? 'log_fatal' : 'log';
345 10         40 $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       3878 Carp::carp("error inserting version in " . $file->name)
353             unless $stmt->insert_after($bogus_token);
354             }
355              
356 38         2031 $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       397 $file->content($document->serialize) if $munged;
364 44         284 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.037
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
431             released in the last two to three years. (That is, if the most recently
432             released version is v5.40, then this module should work on both v5.40 and
433             v5.38.)
434              
435             Although it may work on older versions of perl, no guarantee is made that the
436             minimum required version will not be increased. The version may be increased
437             for any reason, and there is no promise that patches will be accepted to
438             lower the minimum required perl.
439              
440             =head1 ATTRIBUTES
441              
442             =head2 die_on_existing_version
443              
444             If true, then when PkgVersion sees an existing C<$VERSION> assignment, it will
445             throw an exception rather than skip the file. This attribute defaults to
446             false.
447              
448             =head2 die_on_line_insertion
449              
450             By default, PkgVersion looks for a blank line after each C<package> statement.
451             If it finds one, it inserts the C<$VERSION> assignment on that line. If it
452             doesn't, it will insert a new line, which means the shipped copy of the module
453             will have different line numbers (off by one) than the source. If
454             C<die_on_line_insertion> is true, PkgVersion will raise an exception rather
455             than insert a new line.
456              
457             =head2 use_package
458              
459             This option, if true, will not insert an assignment to C<$VERSION> but will
460             replace the existing C<package> declaration with one that includes a version
461             like:
462              
463             package Module::Name 0.001;
464              
465             =head2 use_our
466              
467             The idea here was to insert C<< { our $VERSION = '0.001'; } >> instead of C<<
468             $Module::Name::VERSION = '0.001'; >>. It turns out that this causes problems
469             with some analyzers. Use of this feature is deprecated.
470              
471             Something else will replace it in the future.
472              
473             =head2 use_begin
474              
475             If true, the version assignment is wrapped in a BEGIN block. This may help in
476             rare cases, such as when DynaLoader has to be called at BEGIN time, and
477             requires VERSION. This option should be needed rarely.
478              
479             Also note that assigning to C<$VERSION> before the module has finished
480             compiling can lead to confused behavior with attempts to determine whether a
481             module was successfully loaded on perl v5.8.
482              
483             =head2 finder
484              
485             =for stopwords FileFinder
486              
487             This is the name of a L<FileFinder|Dist::Zilla::Role::FileFinder> for finding
488             modules to edit. The default value is C<:InstallModules> and C<:ExecFiles>;
489             this option can be used more than once.
490              
491             Other predefined finders are listed in
492             L<Dist::Zilla::Role::FileFinderUser/default_finders>.
493             You can define your own with the
494             L<[FileFinder::ByName]|Dist::Zilla::Plugin::FileFinder::ByName> and
495             L<[FileFinder::Filter]|Dist::Zilla::Plugin::FileFinder::Filter> plugins.
496              
497             =head1 SEE ALSO
498              
499             Core Dist::Zilla plugins:
500             L<PodVersion|Dist::Zilla::Plugin::PodVersion>,
501             L<AutoVersion|Dist::Zilla::Plugin::AutoVersion>,
502             L<NextRelease|Dist::Zilla::Plugin::NextRelease>.
503              
504             Other Dist::Zilla plugins:
505             L<OurPkgVersion|Dist::Zilla::Plugin::OurPkgVersion> inserts version
506             numbers using C<our $VERSION = '...';> and without changing line numbers
507              
508             =head1 AUTHOR
509              
510             Ricardo SIGNES 😏 <cpan@semiotic.systems>
511              
512             =head1 COPYRIGHT AND LICENSE
513              
514             This software is copyright (c) 2026 by Ricardo SIGNES.
515              
516             This is free software; you can redistribute it and/or modify it under
517             the same terms as the Perl 5 programming language system itself.
518              
519             =cut