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   1790540 use strict;
  25         256  
  25         661  
4 25     25   112 use warnings;
  25         48  
  25         584  
5 25     25   9279 use version;
  25         42066  
  25         127  
6              
7 25     25   10579 use Capture::Tiny qw(:all);
  25         474611  
  25         3192  
8 25     25   180 use Carp qw(croak cluck);
  25         39  
  25         1169  
9 25     25   10323 use CPAN::Uploader;
  25         1195851  
  25         943  
10 25     25   207 use Cwd qw(getcwd);
  25         44  
  25         1292  
11 25     25   1372 use Data::Dumper;
  25         12269  
  25         1027  
12 25     25   11597 use Digest::SHA;
  25         64586  
  25         1214  
13 25     25   11559 use Dist::Mgr::FileData qw(:all);
  25         65  
  25         3932  
14 25     25   9925 use Dist::Mgr::Git qw(:all);
  25         71  
  25         3715  
15 25     25   174 use File::Copy;
  25         47  
  25         1241  
16 25     25   139 use File::Copy::Recursive qw(rmove_glob);
  25         48  
  25         1055  
17 25     25   136 use File::Path qw(make_path rmtree);
  25         37  
  25         1133  
18 25     25   140 use File::Find::Rule;
  25         54  
  25         290  
19 25     25   16363 use JSON;
  25         215595  
  25         131  
20 25     25   2804 use Module::Starter;
  25         45  
  25         225  
21 25     25   2820 use PPI;
  25         45  
  25         429  
22 25     25   107 use Term::ReadKey;
  25         38  
  25         1472  
23 25     25   127 use Tie::File;
  25         45  
  25         528  
24              
25 25     25   139 use Exporter qw(import);
  25         38  
  25         3407  
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.13';
73              
74             use constant {
75 25 50       148783 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   164 };
  25         44  
