File Coverage

blib/lib/Dist/Mgr.pm
Criterion Covered Total %
statement 505 552 91.4
branch 171 232 73.7
condition 52 68 76.4
subroutine 77 92 83.7
pod 34 34 100.0
total 839 978 85.7


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