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