File Coverage

blib/lib/Dist/Mgr.pm
Criterion Covered Total %
statement 512 559 91.5
branch 177 240 73.7
condition 52 68 76.4
subroutine 77 92 83.7
pod 34 34 100.0
total 852 993 85.8


line stmt bran cond sub pod time code
1             package Dist::Mgr;
2              
3 25     25   2376320 use strict;
  25         39  
  25         737  
4 25     25   81 use warnings;
  25         36  
  25         968  
5 25     25   8453 use version;
  25         37868  
  25         116  
6              
7 25     25   9957 use Capture::Tiny qw(:all);
  25         452474  
  25         3707  
8 25     25   167 use Carp qw(croak cluck);
  25         46  
  25         1152  
9 25     25   9873 use CPAN::Uploader;
  25         1214028  
  25         944  
10 25     25   199 use Cwd qw(getcwd);
  25         39  
  25         1380  
11 25     25   1109 use Data::Dumper;
  25         13793  
  25         980  
12 25     25   10658 use Digest::SHA;
  25         64157  
  25         1288  
13 25     25   14895 use Dist::Mgr::FileData qw(:all);
  25         105  
  25         4214  
14 25     25   9610 use Dist::Mgr::Git qw(:all);
  25         80  
  25         3251  
15 25     25   164 use File::Copy;
  25         35  
  25         1261  
16 25     25   102 use File::Copy::Recursive qw(rmove_glob);
  25         33  
  25         889  
17 25     25   98 use File::Path qw(make_path rmtree);
  25         42  
  25         857  
18 25     25   94 use File::Find::Rule;
  25         32  
  25         248  
19 25     25   15659 use JSON;
  25         209430  
  25         114  
20 25     25   2935 use Module::Starter;
  25         50  
  25         252  
21 25     25   2482 use PPI;
  25         40  
  25         383  
22 25     25   78 use Term::ReadKey;
  25         32  
  25         1423  
23 25     25   90 use Tie::File;
  25         33  
  25         465  
24              
25 25     25   67 use Exporter qw(import);
  25         34  
  25         3704  
26             our @ISA = qw(Exporter);
27             our @EXPORT_OK = qw(
28             add_bugtracker
29             add_repository
30             changes
31             changes_bump
32             changes_date
33             ci_badges
34             ci_github
35             config
36             config_file
37             copyright_info
38             copyright_bump
39             cpan_upload
40             git_add
41             git_commit
42             git_clone
43             git_pull
44             git_push
45             git_ignore
46             git_release
47             git_repo
48             git_status_differs
49             git_tag
50             init
51             make_dist
52             make_distclean
53             make_manifest
54             make_test
55             manifest_skip
56             manifest_t
57             move_distribution_files
58             remove_unwanted_files
59             version_bump
60             version_incr
61             version_info
62             );
63             our @EXPORT_PRIVATE = qw(
64             _dist_dir_re
65             _validate_git
66             );
67             our %EXPORT_TAGS = (
68             all => [@EXPORT_OK],
69             private => _export_private(),
70             );
71              
72             our $VERSION = '1.14';
73              
74             use constant {
75 25 50       148017 CONFIG_FILE => 'dist-mgr.json',
76             GITHUB_CI_FILE => 'github_ci_default.yml',
77             GITHUB_CI_PATH => '.github/workflows/',
78             CHANGES_FILE => 'Changes',
79             CHANGES_ORIG_SHA => '97624d56464d7254ef5577e4a0c8a098d6c6d9e6', # Module::Starter version
80             FSTYPE_IS_DIR => 1,
81             FSTYPE_IS_FILE => 2,
82             DEFAULT_DIR => 'lib/',
83             DEFAULT_POD_DIR => '.',
84             MAKE => $^O =~ /win32/i ? 'gmake' : 'make',
85 25     25   113 };
  25         31  