86              
87             # Public
88              
89             sub add_bugtracker {
90 6     6 1 51088 my ($author, $repo, $makefile) = @_;
91              
92 6 100 100     53 if (! defined $author || ! defined $repo) {
93 2         278 croak("Usage: add_bugtracker(\$author, \$repository_name)\n");
94             }
95              
96 4   100     15 $makefile //= 'Makefile.PL';
97              
98 4         19 _makefile_insert_bugtracker($author, $repo, $makefile);
99             }
100             sub add_repository {
101 6     6 1 49908 my ($author, $repo, $makefile) = @_;
102              
103 6 100 100     55 if (! defined $author || ! defined $repo) {
104 2         327 croak("Usage: add_repository(\$author, \$repository_name)\n");
105             }
106              
107 4   100     23 $makefile //= 'Makefile.PL';
108              
109 4         18 _makefile_insert_repository($author, $repo, $makefile);
110             }
111             sub changes {
112 2     2 1 10413 my ($module, $file) = @_;
113              
114 2 50       17 croak("changes() needs a module parameter") if ! defined $module;
115              
116 2   100     19 $file //= 'Changes';
117              
118             # Overwrite the Changes file if there aren't any dates in it
119              
120 2         6 my @contents;
121              
122 2         8 my $changes_date_count = 0;
123              
124 2 50       34 if (-e $file) {
125 2         21 my ($contents, $tie) = _changes_tie($file);
126 2         39 $changes_date_count = grep /\d{4}-\d{2}-\d{2}/, $contents;
127 2         18 untie $tie;
128             }
129 2 50 33     139 if (! -e $file || ! $changes_date_count) {
130 2         24 my @contents = _changes_file($module);
131 2         10 _changes_write_file($file, \@contents);
132             }
133              
134 2         9 return @contents;
135             }
136             sub changes_bump {
137 1     1 1 4006 my ($version, $file) = @_;
138              
139 1 50       5 croak("changes_bump() requires a version sent in") if ! defined $version;
140 1         5 _validate_version($version);
141              
142 1   50     4 $file //= 'Changes';
143              
144 1         5 my ($contents, $tie) = _changes_tie($file);
145              
146 1         4 for (0..$#$contents) {
147 3 100       358 if ($contents->[$_] =~ /^\d+\.\d+\s+/) {
148 1         112 $contents->[$_-1] = "\n$version UNREL\n -\n\n";
149 1         300 last;
150             }
151             }
152              
153 1         6 untie $tie;
154             }
155             sub changes_date {
156 1     1 1 3161 my ($file) = @_;
157              
158 1   50     4 $file //= 'Changes';
159              
160 1         4 my ($contents, $tie) = _changes_tie($file);
161              
162 1         18 my ($d, $m, $y) = (localtime)[3, 4, 5];
163 1         4 $y += 1900;
164 1         2 $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         21 for (0..$#$contents) {
170 3 100       338 if ($contents->[$_] =~ /^(.*)\s+UNREL/) {
171 1         115 $contents->[$_] = "$1 $y-$m-$d";
172 1         300 last;
173             }
174             }
175              
176 1         8 untie $tie;
177             }
178             sub ci_badges {
179 9 100   9 1 16671 if (scalar @_ < 2) {
180 2         263 croak("ci_badges() needs \$author and \$repo sent in");
181             }
182              
183 7         26 my ($author, $repo, $fs_entry) = @_;
184              
185 7   50     40 $fs_entry //= DEFAULT_DIR;
186              
187 7         20 my $exit = 0;
188              
189 7         34 for (_module_find_files($fs_entry)) {
190 7 100       5558 $exit = -1 if _module_insert_ci_badges($author, $repo, $_) == -1;
191             }
192              
193 7         8665 return $exit;
194             }
195             sub ci_github {
196 9     9 1 91814 my ($os) = @_;
197              
198 9 100 100     48 if (defined $os && ref $os ne 'ARRAY') {
199 3         341 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       94 if (-e 'MANIFEST.SKIP') {
205 5 50       168 open my $fh, '<', 'MANIFEST.SKIP'
206             or croak("Can't open MANIFEST.SKIP for reading");
207              
208 5         137 my @makefile_skip_contents = <$fh>;
209              
210 5 50       43 if (grep !m|\.github$|, @makefile_skip_contents) {
211 5         46 close $fh;
212 5 50       165 open my $wfh, '>>', 'MANIFEST.SKIP'
213             or croak("Can't open MANIFEST.SKIP for writing");
214              
215 5         147 print $wfh '^\.github/';
216             }
217             }
218             else {
219 1 50       67 open my $wfh, '>>', 'MANIFEST.SKIP'
220             or croak("Can't open MANIFEST.SKIP for writing");
221              
222 1         63 print $wfh '^\.github/';
223             }
224              
225 6         39 my @contents = _ci_github_file($os);
226 6         24 _ci_github_write_file(\@contents);
227              
228 6         63 return @contents;
229             }
230             sub config {
231 21     21 1 11582 my ($args, $file) = @_;
232              
233 21 100       93 if (! defined $args) {
    100          
234 1         176 croak("config() requires \$args hash reference parameter");
235             }
236             elsif (ref $args ne 'HASH') {
237 1         108 croak("\$args parameter must be a hash reference.");
238             }
239              
240 19 100       57 $file = config_file() if ! defined $file;
241 19         25 my $conf;
242              
243 19 100 66     401 if (-e $file && -f $file) {
244             {
245 15         36 local $/;
  15         59  
246 15 50       451 open my $fh, '<', $file or croak "Can't open config file $file: $!";
247 15         421 my $json = <$fh>;
248 15         135 $conf = decode_json $json;
249              
250 15         29 for (keys %{ $conf }) {
  15         63  
251 31 100       313 delete $conf->{$_} if $conf->{$_} eq '';
252             }
253             }
254             }
255             else {
256             # No config file present
257 4         22 _config_file_write($file, _config_file());
258              
259 4         274 print "\nGenerated new configuration file: $file\n";
260             }
261              
262 19 100       82 %{ $args } = (%{ $args }, %{ $conf }) if $conf;
  15         43  
  15         35  
  15         23  
263              
264 19         53 return $args;
265             }
266             sub config_file {
267 20 50   20 1 406 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 1     1 1 2705 my ($fs_entry) = @_;
275              
276 1   50     4 $fs_entry //= DEFAULT_POD_DIR;
277 1         3 _validate_fs_entry($fs_entry);
278              
279 1         21 my ($year) = (localtime)[5];
280 1         4 $year += 1900;
281              
282 1         3 my @pod_files = _pod_find_files($fs_entry);
283 1         1143 my %info;
284              
285 1         3 for my $pod_file (@pod_files) {
286 6         226 my ($contents, $tie) = _pod_tie($pod_file);
287              
288 6         21 for (0 .. $#$contents) {
289 129 100       13590 if ($contents->[$_] =~ /^(Copyright\s+)\d{4}(\s+.*)/) {
290 2         256 $contents->[$_] = "$1$year$2";
291 2         626 $info{$pod_file} = $year;
292 2         3 last;
293             }
294             }
295 6         379 untie $tie;
296             }
297              
298 1         42 return \%info;
299             }
300             sub copyright_info {
301 2     2 1 4340 my ($fs_entry) = @_;
302              
303 2   50     7 $fs_entry //= DEFAULT_POD_DIR;
304              
305 2         7 _validate_fs_entry($fs_entry);
306              
307 1         5 my @pod_files = _pod_find_files($fs_entry);
308              
309 1         1143 my %copyright_info;
310              
311 1         3 for my $file (@pod_files) {
312 6         35 my $copyright = _pod_extract_file_copyright($file);
313 6 100 66     20 next if ! defined $copyright || $copyright !~ /^\d{4}$/;
314 2 50       8 $copyright_info{$file} = $copyright if defined $copyright;
315             }
316              
317 1         5 return \%copyright_info;
318             }
319             sub cpan_upload {
320 5     5 1 1787 my ($dist_file_name, %args) = @_;
321              
322 5         15 config(\%args);
323              
324 5 100       15 if (! defined $dist_file_name) {
325 1         142 croak("cpan_upload() requires the name of a distribution file sent in");
326             }
327              
328 4 100       76 if (! -f $dist_file_name) {
329 1         111 croak("File name sent to cpan_upload() isn't a valid file");
330             }
331              
332 3   66     53 $args{user} //= $args{cpan_id};
333 3   66     13 $args{password} //= $args{cpan_pw};
334              
335 3 100       25 $args{user} = $ENV{CPAN_USERNAME} if ! $args{user};
336 3 100       6 $args{password} = $ENV{CPAN_PASSWORD} if ! $args{password};
337              
338 3 100 66     11 if (! $args{user} || ! $args{password}) {
339 2         1641 croak("\ncpan_upload() requires --cpan_id and --cpan_pw");
340             }
341              
342 1 50       4 if ($args{dry_run}) {
343 1         18 print "\nCPAN upload is in dry run mode... nothing will be uploaded\n";
344             }
345              
346             CPAN::Uploader->upload_file(
347 1         14 $dist_file_name,
348             \%args
349             );
350              
351 1         307 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 2840 my ($dir) = @_;
360              
361 2   100     18 $dir //= '.';
362              
363 2         13 my @content = _git_ignore_file();
364              
365 2         17 _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 10034 my (%args) = @_;
395              
396 8         25 config(\%args);
397              
398 8         69 my $cwd = getcwd();
399              
400 8 100       22 if ($cwd =~ _dist_dir_re()) {
401 1         141 croak "Can't run init() while in the '$cwd' directory";
402             }
403              
404 7 100       30 $args{license} = 'artistic2' if ! exists $args{license};
405 7         12 $args{builder} = 'ExtUtils::MakeMaker';
406              
407 7         17 for (qw(modules author email)) {
408 18 100       35 if (! exists $args{$_}) {
409 3         287 croak("init() requires '$_' in the parameter hash");
410             }
411             }
412              
413 4 100       14 if (ref $args{modules} ne 'ARRAY') {
414 1         92 croak("init()'s 'modules' parameter must be an array reference");
415             }
416              
417 3 100       10 if ($args{verbose}) {
418 2         5 delete $args{verbose};
419 2         26 Module::Starter->create_distro(%args);
420             }
421             else {
422             capture_merged {
423 1     1   1458 Module::Starter->create_distro(%args);
424 1         23 };
425             }
426              
427 3         171208 my ($module) = (@{ $args{modules} })[0];
  3         31  
428 3         16 my $module_file = $module;
429 3         41 $module_file =~ s/::/\//g;
430 3         19 $module_file = "lib/$module_file.pm";
431              
432 3         12 my $module_dir = $module;
433 3         17 $module_dir =~ s/::/-/g;
434              
435 3 50       49 chdir $module_dir or croak("Can't change into directory '$module_dir'");
436              
437 3 50       147 unlink $module_file
438             or croak("Can't delete the Module::Starter module '$module_file': $!");
439              
440 3         67 _module_write_template($module_file, $module, $args{author}, $args{email});
441              
442 3 50       79 chdir '..' or croak "Can't change into original directory";
443             }
444             sub manifest_skip {
445 2     2 1 2277 my ($dir) = @_;
446              
447 2   100     25 $dir //= '.';
448              
449 2         16 my @content = _manifest_skip_file();
450              
451 2         13 _manifest_skip_write_file($dir, \@content);
452              
453 2         17 return @content;
454             }
455             sub manifest_t {
456 2     2 1 2987 my ($dir) = @_;
457              
458 2   100     24 $dir //= './t';
459              
460 2         10 my @content = _manifest_t_file();
461              
462 2         13 _manifest_t_write_file($dir, \@content);
463              
464 2         16 return @content;
465             }
466             sub move_distribution_files {
467 4     4 1 3072 my ($module) = @_;
468              
469 4 100       27 if (! defined $module) {
470 1         148 croak("_move_distribution_files() requires a module name sent in");
471             }
472              
473 3         7 my $module_dir = $module;
474 3         15 $module_dir =~ s/::/-/g;
475              
476 3 100       35 my @move_count = rmove_glob("$module_dir/*", '.')
477             or croak("Can't move files from the '$module_dir' directory: $!");
478              
479 2         48978 my $dist_count = _default_distribution_file_count();
480              
481 2         80 for my $outer_idx (0..$#move_count) {
482 16         35 my $outer = $move_count[$outer_idx];
483 16         29 for my $inner_idx (0..$#$outer) {
484 16         29 my $inner = $move_count[$outer_idx][$inner_idx];
485 16         29 for (0..$#$inner) {
486 48 100       113 if ($inner->[$_] != $dist_count->[$outer_idx][$inner_idx][$_]) {
487 1         366 croak("Results from the move are mismatched... bailing out");
488             }
489             }
490             }
491             }
492              
493 1 50       376 rmtree $module_dir or croak("Couldn't remove the '$module_dir' directory");
494              
495 1         37 return 0;
496             }
497             sub remove_unwanted_files {
498 2     2 1 71282 for (_unwanted_filesystem_entries()) {
499 8         1336 rmtree $_;
500             }
501 2         35 make_manifest();
502 2         15 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 41 my ($verbose) = @_;
518              
519 3         15 my $cmd = "${\MAKE} distclean";
  3         42  
520 3 50   3   531 $verbose ? print `$cmd` : capture_merged {`$cmd`};
  3         206997  
521              
522 3 50       3209 if ($? != 0) {
523 0         0 croak("Exit code $? returned... '${\MAKE} distclean' failed\n");
  0         0  
524             }
525              
526 3         54 return $?;
527             }
528             sub make_manifest {
529 3     3 1 985 my ($verbose) = @_;
530              
531 3 50       24 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   4090 if (-f 'MANIFEST') {
542 1 50       61 unlink 'MANIFEST' or die "make_manifest() Couldn't remove MANIFEST\n";
543             }
544 3         675165 `$^X Makefile.PL`;
545 3         268 `${\MAKE} manifest`;
  3         145445  
546 3         148 make_distclean($verbose);
547 3         131 };
548             }
549              
550 3 50       1809 if ($? != 0) {
551 0         0 croak("Exit code $? returned... '${\MAKE} manifest' failed\n");
  0         0  
552             }
553              
554 3         23 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 35404 my ($version, $fs_entry) = @_;
576              
577 14         24 my $dry_run = 0;
578              
579 14 100 100     103 if (defined $version && $version =~ /^-/) {
580 5         63 print "\nDry run\n\n";
581 5         27 $version =~ s/-//;
582 5         10 $dry_run = 1;
583             }
584              
585 14   100     48 $fs_entry //= DEFAULT_DIR;
586              
587 14         43 _validate_version($version);
588 10         57 _validate_fs_entry($fs_entry);
589              
590 8         30 my @module_files = _module_find_files($fs_entry);
591              
592 8         6286 my %files;
593              
594 8         19 for (@module_files) {
595 23         56 my $current_version = _module_extract_file_version($_);
596 23         40 my $version_line = _module_extract_file_version_line($_);
597 23         6074 my @file_contents = _module_fetch_file_contents($_);
598              
599 23 100       55 if (! defined $version_line) {
600 3         9 next;
601             }
602              
603 20 100       38 if (! defined $current_version) {
604 3         10 next;
605             }
606              
607 17 100       269 if (version->parse($current_version) >= version->parse($version)) {
608 1         191 croak(
609             "Your new version $version must be greater than the current " .
610             "one, $current_version"
611             );
612             }
613              
614 16         47 my $mem_file;
615              
616 16 50       138 open my $wfh, '>', \$mem_file or croak("Can't open mem file!: $!");
617              
618 16         39 for my $line (@file_contents) {
619 495         492 chomp $line;
620              
621 495 100       628 if ($line eq $version_line) {
622 16         260 $line =~ s/$current_version/$version/;
623             }
624              
625 495         543 $line .= "\n";
626              
627             # Write out the line to the in-memory temp file
628 495         560 print $wfh $line;
629              
630 495         628 $files{$_}{from} = $current_version;
631 495         599 $files{$_}{to} = $version;
632             }
633              
634 16         32 close $wfh;
635              
636 16         35 $files{$_}{dry_run} = $dry_run;
637 16         35 $files{$_}{content} = $mem_file;
638              
639 16 100       61 if (! $dry_run) {
640             # Write out the actual file
641 5         15 _module_write_file($_, $mem_file);
642             }
643             }
644 7         30 return \%files;
645             }
646             sub version_incr {
647 503     503 1 215807 my ($version) = @_;
648              
649 503 100       1167 croak("version_incr() needs a version number sent in") if ! defined $version;
650              
651 502         555 my $incremented_version;
652              
653 502         996 _validate_version($version);
654 501         3478 return sprintf("%.2f", $version + '0.01');
655             }
656             sub version_info {
657 5     5 1 6712 my ($fs_entry) = @_;
658              
659 5   50     17 $fs_entry //= DEFAULT_DIR;
660              
661 5         16 _validate_fs_entry($fs_entry);
662              
663 5         18 my @module_files = _module_find_files($fs_entry);
664              
665 5         5179 my %version_info;
666              
667 5         14 for (@module_files) {
668 15         63 my $version = _module_extract_file_version($_);
669 15         66 $version_info{$_} = $version;
670             }
671              
672 5         27 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   13 my ($changes) = @_;
681 4 50       19 croak("_changes_tie() needs a Changes file name sent in") if ! defined $changes;
682              
683 4         79 my $tie = tie my @changes, 'Tie::File', $changes;
684 4         685 return (\@changes, $tie);
685             }
686             sub _changes_write_file {
687             # Writes out the custom Changes file
688              
689 2     2   10 my ($file, $content) = @_;
690              
691 2 50       146 open my $fh, '>', $file or cluck("Can't open file $file: $!");
692              
693 2         10 for (@$content) {
694 8         29 print $fh "$_\n"
695             }
696              
697 2         193 close $fh;
698              
699 2         19 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   345 my ($contents) = @_;
708              
709 7 100       22 if (ref $contents ne 'ARRAY') {
710 1         74 croak("_ci_github_write_file() requires an array ref of contents");
711             }
712              
713 6   50     31 my $ci_file //= GITHUB_CI_PATH . GITHUB_CI_FILE;
714              
715 6 100       765 make_path(GITHUB_CI_PATH) if ! -d GITHUB_CI_PATH;
716              
717 6 50       318 open my $fh, '>', $ci_file or croak $!;
718              
719 6         319 print $fh "$_\n" for @$contents;
720             }
721              
722             # Configuration related
723              
724             sub _config_file_write {
725 4     4   32 my ($file, $contents) = @_;
726              
727 4 50       14 if (ref $contents ne 'HASH') {
728 0         0 croak("_config_file_write() requires a hash ref of contents");
729             }
730              
731 4         57 my $jobj = JSON->new;
732              
733 4         60 my $json = $jobj->pretty->encode($contents);
734              
735 4 50       373 open my $fh, '>', $file or croak "Can't open config $file for writing: $!";
736              
737 4         292 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   10 my ($dir, $content) = @_;
765              
766 2 50       175 open my $fh, '>', "$dir/.gitignore" or croak $!;
767              
768 2         9 for (@$content) {
769 48         87 print $fh "$_\n"
770             }
771              
772 2         98 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   17 my ($mf) = @_;
781 8 50       21 croak("_makefile_tie() needs a Makefile name sent in") if ! defined $mf;
782              
783 8         74 my $tie = tie my @mf, 'Tie::File', $mf;
784 8         1437 return (\@mf, $tie);
785             }
786             sub _makefile_insert_meta_merge {
787             # Inserts the META_MERGE section into Makefile.PL
788              
789 6     6   19 my ($mf) = @_;
790              
791 6 50       18 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       29 return if grep /META_MERGE/, @$mf;
795              
796 4         12361 for (0..$#$mf) {
797 51 100       5133 if ($mf->[$_] =~ /MIN_PERL_VERSION/) {
798 4         457 splice @$mf, $_+1, 0, _makefile_section_meta_merge();
799 4         2321 last;
800             }
801             }
802             }
803             sub _makefile_insert_bugtracker {
804             # Inserts bugtracker information into Makefile.PL
805              
806 5     5   18 my ($author, $repo, $makefile) = @_;
807              
808 5 100       18 if (! defined $makefile) {
809 1         96 croak("_makefile_insert_bugtracker() needs author, repo and makefile");
810             }
811              
812 4         11 my ($mf, $tie) = _makefile_tie($makefile);
813              
814 4 100       22 return -1 if grep /bugtracker/, @$mf;
815              
816 3 50       10884 if (grep ! /META_MERGE/, @$mf) {
817 3         11495 _makefile_insert_meta_merge($mf);
818             }
819              
820 3         3682 for (0..$#$mf) {
821 47 100       3173 if ($mf->[$_] =~ /resources => \{/) {
822 3         318 splice @$mf, $_+1, 0, _makefile_section_bugtracker($author, $repo);
823 3         1388 last;
824             }
825             }
826 3         10 untie $tie;
827              
828 3         15 return 0;
829             }
830             sub _makefile_insert_repository {
831             # Inserts repository information to Makefile.PL
832              
833 5     5   17 my ($author, $repo, $makefile) = @_;
834              
835 5 100       20 if (! defined $makefile) {
836 1         113 croak("_makefile_insert_repository() needs author, repo and makefile");
837             }
838              
839 4         14 my ($mf, $tie) = _makefile_tie($makefile);
840              
841 4 100       25 return -1 if grep /repository/, @$mf;
842              
843 3 50       10633 if (grep ! /META_MERGE/, @$mf) {
844 3         11080 _makefile_insert_meta_merge($mf);
845             }
846              
847 3         6219 for (0..$#$mf) {
848 47 100       3297 if ($mf->[$_] =~ /resources => \{/) {
849 3         333 splice @$mf, $_+1, 0, _makefile_section_repo($author, $repo);
850 3         1444 last;
851             }
852             }
853 3         8 untie $tie;
854              
855 3         19 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   6 my ($dir, $content) = @_;
864              
865 2 50       180 open my $fh, '>', "$dir/MANIFEST.SKIP" or croak $!;
866              
867 2         37 for (@$content) {
868 74         113 print $fh "$_\n"
869             }
870              
871 2         97 return 0;
872             }
873             sub _manifest_t_write_file {
874             # Writes out the t/manifest.t test file
875              
876 2     2   6 my ($dir, $content) = @_;
877              
878 2 50       520 open my $fh, '>', "$dir/manifest.t"
879             or croak("Can't open t/manifest.t for writing: $!\n");
880              
881 2         15 for (@$content) {
882 28         75 print $fh "$_\n"
883             }
884              
885 2         130 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   70 my ($module_file) = @_;
894              
895 38         70 my $version_line = _module_extract_file_version_line($module_file);
896              
897 38 100       10382 if (defined $version_line) {
898              
899 33 50       176 if ($version_line =~ /=(.*)$/) {
900 33         76 my $ver = $1;
901              
902 33         119 $ver =~ s/\s+//g;
903 33         75 $ver =~ s/;//g;
904 33         62 $ver =~ s/[a-zA-Z]+//g;
905 33         46 $ver =~ s/"//g;
906 33         62 $ver =~ s/'//g;
907              
908 33 100       49 if (! defined eval { version->parse($ver); 1 }) {
  33         295  
  28         83  
909 5         55 warn("$_: Can't find a valid version\n");
910 5         30 return undef;
911             }
912              
913 28         69 return $ver;
914             }
915             }
916             else {
917 5         72 warn("$_: Can't find a \$VERSION definition\n");
918             }
919 5         29 return undef;
920             }
921             sub _module_extract_file_version_line {
922             # Extracts the $VERSION definition line from a module file
923              
924 61     61   86 my ($module_file) = @_;
925              
926 61         263 my $doc = PPI::Document->new($module_file);
927              
928             my $token = $doc->find(
929             sub {
930 3262 100   3262   32137 $_[1]->isa("PPI::Statement::Variable")
931             and $_[1]->content =~ /\$VERSION/;
932             }
933 61         346433 );
934              
935 61 100       742 return undef if ref $token ne 'ARRAY';
936              
937 53         114 my $version_line = $token->[0]->content;
938              
939 53         1352 return $version_line;
940             }
941             sub _module_fetch_file_contents {
942             # Fetches the file contents of a module file
943              
944 23     23   41 my ($file) = @_;
945              
946 23 50       869 open my $fh, '<', $file
947             or croak("Can't open file '$file' for reading!: $!");
948              
949 23         722 my @contents = <$fh>;
950 23         254 close $fh;
951 23         248 return @contents;
952             }
953             sub _module_find_files {
954             # Finds module files
955              
956 21     21   590 my ($fs_entry, $module) = @_;
957              
958 21   50     46 $fs_entry //= DEFAULT_DIR;
959              
960 21 100       57 if (defined $module) {
961 1         3 $module =~ s/::/\//g;
962 1         3 $module .= '.pm';
963             }
964             else {
965 20         41 $module = '*.pm';
966             }
967              
968              
969 21         661 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   24 my ($author, $repo, $module_file) = @_;
977              
978 7         46 my ($mf, $tie) = _module_tie($module_file);
979              
980 7 100       45 return -1 if grep /badge\.svg/, @$mf;
981              
982 5         14874 for (0..$#$mf) {
983 68 100       6665 if ($mf->[$_] =~ /^=head1 NAME/) {
984 3         369 splice @$mf, $_+3, 0, _module_section_ci_badges($author, $repo);
985 3         1836 last;
986             }
987             }
988 5         177 untie $tie;
989              
990 5         28 return 0;
991             }
992             sub _module_tie {
993             # Ties a module file to an array
994              
995 7     7   16 my ($mod_file) = @_;
996 7 50       24 croak("Acme-STEVEB() needs a module file name sent in") if ! defined $mod_file;
997              
998 7         71 my $tie = tie my @mf, 'Tie::File', $mod_file;
999 7         1176 return (\@mf, $tie);
1000             }
1001             sub _module_write_file {
1002             # Writes out a Perl module file
1003              
1004 5     5   13 my ($module_file, $content) = @_;
1005              
1006 5 50       2423 open my $wfh, '>', $module_file or croak("Can't open '$module_file' for writing!: $!");
1007              
1008 5         41 print $wfh $content;
1009              
1010 5 50       474 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   441 my ($module_file, $module, $author, $email) = @_;
1016              
1017 7 100       27 if (! defined $module_file) {
1018 1         76 croak("_module_write_template() needs the module's file name sent in");
1019             }
1020              
1021 6 100 100     61 if (! defined $module || ! defined $author || ! defined $email) {
      100        
1022 3         259 croak("_module_template_file() requires 'module', 'author' and 'email' parameters");
1023             }
1024              
1025 3         52 my @content = _module_template_file($module, $author, $email);
1026              
1027 3 50       203 open my $wfh, '>', $module_file or croak("Can't open '$module_file' for writing!: $!");
1028              
1029 3         159 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   12 my ($module_file) = @_;
1038              
1039 6         10 my $copyright_line = _pod_extract_file_copyright_line($module_file);
1040              
1041 6 50       19 if (defined $copyright_line) {
1042 6 100       15 if ($copyright_line =~ /^Copyright\s+(\d{4})\s+\w+/) {
1043 2         8 return $1;
1044             }
1045             }
1046             else {
1047 0         0 warn("$_: Can't find a Copyright definition\n");
1048             }
1049 4         8 return undef;
1050             }
1051             sub _pod_extract_file_copyright_line {
1052             # Extracts the Copyright line from a module file
1053              
1054 6     6   15 my ($pod_file) = @_;
1055              
1056 6 50       174 open my $fh, '<', $pod_file or croak("Can't open POD file $pod_file: $!");
1057              
1058 6         93 while (<$fh>) {
1059 129 100       328 if (/^Copyright\s+\d{4}\s+\w+/) {
1060 2         31 return $_;
1061             }
1062             }
1063             }
1064             sub _pod_find_files {
1065             # Finds POD files
1066              
1067 2     2   4 my ($fs_entry) = @_;
1068              
1069 2   50     5 $fs_entry //= DEFAULT_POD_DIR;
1070              
1071 2         53 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   13 my ($pod_file) = @_;
1079 6 50       13 croak("_pod_tie() needs a POD file name sent in") if ! defined $pod_file;
1080              
1081 6         30 my $tie = tie my @pf, 'Tie::File', $pod_file;
1082 6         771 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   571 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   62 my ($fs_entry) = @_;
1102              
1103 18 50       45 cluck("Need name of dir or file!") if ! defined $fs_entry;
1104              
1105 18 100       302 return FSTYPE_IS_DIR if -d $fs_entry;
1106 9 100       100 return FSTYPE_IS_FILE if -f $fs_entry;
1107              
1108 3         491 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   624 my ($version) = @_;
1114              
1115 517 100       1127 croak("version parameter must be supplied!") if ! defined $version;
1116              
1117 515 100       586 if (! defined eval { version->parse($version); 1 }) {
  515         2860  
  512         1530  
1118 3         298 croak("The version number '$version' specified is invalid");
1119             }
1120             }
1121              
1122             # Miscellaneous
1123              
1124             sub _export_private {
1125 25     25   84 push @EXPORT_OK, @EXPORT_PRIVATE;
1126 25         108 return \@EXPORT_OK;
1127             }
1128       0     sub __placeholder {}
1129              
1130             1;
1131             __END__