86              
87             # Public
88              
89             sub add_bugtracker {
90 6     6 1 45796 my ($author, $repo, $makefile) = @_;
91              
92 6 100 100     45 if (! defined $author || ! defined $repo) {
93 2         162 croak("Usage: add_bugtracker(\$author, \$repository_name)\n");
94             }
95              
96 4   100     28 $makefile //= 'Makefile.PL';
97              
98 4         16 _makefile_insert_bugtracker($author, $repo, $makefile);
99             }
100             sub add_repository {
101 6     6 1 222444 my ($author, $repo, $makefile) = @_;
102              
103 6 100 100     38 if (! defined $author || ! defined $repo) {
104 2         243 croak("Usage: add_repository(\$author, \$repository_name)\n");
105             }
106              
107 4   100     45 $makefile //= 'Makefile.PL';
108              
109 4         14 _makefile_insert_repository($author, $repo, $makefile);
110             }
111             sub changes {
112 2     2 1 9989 my ($module, $file) = @_;
113              
114 2 50       15 croak("changes() needs a module parameter") if ! defined $module;
115              
116 2   100     20 $file //= 'Changes';
117              
118             # Overwrite the Changes file if there aren't any dates in it
119              
120 2         4 my @contents;
121              
122 2         5 my $changes_date_count = 0;
123              
124 2 50       57 if (-e $file) {
125 2         1567 my ($contents, $tie) = _changes_tie($file);
126 2         16 $changes_date_count = grep /\d{4}-\d{2}-\d{2}/, $contents;
127 2         19 untie $tie;
128             }
129 2 50 33     120 if (! -e $file || ! $changes_date_count) {
130 2         20 my @contents = _changes_file($module);
131 2         9 _changes_write_file($file, \@contents);
132             }
133              
134 2         8 return @contents;
135             }
136             sub changes_bump {
137 1     1 1 3100 my ($version, $file) = @_;
138              
139 1 50       4 croak("changes_bump() requires a version sent in") if ! defined $version;
140 1         4 _validate_version($version);
141              
142 1   50     2 $file //= 'Changes';
143              
144 1         4 my ($contents, $tie) = _changes_tie($file);
145              
146 1         3 for (0..$#$contents) {
147 3 100       1794 if ($contents->[$_] =~ /^\d+\.\d+\s+/) {
148 1         77 $contents->[$_-1] = "\n$version UNREL\n -\n\n";
149 1         259 last;
150             }
151             }
152              
153 1         4 untie $tie;
154             }
155             sub changes_date {
156 1     1 1 2878 my ($file) = @_;
157              
158 1   50     3 $file //= 'Changes';
159              
160 1         5 my ($contents, $tie) = _changes_tie($file);
161              
162 1         14 my ($d, $m, $y) = (localtime)[3, 4, 5];
163 1         3 $y += 1900;
164 1         3 $m += 1;
165              
166 1 50       5 $m = "0$m" if length $m == 1;
167 1 50       3 $d = "0$d" if length $d == 1;
168              
169 1         5 for (0..$#$contents) {
170 3 100       275 if ($contents->[$_] =~ /^(.*)\s+UNREL/) {
171 1         74 $contents->[$_] = "$1 $y-$m-$d";
172 1         239 last;
173             }
174             }
175              
176 1         5 untie $tie;
177             }
178             sub ci_badges {
179 9 100   9 1 217820 if (scalar @_ < 2) {
180 2         279 croak("ci_badges() needs \$author and \$repo sent in");
181             }
182              
183 7         25 my ($author, $repo, $fs_entry) = @_;
184              
185 7   50     18 $fs_entry //= DEFAULT_DIR;
186              
187 7         11 my $exit = 0;
188              
189 7         26 for (_module_find_files($fs_entry)) {
190 7 100       5482 $exit = -1 if _module_insert_ci_badges($author, $repo, $_) == -1;
191             }
192              
193 7         6851 return $exit;
194             }
195             sub ci_github {
196 9     9 1 286282 my ($os) = @_;
197              
198 9 100 100     64 if (defined $os && ref $os ne 'ARRAY') {
199 3         327 croak("\$os parameter to ci_github() must be an array ref");
200             }
201              
202             # Add the CI file to MANIFEST.SKIP
203              
204 6 100       135 if (-e 'MANIFEST.SKIP') {
205 5 50       246 open my $fh, '<', 'MANIFEST.SKIP'
206             or croak("Can't open MANIFEST.SKIP for reading");
207              
208 5         217 my @makefile_skip_contents = <$fh>;
209              
210 5 50       45 if (grep !m|\.github$|, @makefile_skip_contents) {
211 5         52 close $fh;
212 5 50       138 open my $wfh, '>>', 'MANIFEST.SKIP'
213             or croak("Can't open MANIFEST.SKIP for writing");
214              
215 5         192 print $wfh '^\.github/';
216             }
217             }
218             else {
219 1 50       118 open my $wfh, '>>', 'MANIFEST.SKIP'
220             or croak("Can't open MANIFEST.SKIP for writing");
221              
222 1         51 print $wfh '^\.github/';
223             }
224              
225 6         43 my @contents = _ci_github_file($os);
226 6         28 _ci_github_write_file(\@contents);
227              
228 6         56 return @contents;
229             }
230             sub config {
231 21     21 1 183115 my ($args, $file) = @_;
232              
233 21 100       88 if (! defined $args) {
    100          
234 1         147 croak("config() requires \$args hash reference parameter");
235             }
236             elsif (ref $args ne 'HASH') {
237 1         106 croak("\$args parameter must be a hash reference.");
238             }
239              
240 19 100       55 $file = config_file() if ! defined $file;
241 19         24 my $conf;
242              
243 19 100 66     476 if (-e $file && -f $file) {
244             {
245 15         27 local $/;
  15         54  
246 15 50       521 open my $fh, '<', $file or croak "Can't open config file $file: $!";
247 15         436 my $json = <$fh>;
248 15         114 $conf = decode_json $json;
249              
250 15         1541 for (keys %{ $conf }) {
  15         52  
251 31 100       256 delete $conf->{$_} if $conf->{$_} eq '';
252             }
253             }
254             }
255             else {
256             # No config file present
257 4         39 _config_file_write($file, _config_file());
258              
259 4         79 print "\nGenerated new configuration file: $file\n";
260             }
261              
262 19 100       50 %{ $args } = (%{ $args }, %{ $conf }) if $conf;
  15         40  
  15         27  
  15         20  
263              
264 19         66 return $args;
265             }
266             sub config_file {
267 20 50   20 1 389 my $file = $^O =~ /win32/i
268 0         0 ? "$ENV{USERPROFILE}/${\CONFIG_FILE}"
269 20         47 : "$ENV{HOME}/${\CONFIG_FILE}";
270              
271 20         36 return $file;
272             }
273             sub copyright_bump {
274 2     2 1 4819 my ($fs_entry) = @_;
275              
276 2   50     6 $fs_entry //= DEFAULT_POD_DIR;
277 2         1494 _validate_fs_entry($fs_entry);
278              
279 2         32 my ($year) = (localtime)[5];
280 2         6 $year += 1900;
281              
282 2         5 my @pod_files = _pod_find_files($fs_entry);
283 2         2036 my %info;
284              
285 2         4 for my $pod_file (@pod_files) {
286 9         263 my ($contents, $tie) = _pod_tie($pod_file);
287              
288 9         30 for (0 .. $#$contents) {
289             # Match a single year (Copyright 2016), a dash range
290             # (Copyright 2016-2019) or a comma range (Copyright 2016,2019)
291 144 100       10462 if ($contents->[$_] =~ /^(Copyright\s+)(\d{4})(?:\s*[-,]\s*(\d{4}))?(\s+.*)/) {
292 5         330 my ($prefix, $first, $second, $rest) = ($1, $2, $3, $4);
293              
294 5 100       38 if (defined $second) {
295             # A range: keep the first year, bump the latter to the
296             # current year, and normalize the separator to a dash
297 2         11 $contents->[$_] = "$prefix$first-$year$rest";
298             }
299             else {
300             # A single year: replace it with the current year
301 3         15 $contents->[$_] = "$prefix$year$rest";
302             }
303              
304 5         998 $info{$pod_file} = $year;
305 5         7 last;
306             }
307             }
308 9         262 untie $tie;
309             }
310              
311 2         61 return \%info;
312             }
313             sub copyright_info {
314 3     3 1 184124 my ($fs_entry) = @_;
315              
316 3   50     9 $fs_entry //= DEFAULT_POD_DIR;
317              
318 3         10 _validate_fs_entry($fs_entry);
319              
320 2         8 my @pod_files = _pod_find_files($fs_entry);
321              
322 2         2126 my %copyright_info;
323              
324 2         4 for my $file (@pod_files) {
325 9         15 my $copyright = _pod_extract_file_copyright($file);
326 9 100 66     31 next if ! defined $copyright || $copyright !~ /^\d{4}$/;
327 5 50       17 $copyright_info{$file} = $copyright if defined $copyright;
328             }
329              
330 2         7 return \%copyright_info;
331             }
332             sub cpan_upload {
333 5     5 1 179282 my ($dist_file_name, %args) = @_;
334              
335 5         14 config(\%args);
336              
337 5 100       10 if (! defined $dist_file_name) {
338 1         148 croak("cpan_upload() requires the name of a distribution file sent in");
339             }
340              
341 4 100       93 if (! -f $dist_file_name) {
342 1         127 croak("File name sent to cpan_upload() isn't a valid file");
343             }
344              
345 3   66     16 $args{user} //= $args{cpan_id};
346 3   66     11 $args{password} //= $args{cpan_pw};
347              
348 3 100       7 $args{user} = $ENV{CPAN_USERNAME} if ! $args{user};
349 3 100       7 $args{password} = $ENV{CPAN_PASSWORD} if ! $args{password};
350              
351 3 100 66     24 if (! $args{user} || ! $args{password}) {
352 2         248 croak("\ncpan_upload() requires --cpan_id and --cpan_pw");
353             }
354              
355 1 50       10 if ($args{dry_run}) {
356 1         21 print "\nCPAN upload is in dry run mode... nothing will be uploaded\n";
357             }
358              
359             CPAN::Uploader->upload_file(
360 1         13 $dist_file_name,
361             \%args
362             );
363              
364 1         213 print "\nSuccessfully uploaded $dist_file_name to the CPAN\n";
365              
366 1         12 return %args;
367             }
368             sub git_add {
369 0     0 1 0 _git_add();
370             }
371             sub git_ignore {
372 2     2 1 173544 my ($dir) = @_;
373              
374 2   100     28 $dir //= '.';
375              
376 2         15 my @content = _git_ignore_file();
377              
378 2         13 _git_ignore_write_file($dir, \@content);
379              
380 2         16 return @content;
381             }
382             sub git_commit {
383 0     0 1 0 _git_commit(@_);
384             }
385             sub git_clone {
386 0     0 1 0 _git_clone(@_);
387             }
388             sub git_push {
389 0     0 1 0 _git_push(@_);
390             }
391             sub git_pull {
392 0     0 1 0 _git_pull(@_);
393             }
394             sub git_release {
395 0     0 1 0 _git_release(@_);
396             }
397             sub git_repo {
398 0     0 1 0 _git_repo();
399             }
400             sub git_status_differs {
401 0     0 1 0 _git_status_differs(@_);
402             }
403             sub git_tag {
404 0     0 1 0 _git_tag(@_);
405             }
406             sub init {
407 8     8 1 366750 my (%args) = @_;
408              
409 8         30 config(\%args);
410              
411 8         68 my $cwd = getcwd();
412              
413 8 100       42 if ($cwd =~ _dist_dir_re()) {
414 1         140 croak "Can't run init() while in the '$cwd' directory";
415             }
416              
417 7 100       26 $args{license} = 'artistic2' if ! exists $args{license};
418 7         12 $args{builder} = 'ExtUtils::MakeMaker';
419              
420 7         11 for (qw(modules author email)) {
421 18 100       31 if (! exists $args{$_}) {
422 3         338 croak("init() requires '$_' in the parameter hash");
423             }
424             }
425              
426 4 100       14 if (ref $args{modules} ne 'ARRAY') {
427 1         128 croak("init()'s 'modules' parameter must be an array reference");
428             }
429              
430             # Module::Starter 1.79+ added multi-author support and now requires
431             # 'author' to be an arrayref of 'Name ' strings; older versions
432             # expect a plain scalar. Normalize for the installed version while
433             # leaving $args{author} untouched for _module_write_template() below
434 3         11 my %distro_args = %args;
435              
436 3 50       24 if ($Module::Starter::VERSION >= 1.79) {
437 3         10 $distro_args{author} = ["$args{author} <$args{email}>"];
438             }
439              
440 3 100       9 if ($args{verbose}) {
441 2         3 delete $distro_args{verbose};
442 2         31 Module::Starter->create_distro(%distro_args);
443             }
444             else {
445             capture_merged {
446 1     1   1484 Module::Starter->create_distro(%distro_args);
447 1         25 };
448             }
449              
450 3         135522 my ($module) = (@{ $args{modules} })[0];
  3         40  
451 3         16 my $module_file = $module;
452 3         36 $module_file =~ s/::/\//g;
453 3         14 $module_file = "lib/$module_file.pm";
454              
455 3         18 my $module_dir = $module;
456 3         19 $module_dir =~ s/::/-/g;
457              
458 3 50       53 chdir $module_dir or croak("Can't change into directory '$module_dir'");
459              
460 3 50       269 unlink $module_file
461             or croak("Can't delete the Module::Starter module '$module_file': $!");
462              
463 3         57 _module_write_template($module_file, $module, $args{author}, $args{email});
464              
465 3 50       116 chdir '..' or croak "Can't change into original directory";
466             }
467             sub manifest_skip {
468 2     2 1 174708 my ($dir) = @_;
469              
470 2   100     14 $dir //= '.';
471              
472 2         20 my @content = _manifest_skip_file();
473              
474 2         16 _manifest_skip_write_file($dir, \@content);
475              
476 2         14 return @content;
477             }
478             sub manifest_t {
479 2     2 1 186536 my ($dir) = @_;
480              
481 2   100     21 $dir //= './t';
482              
483 2         10 my @content = _manifest_t_file();
484              
485 2         16 _manifest_t_write_file($dir, \@content);
486              
487 2         14 return @content;
488             }
489             sub move_distribution_files {
490 4     4 1 3199 my ($module) = @_;
491              
492 4 100       16 if (! defined $module) {
493 1         1705 croak("_move_distribution_files() requires a module name sent in");
494             }
495              
496 3         7 my $module_dir = $module;
497 3         32 $module_dir =~ s/::/-/g;
498              
499 3 100       36 my @move_count = rmove_glob("$module_dir/*", '.')
500             or croak("Can't move files from the '$module_dir' directory: $!");
501              
502 2         62272 my $dist_count = _default_distribution_file_count();
503              
504 2         51 for my $outer_idx (0..$#move_count) {
505 16         24 my $outer = $move_count[$outer_idx];
506 16         23 for my $inner_idx (0..$#$outer) {
507 16         24 my $inner = $move_count[$outer_idx][$inner_idx];
508 16         22 for (0..$#$inner) {
509 48 100       86 if ($inner->[$_] != $dist_count->[$outer_idx][$inner_idx][$_]) {
510 1         369 croak("Results from the move are mismatched... bailing out");
511             }
512             }
513             }
514             }
515              
516 1 50       372 rmtree $module_dir or croak("Couldn't remove the '$module_dir' directory");
517              
518 1         29 return 0;
519             }
520             sub remove_unwanted_files {
521 2     2 1 63915 for (_unwanted_filesystem_entries()) {
522 8         1757 rmtree $_;
523             }
524 2         26 make_manifest();
525 2         17 return 0;
526             }
527             sub make_dist {
528 0     0 1 0 my ($verbose) = @_;
529              
530 0         0 my $cmd = "${\MAKE} dist";
  0         0  
531 0 0   0   0 $verbose ? `$cmd` : capture_merged {`$cmd`};
  0         0  
532              
533 0 0       0 if ($? != 0) {
534 0         0 croak("Exit code $? returned... '${\MAKE} dist' failed");
  0         0  
535             }
536              
537 0         0 return $?;
538             }
539             sub make_distclean {
540 3     3 1 62 my ($verbose) = @_;
541              
542 3         13 my $cmd = "${\MAKE} distclean";
  3         27  
543 3 50   3   569 $verbose ? print `$cmd` : capture_merged {`$cmd`};
  3         950016  
544              
545 3 50       3240 if ($? != 0) {
546 0         0 croak("Exit code $? returned... '${\MAKE} distclean' failed\n");
  0         0  
547             }
548              
549 3         53 return $?;
550             }
551             sub make_manifest {
552 3     3 1 1315 my ($verbose) = @_;
553              
554 3 50       22 if ($verbose) {
555 0 0       0 if (-f 'MANIFEST') {
556 0 0       0 unlink 'MANIFEST' or die "make_manifest() Couldn't remove MANIFEST\n";
557             }
558 0         0 print `$^X Makefile.PL`;
559 0         0 print `${\MAKE} manifest`;
  0         0  
560 0         0 make_distclean($verbose);
561             }
562             else {
563             capture_merged {
564 3 100   3   4155 if (-f 'MANIFEST') {
565 1 50       88 unlink 'MANIFEST' or die "make_manifest() Couldn't remove MANIFEST\n";
566             }
567 3         2548083 `$^X Makefile.PL`;
568 3         265 `${\MAKE} manifest`;
  3         1125428  
569 3         142 make_distclean($verbose);
570 3         129 };
571             }
572              
573 3 50       2220 if ($? != 0) {
574 0         0 croak("Exit code $? returned... '${\MAKE} manifest' failed\n");
  0         0  
575             }
576              
577 3         20 return $?;
578             }
579             sub make_test {
580 0     0 1 0 my ($verbose) = @_;
581              
582 0 0       0 if ($verbose) {
583 0         0 print `$^X Makefile.PL`;
584 0         0 print `${\MAKE} test`;
  0         0  
585             }
586             capture_merged {
587 0     0   0 `$^X Makefile.PL`;
588 0         0 `${\MAKE} test`;
  0         0  
589 0         0 };
590              
591 0 0       0 if ($? != 0) {
592 0         0 croak("Exit code $? returned... '${\MAKE} test' failed\n");
  0         0  
593             }
594              
595 0         0 return $?;
596             }
597             sub version_bump {
598 14     14 1 401668 my ($version, $fs_entry) = @_;
599              
600 14         32 my $dry_run = 0;
601              
602 14 100 100     132 if (defined $version && $version =~ /^-/) {
603 5         66 print "\nDry run\n\n";
604 5         28 $version =~ s/-//;
605 5         12 $dry_run = 1;
606             }
607              
608 14   100     60 $fs_entry //= DEFAULT_DIR;
609              
610 14         87 _validate_version($version);
611 10         44 _validate_fs_entry($fs_entry);
612              
613 8         41 my @module_files = _module_find_files($fs_entry);
614              
615 8         7805 my %files;
616              
617 8         19 for (@module_files) {
618 23         63 my $current_version = _module_extract_file_version($_);
619 23         39 my $version_line = _module_extract_file_version_line($_);
620 23         5864 my @file_contents = _module_fetch_file_contents($_);
621              
622 23 100       62 if (! defined $version_line) {
623 3         9 next;
624             }
625              
626 20 100       42 if (! defined $current_version) {
627 3         9 next;
628             }
629              
630 17 100       267 if (version->parse($current_version) >= version->parse($version)) {
631 1         162 croak(
632             "Your new version $version must be greater than the current " .
633             "one, $current_version"
634             );
635             }
636              
637 16         60 my $mem_file;
638              
639 16 50       125 open my $wfh, '>', \$mem_file or croak("Can't open mem file!: $!");
640              
641 16         39 for my $line (@file_contents) {
642 495         442 chomp $line;
643              
644 495 100       549 if ($line eq $version_line) {
645 16         350 $line =~ s/$current_version/$version/;
646             }
647              
648 495         416 $line .= "\n";
649              
650             # Write out the line to the in-memory temp file
651 495         534 print $wfh $line;
652              
653 495         565 $files{$_}{from} = $current_version;
654 495         499 $files{$_}{to} = $version;
655             }
656              
657 16         29 close $wfh;
658              
659 16         33 $files{$_}{dry_run} = $dry_run;
660 16         34 $files{$_}{content} = $mem_file;
661              
662 16 100       67 if (! $dry_run) {
663             # Write out the actual file
664 5         17 _module_write_file($_, $mem_file);
665             }
666             }
667 7         46 return \%files;
668             }
669             sub version_incr {
670 1006     1006 1 777989 my ($version) = @_;
671              
672 1006 100       2615 croak("version_incr() needs a version number sent in") if ! defined $version;
673              
674 1005         2519 _validate_version($version);
675              
676             # Increment the least-significant digit while preserving the version's
677             # precision (eg. 3.1802 -> 3.1803, not 3.19)
678              
679 1004 50       5978 my $decimals = $version =~ /\.(\d+)$/ ? length $1 : 0;
680 1004         2201 my $increment = 1 / 10 ** $decimals;
681              
682 1004         9587 return sprintf("%.${decimals}f", $version + $increment);
683             }
684             sub version_info {
685 5     5 1 6140 my ($fs_entry) = @_;
686              
687 5   50     14 $fs_entry //= DEFAULT_DIR;
688              
689 5         24 _validate_fs_entry($fs_entry);
690              
691 5         17 my @module_files = _module_find_files($fs_entry);
692              
693 5         5444 my %version_info;
694              
695 5         14 for (@module_files) {
696 15         34 my $version = _module_extract_file_version($_);
697 15         42 $version_info{$_} = $version;
698             }
699              
700 5         37 return \%version_info;
701             }
702              
703             # Changes file related
704              
705             sub _changes_tie {
706             # Ties the Changes file to an array
707              
708 4     4   16 my ($changes) = @_;
709 4 50       12 croak("_changes_tie() needs a Changes file name sent in") if ! defined $changes;
710              
711 4         40 my $tie = tie my @changes, 'Tie::File', $changes;
712 4         571 return (\@changes, $tie);
713             }
714             sub _changes_write_file {
715             # Writes out the custom Changes file
716              
717 2     2   6 my ($file, $content) = @_;
718              
719 2 50       153 open my $fh, '>', $file or cluck("Can't open file $file: $!");
720              
721 2         6 for (@$content) {
722 8         27 print $fh "$_\n"
723             }
724              
725 2         185 close $fh;
726              
727 2         27 return 0;
728             }
729              
730             # CI related
731              
732             sub _ci_github_write_file {
733             # Writes out the Github Actions config file
734              
735 7     7   317 my ($contents) = @_;
736              
737 7 100       26 if (ref $contents ne 'ARRAY') {
738 1         81 croak("_ci_github_write_file() requires an array ref of contents");
739             }
740              
741 6   50     40 my $ci_file //= GITHUB_CI_PATH . GITHUB_CI_FILE;
742              
743 6 100       793 make_path(GITHUB_CI_PATH) if ! -d GITHUB_CI_PATH;
744              
745 6 50       578 open my $fh, '>', $ci_file or croak $!;
746              
747 6         299 print $fh "$_\n" for @$contents;
748             }
749              
750             # Configuration related
751              
752             sub _config_file_write {
753 4     4   8 my ($file, $contents) = @_;
754              
755 4 50       12 if (ref $contents ne 'HASH') {
756 0         0 croak("_config_file_write() requires a hash ref of contents");
757             }
758              
759 4         47 my $jobj = JSON->new;
760              
761 4         37 my $json = $jobj->pretty->encode($contents);
762              
763 4 50       2415 open my $fh, '>', $file or croak "Can't open config $file for writing: $!";
764              
765 4         318 print $fh $json;
766              
767             }
768              
769             # Distribution related
770              
771             sub _default_distribution_file_count {
772             # Returns the file count in a distribution
773             # This is used to ensure everything moved OK
774              
775             return [
776 1     1   16 [ [1, 0, 0] ],
777             [ [1, 0, 0] ],
778             [ [3, 2, 0] ],
779             [ [1, 0, 0] ],
780             [ [1, 0, 0] ],
781             [ [1, 0, 0] ],
782             [ [5, 1, 0] ],
783             [ [2, 1, 0] ],
784             ];
785             }
786              
787             # Git related
788              
789             sub _git_ignore_write_file {
790             # Writes out the .gitignore file
791              
792 2     2   5 my ($dir, $content) = @_;
793              
794 2 50       302 open my $fh, '>', "$dir/.gitignore" or croak $!;
795              
796 2         9 for (@$content) {
797 48         83 print $fh "$_\n"
798             }
799              
800 2         113 return 0;
801             }
802              
803             # Makefile related
804              
805             sub _makefile_tie {
806             # Ties the Makefile.PL file to an array
807              
808 8     8   15 my ($mf) = @_;
809 8 50       19 croak("_makefile_tie() needs a Makefile name sent in") if ! defined $mf;
810              
811 8         75 my $tie = tie my @mf, 'Tie::File', $mf;
812 8         1234 return (\@mf, $tie);
813             }
814             sub _makefile_insert_meta_merge {
815             # Inserts the META_MERGE section into Makefile.PL
816              
817 6     6   13 my ($mf) = @_;
818              
819 6 50       15 croak("_makefile_insert_meta_merge() needs a Makefile tie sent in") if ! defined $mf;
820              
821             # Check to ensure we're not duplicating
822 6 100       15 return if grep /META_MERGE/, @$mf;
823              
824 4         11740 for (0..$#$mf) {
825 51 100       4012 if ($mf->[$_] =~ /MIN_PERL_VERSION/) {
826 4         368 splice @$mf, $_+1, 0, _makefile_section_meta_merge();
827 4         1848 last;
828             }
829             }
830             }
831             sub _makefile_insert_bugtracker {
832             # Inserts bugtracker information into Makefile.PL
833              
834 5     5   12 my ($author, $repo, $makefile) = @_;
835              
836 5 100       15 if (! defined $makefile) {
837 1         106 croak("_makefile_insert_bugtracker() needs author, repo and makefile");
838             }
839              
840 4         14 my ($mf, $tie) = _makefile_tie($makefile);
841              
842 4 100       38 return -1 if grep /bugtracker/, @$mf;
843              
844 3 50       7648 if (grep ! /META_MERGE/, @$mf) {
845 3         9300 _makefile_insert_meta_merge($mf);
846             }
847              
848 3         3095 for (0..$#$mf) {
849 47 100       2686 if ($mf->[$_] =~ /resources => \{/) {
850 3         234 splice @$mf, $_+1, 0, _makefile_section_bugtracker($author, $repo);
851 3         1217 last;
852             }
853             }
854 3         10 untie $tie;
855              
856 3         15 return 0;
857             }
858             sub _makefile_insert_repository {
859             # Inserts repository information to Makefile.PL
860              
861 5     5   10 my ($author, $repo, $makefile) = @_;
862              
863 5 100       13 if (! defined $makefile) {
864 1         118 croak("_makefile_insert_repository() needs author, repo and makefile");
865             }
866              
867 4         12 my ($mf, $tie) = _makefile_tie($makefile);
868              
869 4 100       19 return -1 if grep /repository/, @$mf;
870              
871 3 50       7551 if (grep ! /META_MERGE/, @$mf) {
872 3         9215 _makefile_insert_meta_merge($mf);
873             }
874              
875 3         5287 for (0..$#$mf) {
876 47 100       2666 if ($mf->[$_] =~ /resources => \{/) {
877 3         233 splice @$mf, $_+1, 0, _makefile_section_repo($author, $repo);
878 3         1352 last;
879             }
880             }
881 3         8 untie $tie;
882              
883 3         16 return 0;
884             }
885              
886             # MANIFEST related
887              
888             sub _manifest_skip_write_file {
889             # Writes out the MANIFEST.SKIP file
890              
891 2     2   4 my ($dir, $content) = @_;
892              
893 2 50       294 open my $fh, '>', "$dir/MANIFEST.SKIP" or croak $!;
894              
895 2         8 for (@$content) {
896 74         92 print $fh "$_\n"
897             }
898              
899 2         92 return 0;
900             }
901             sub _manifest_t_write_file {
902             # Writes out the t/manifest.t test file
903              
904 2     2   4 my ($dir, $content) = @_;
905              
906 2 50       306 open my $fh, '>', "$dir/manifest.t"
907             or croak("Can't open t/manifest.t for writing: $!\n");
908              
909 2         16 for (@$content) {
910 28         60 print $fh "$_\n"
911             }
912              
913 2         174 return 0;
914             }
915              
916             # Module related
917              
918             sub _module_extract_file_version {
919             # Extracts the version number from a module's $VERSION definition line
920              
921 38     38   82 my ($module_file) = @_;
922              
923 38         65 my $version_line = _module_extract_file_version_line($module_file);
924              
925 38 100       10263 if (defined $version_line) {
926              
927 33 50       194 if ($version_line =~ /=(.*)$/) {
928 33         84 my $ver = $1;
929              
930 33         142 $ver =~ s/\s+//g;
931 33         88 $ver =~ s/;//g;
932 33         53 $ver =~ s/[a-zA-Z]+//g;
933 33         51 $ver =~ s/"//g;
934 33         84 $ver =~ s/'//g;
935              
936 33 100       37 if (! defined eval { version->parse($ver); 1 }) {
  33         305  
  28         95  
937 5         50 warn("$_: Can't find a valid version\n");
938 5         25 return undef;
939             }
940              
941 28         73 return $ver;
942             }
943             }
944             else {
945 5         77 warn("$_: Can't find a \$VERSION definition\n");
946             }
947 5         30 return undef;
948             }
949             sub _module_extract_file_version_line {
950             # Extracts the $VERSION definition line from a module file
951              
952 61     61   103 my ($module_file) = @_;
953              
954 61         320 my $doc = PPI::Document->new($module_file);
955              
956             my $token = $doc->find(
957             sub {
958 3262 100   3262   24338 $_[1]->isa("PPI::Statement::Variable")
959             and $_[1]->content =~ /\$VERSION/;
960             }
961 61         337909 );
962              
963 61 100       739 return undef if ref $token ne 'ARRAY';
964              
965 53         113 my $version_line = $token->[0]->content;
966              
967 53         1198 return $version_line;
968             }
969             sub _module_fetch_file_contents {
970             # Fetches the file contents of a module file
971              
972 23     23   50 my ($file) = @_;
973              
974 23 50       1079 open my $fh, '<', $file
975             or croak("Can't open file '$file' for reading!: $!");
976              
977 23         611 my @contents = <$fh>;
978 23         207 close $fh;
979 23         213 return @contents;
980             }
981             sub _module_find_files {
982             # Finds module files
983              
984 21     21   767 my ($fs_entry, $module) = @_;
985              
986 21   50     55 $fs_entry //= DEFAULT_DIR;
987              
988 21 100       99 if (defined $module) {
989 1         4 $module =~ s/::/\//g;
990 1         3 $module .= '.pm';
991             }
992             else {
993 20         38 $module = '*.pm';
994             }
995              
996              
997 21         854 return File::Find::Rule->file()
998             ->name($module)
999             ->in($fs_entry);
1000             }
1001             sub _module_insert_ci_badges {
1002             # Inserts the CI and Coveralls badges into POD
1003              
1004 7     7   25 my ($author, $repo, $module_file) = @_;
1005              
1006 7         23 my ($mf, $tie) = _module_tie($module_file);
1007              
1008 7 100       38 return -1 if grep /badge\.svg/, @$mf;
1009              
1010 5         11645 for (0..$#$mf) {
1011 68 100       5884 if ($mf->[$_] =~ /^=head1 NAME/) {
1012 3         334 splice @$mf, $_+3, 0, _module_section_ci_badges($author, $repo);
1013 3         3404 last;
1014             }
1015             }
1016 5         165 untie $tie;
1017              
1018 5         20 return 0;
1019             }
1020             sub _module_tie {
1021             # Ties a module file to an array
1022              
1023 7     7   16 my ($mod_file) = @_;
1024 7 50       20 croak("Acme-STEVEB() needs a module file name sent in") if ! defined $mod_file;
1025              
1026 7         62 my $tie = tie my @mf, 'Tie::File', $mod_file;
1027 7         1072 return (\@mf, $tie);
1028             }
1029             sub _module_write_file {
1030             # Writes out a Perl module file
1031              
1032 5     5   13 my ($module_file, $content) = @_;
1033              
1034 5 50       539 open my $wfh, '>', $module_file or croak("Can't open '$module_file' for writing!: $!");
1035              
1036 5         49 print $wfh $content;
1037              
1038 5 50       609 close $wfh or croak("Can't close the temporary memory module file!: $!");
1039             }
1040             sub _module_write_template {
1041             # Writes out our custom module template after init()
1042              
1043 7     7   26 my ($module_file, $module, $author, $email) = @_;
1044              
1045 7 100       26 if (! defined $module_file) {
1046 1         87 croak("_module_write_template() needs the module's file name sent in");
1047             }
1048              
1049 6 100 100     63 if (! defined $module || ! defined $author || ! defined $email) {
      100        
1050 3         339 croak("_module_template_file() requires 'module', 'author' and 'email' parameters");
1051             }
1052              
1053 3         49 my @content = _module_template_file($module, $author, $email);
1054              
1055 3 50       502 open my $wfh, '>', $module_file or croak("Can't open '$module_file' for writing!: $!");
1056              
1057 3         168 print $wfh "$_\n" for @content;
1058             }
1059              
1060             # POD related
1061              
1062             sub _pod_extract_file_copyright {
1063             # Extracts the copyright year from POD
1064              
1065 9     9   13 my ($module_file) = @_;
1066              
1067 9         17 my $copyright_line = _pod_extract_file_copyright_line($module_file);
1068              
1069 9 50       34 if (defined $copyright_line) {
1070 9 100       23 if ($copyright_line =~ /^Copyright\s+(\d{4})(?:\s*[-,]\s*(\d{4}))?\s+\w+/) {
1071             # For a range, report the latter (most recent) year
1072 5 100       18 return defined $2 ? $2 : $1;
1073             }
1074             }
1075             else {
1076 0         0 warn("$_: Can't find a Copyright definition\n");
1077             }
1078 4         7 return undef;
1079             }
1080             sub _pod_extract_file_copyright_line {
1081             # Extracts the Copyright line from a module file
1082              
1083 9     9   11 my ($pod_file) = @_;
1084              
1085 9 50       197 open my $fh, '<', $pod_file or croak("Can't open POD file $pod_file: $!");
1086              
1087 9         130 while (<$fh>) {
1088 144 100       284 if (/^Copyright\s+\d{4}(?:\s*[-,]\s*\d{4})?\s+\w+/) {
1089 5         54 return $_;
1090             }
1091             }
1092             }
1093             sub _pod_find_files {
1094             # Finds POD files
1095              
1096 4     4   7 my ($fs_entry) = @_;
1097              
1098 4   50     9 $fs_entry //= DEFAULT_POD_DIR;
1099              
1100 4         137 return File::Find::Rule->file()
1101             ->name('*.pod', '*.pm', '*.pl')
1102             ->in($fs_entry);
1103             }
1104             sub _pod_tie {
1105             # Ties a POD file to an array
1106              
1107 9     9   14 my ($pod_file) = @_;
1108 9 50       15 croak("_pod_tie() needs a POD file name sent in") if ! defined $pod_file;
1109              
1110 9         39 my $tie = tie my @pf, 'Tie::File', $pod_file;
1111 9         1005 return (\@pf, $tie);
1112             }
1113              
1114             # Validation related
1115              
1116             sub _dist_dir_re {
1117             # Capture permutations of the distribution directory for various
1118             # CPAN testers
1119             # Use YAPE::Regex::Explain for details
1120              
1121 15     15   729252 return qr/dist-mgr(?:-\d+\.\d+)?(?:-\w+|_\d+)?$/i;
1122             }
1123             sub _validate_git {
1124 0 0   0   0 my $sep = $^O =~ /win32/i ? ';' : ':';
1125 0         0 return grep {-x "$_/git" } split /$sep/, $ENV{PATH};
  0         0  
1126             }
1127             sub _validate_fs_entry {
1128             # Validates a file system entry as valid
1129              
1130 20     20   45 my ($fs_entry) = @_;
1131              
1132 20 50       61 cluck("Need name of dir or file!") if ! defined $fs_entry;
1133              
1134 20 100       552 return FSTYPE_IS_DIR if -d $fs_entry;
1135 9 100       104 return FSTYPE_IS_FILE if -f $fs_entry;
1136              
1137 3         518 croak("File system entry '$fs_entry' is invalid");
1138             }
1139             sub _validate_version {
1140             # Parses a version number to ensure it is valid
1141              
1142 1020     1020   1618 my ($version) = @_;
1143              
1144 1020 100       1881 croak("version parameter must be supplied!") if ! defined $version;
1145              
1146 1018 100       1295 if (! defined eval { version->parse($version); 1 }) {
  1018         6844  
  1015         3385  
1147 3         379 croak("The version number '$version' specified is invalid");
1148             }
1149             }
1150              
1151             # Miscellaneous
1152              
1153             sub _export_private {
1154 25     25   72 push @EXPORT_OK, @EXPORT_PRIVATE;
1155 25         117 return \@EXPORT_OK;
1156             }
1157       0     sub __placeholder {}
1158              
1159             1;
1160             __END__