File Coverage

blib/lib/Minilla/Project.pm
Criterion Covered Total %
statement 81 442 18.3
branch 0 196 0.0
condition 0 53 0.0
subroutine 27 72 37.5
pod 0 30 0.0
total 108 793 13.6


line stmt bran cond sub pod time code
1             package Minilla::Project;
2 1     1   6 use strict;
  1         3  
  1         40  
3 1     1   5 use warnings;
  1         2  
  1         48  
4 1     1   5 use utf8;
  1         2  
  1         6  
5              
6 1     1   984 use TOML 0.92 qw(from_toml);
  1         16334  
  1         98  
7 1     1   10 use File::Basename qw(basename dirname);
  1         2  
  1         85  
8 1     1   606 use File::Spec::Functions qw(catdir catfile);
  1         911  
  1         137  
9 1     1   673 use DirHandle;
  1         3886  
  1         38  
10 1     1   764 use File::pushd;
  1         25054  
  1         83  
11 1     1   9 use CPAN::Meta;
  1         2  
  1         32  
12 1     1   1605 use Module::CPANfile;
  1         8703  
  1         108  
13 1     1   646 use Module::Runtime qw(require_module);
  1         5848  
  1         9  
14              
15 1     1   78 use Minilla;
  1         13  
  1         36  
16 1     1   791 use Minilla::Git qw(git_show_toplevel);
  1         22  
  1         159  
17 1     1   9 use Minilla::Logger;
  1         2  
  1         70  
18 1     1   1624 use Minilla::Metadata;
  1         4  
  1         45  
19 1     1   723 use Minilla::WorkDir;
  1         5  
  1         54  
20 1     1   9 use Minilla::ReleaseTest;
  1         2  
  1         27  
21 1     1   736 use Minilla::Unsupported;
  1         4  
  1         60  
22 1     1   584 use Minilla::ModuleMaker::ModuleBuild;
  1         4  
  1         43  
23 1     1   649 use Minilla::ModuleMaker::ModuleBuildTiny;
  1         5  
  1         50  
24 1     1   1703 use Minilla::ModuleMaker::ExtUtilsMakeMaker;
  1         4  
  1         51  
25 1     1   8 use Minilla::Util qw(slurp_utf8 find_dir cmd spew_raw slurp_raw spew_utf8);
  1         2  
  1         84  
26 1     1   7 use Encode qw(decode_utf8);
  1         2  
  1         130  
27 1     1   1007 use URI;
  1         12847  
  1         62  
28 1     1   693 use Term::Encoding qw(term_encoding);
  1         766  
  1         83  
29              
30 1     1   8 use Moo;
  1         2  
  1         9  
31              
32             has dir => (
33             is => 'rw',
34             builder => 1,
35             trigger => 1,
36             required => 1,
37             );
38              
39             has module_maker => (
40             is => 'ro',
41             default => sub {
42             my $self = shift;
43             if ($self->config && defined($self->config->{module_maker})) {
44             # Automatic require.
45             my $klass = $self->config->{module_maker};
46             $klass = $klass =~ s/^\+// ? $klass : "Minilla::ModuleMaker::$klass";
47             return $klass->new();
48             }
49             Minilla::ModuleMaker::ModuleBuildTiny->new()
50             },
51             lazy => 1,
52             );
53              
54             has dist_name => (
55             is => 'lazy',
56             );
57              
58             has build_class => (
59             is => 'lazy',
60             );
61              
62             has main_module_path => (
63             is => 'lazy',
64             );
65              
66             has metadata => (
67             is => 'lazy',
68             required => 1,
69             handles => [qw(name perl_version license)],
70             clearer => 1,
71             );
72              
73             has contributors => (
74             is => 'lazy',
75             );
76              
77             has work_dir => (
78             is => 'lazy',
79             );
80              
81             has files => (
82             is => 'lazy',
83             );
84              
85             has release_branch => (
86             is => 'lazy',
87             clearer => 1,
88             );
89              
90             has no_index => (
91             is => 'ro',
92             default => sub {
93             my $self = shift;
94             exists $self->config->{no_index} ?
95             $self->config->{no_index} :
96             {
97             directory => [qw(
98             t xt inc share eg examples author builder
99             ) ]
100             };
101             },
102             );
103              
104             has script_files => (
105             is => 'ro',
106             default => sub {
107             my $self = shift;
108             my $script_files = exists $self->config->{script_files} ?
109             $self->config->{script_files} :
110             ['script/*', 'bin/*'];
111             join ', ', map { "glob('$_')" } @$script_files;
112             },
113             );
114              
115 1     1   1191 no Moo;
  1         2  
  1         7  
116              
117             sub allow_pureperl {
118 0     0 0   my $self = shift;
119 0 0         $self->config->{allow_pureperl} ? 1 : 0;
120             }
121              
122             sub version {
123 0     0 0   my $self = shift;
124 0   0       my $version = $self->config->{version} || $self->metadata->version;
125 0 0         unless (defined $version) {
126 0           errorf("Minilla can't aggregate version number from '" . $self->main_module_path . '"');
127             }
128 0           return $version;
129             }
130              
131             sub static_install {
132 0     0 0   my $self = shift;
133 0 0         my $v = exists $self->config->{static_install} ? $self->config->{static_install} : 'auto';
134 0 0         return 0+$v if $v =~ /^\d+$/;
135 0 0         errorf "Found unsupported value '%s' for static_install in minil.toml", $v if $v ne 'auto';
136              
137 0 0         return 0 if $self->build_class ne 'Module::Build';
138 0 0         return 0 if $self->requires_external_bin;
139 0           my @script_files = eval $self->script_files; # XXX
140 0 0         return 0 if grep { !/^script\b/ } @script_files;
  0            
141 0 0 0       return 0 if %{$self->PL_files} or grep { /^lib\b.*\.PL$/ } @{$self->files};
  0            
  0            
  0            
142 0 0         return 0 if grep { /\.xs$/ } @{$self->files};
  0            
  0            
143 0 0         return 0 if @{$self->unsupported->os};
  0            
144              
145 0           return 1;
146             }
147              
148             sub authors {
149 0     0 0   my $self = shift;
150 0 0         if (my $authors_from = $self->config->{authors_from}) {
151 0           my $meta = Minilla::Metadata->new(
152             source => $authors_from
153             );
154 0           return $meta->authors;
155             }
156 0 0         $self->config->{authors} || $self->metadata->authors;
157             }
158              
159             sub unsupported {
160 0     0 0   my $self = shift;
161 0   0       my $unsupported = $self->config->{unsupported} || {};
162 0           return Minilla::Unsupported->new(%$unsupported);
163             }
164              
165             sub abstract {
166 0     0 0   my $self = shift;
167 0 0         if (my $abstract_from = $self->config->{abstract_from}) {
168 0           my $meta = Minilla::Metadata->new(
169             source => $abstract_from
170             );
171 0           return $meta->abstract;
172             }
173 0 0         $self->config->{abstract} || $self->metadata->abstract;
174             }
175              
176             sub badges {
177 0     0 0   my $self = shift;
178 0 0         $self->config->{badges} || [];
179             }
180              
181             sub tap_harness_args {
182 0     0 0   my $self = shift;
183 0           $self->config->{tap_harness_args};
184             }
185              
186             sub use_xsutil {
187 0     0 0   my $self = shift;
188 0 0         return defined $self->config->{XSUtil} ? 1 : 0;
189             }
190              
191             sub needs_compiler_c99 {
192 0     0 0   my $self = shift;
193 0 0         if( my $xsutil = $self->config->{XSUtil} ){
194 0 0         return $xsutil->{needs_compiler_c99} ? 1 : 0;
195             }
196             }
197              
198             sub needs_compiler_cpp {
199 0     0 0   my $self = shift;
200 0 0         if( my $xsutil = $self->config->{XSUtil} ){
201 0 0         return $xsutil->{needs_compiler_cpp} ? 1 : 0;
202             }
203             }
204              
205             sub generate_ppport_h {
206 0     0 0   my $self = shift;
207 0 0         if( my $xsutil = $self->config->{XSUtil} ){
208 0   0       return $xsutil->{generate_ppport_h} || 0;
209             }
210             }
211              
212             sub generate_xshelper_h {
213 0     0 0   my $self = shift;
214 0 0         if( my $xsutil = $self->config->{XSUtil} ){
215 0   0       return $xsutil->{generate_xshelper_h} || 0;
216             }
217             }
218              
219             sub cc_warnings{
220 0     0 0   my $self = shift;
221 0 0         if( my $xsutil = $self->config->{XSUtil} ){
222 0 0         return $xsutil->{cc_warnings} ? 1 : 0;
223             }
224             }
225              
226             sub _build_dir {
227 0     0     my $self = shift;
228 0           return git_show_toplevel();
229             }
230              
231             sub _trigger_dir {
232 0     0     my ($self, $dir) = @_;
233 0 0         unless (File::Spec->file_name_is_absolute($dir)) {
234 0           $self->dir(File::Spec->rel2abs($dir));
235             }
236             }
237              
238             sub config {
239 0     0 0   my $self = shift;
240              
241 0           my $toml_path = File::Spec->catfile($self->dir, 'minil.toml');
242 0 0         if (-f $toml_path) {
243 0           my ($conf, $err) = from_toml(slurp_utf8($toml_path));
244 0 0         if ($err) {
245 0           errorf("TOML error in %s: %s\n", $toml_path, $err);
246             }
247 0 0         $self->_patch_config_for_mb($conf) unless $conf->{module_maker};
248 0           $conf;
249             } else {
250 0           +{};
251             }
252             }
253              
254             sub _patch_config_for_mb {
255 0     0     my($self, $conf) = @_;
256              
257 0 0 0       if (exists $conf->{build} or exists $conf->{XSUtil}) {
258 0 0         warn <{__already_warned}++;
259             !
260             ! WARNING:
261             ! module_maker is not set in your Minilla config (minil.toml), but found [build] or [XSUtil] section in it.
262             ! Defaulting to Module::Build, but you're suggested to add the following to your minil.toml:
263             !
264             ! module_maker="ModuleBuild"
265             !
266             ! This friendly warning will go away in the next major release, and Minilla will default to ModuleBuildTiny
267             ! when module_maker is not explicitly set in minil.toml.
268             !
269             WARN
270 0           $conf->{module_maker} = "ModuleBuild";
271             }
272              
273 0           return;
274             }
275              
276             sub c_source {
277 0     0 0   my $self = shift;
278 0 0         $self->config->{c_source} ? join(' ', @{$self->config->{c_source}}) : '';
  0            
279             }
280              
281             sub _build_dist_name {
282 0     0     my $self = shift;
283              
284 0           my $dist_name;
285 0 0 0       if ($self->config && defined($self->config->{name})) {
286 0           my $conf = $self->config;
287 0 0         if ($conf->{name} =~ /::/) {
288 0           (my $better_name = $conf->{name}) =~ s/::/-/g;
289 0           Carp::croak(qq(You shouldn't set 'name="$conf->{name}"' in minil.toml. You need to set the value as 'name="$better_name"'.));
290             }
291 0           $dist_name = $conf->{name};
292             }
293 0 0         unless (defined $dist_name) {
294 0           infof("Detecting project name from directory name.\n");
295 0           $dist_name = $self->_detect_project_name_from_dir;
296             }
297 0 0         if ($dist_name eq '.') { Carp::confess("Heh? " . $self->dir); }
  0            
298              
299 0 0         unless ($dist_name) {
300 0           errorf("Cannot detect distribution name from minil.toml or directory name(cwd: %s, dir:%s)\n", Cwd::getcwd(), $self->dir);
301             }
302              
303 0           return $dist_name;
304             }
305              
306             sub _detect_project_name_from_dir {
307 0     0     my $self = shift;
308              
309 0           local $_ = basename($self->dir);
310 0           $_ =~ s!--!-!g;
311 0           $_ =~ s!\Ap5-!!;
312 0           return $_;
313             }
314              
315             sub _build_build_class {
316 0     0     my $self = shift;
317              
318 0           my $build_class;
319 0 0         if (my $conf = $self->config) {
320 0           $build_class = $conf->{build}{build_class};
321             }
322              
323 0 0         return $build_class if $build_class;
324              
325 0 0         return $self->use_xsutil ? 'Module::Build::XSUtil' : 'Module::Build';
326             }
327              
328             sub _build_main_module_path {
329 0     0     my $self = shift;
330              
331 0           my $dist_name = $self->dist_name;
332 0           my $source_path = $self->_detect_source_path($dist_name);
333 0 0 0       unless (defined($source_path) && -e $source_path) {
334 0   0       errorf("%s not found.\n", $source_path || "main module($dist_name)");
335             }
336              
337 0           infof("Retrieving meta data from %s.\n", $source_path);
338 0           return $source_path;
339             }
340              
341             sub _build_metadata {
342 0     0     my $self = shift;
343              
344 0           my $config = +{%{$self->config}};
  0            
345 0 0         if (my $license = delete $config->{license}) {
346 0           $config->{_license_name} = $license;
347             }
348              
349             # fill from main_module
350 0           my $metadata = Minilla::Metadata->new(
351             source => $self->main_module_path,
352             %$config,
353             );
354 0           infof("Name: %s\n", $metadata->name);
355 0           infof("Abstract: %s\n", Encode::encode(term_encoding(), $metadata->abstract));
356 0           infof("Version: %s\n", $metadata->version);
357              
358 0           return $metadata;
359             }
360              
361             sub _case_insensitive_match {
362 0     0     my $path = shift;
363 0           my @path = File::Spec->splitdir($path);
364 0           my $realpath = '.';
365 0           LOOP: for my $part (@path) {
366             my $d = DirHandle->new($realpath)
367 0 0         or do {
368             # warn "Cannot open dirhandle";
369 0           return;
370             };
371 0           while (defined($_ = $d->read)) {
372 0 0         if (uc($_) eq uc($part)) {
373 0           $realpath = catfile($realpath, $_);
374 0           next LOOP;
375             }
376             }
377              
378             # does not match
379             # warn "Does not match: $part in $realpath";
380 0           return undef;
381             }
382 0           return $realpath;
383             }
384              
385             sub format_tag {
386 0     0 0   my ($self, $version) = @_;
387 0 0         if (defined(my $format = $self->config->{tag_format})) {
388 0           (my $tag = $format) =~ s/%v/$version/;
389 0           $tag;
390             } else {
391 0           $version;
392             }
393             }
394              
395             sub _detect_source_path {
396 0     0     my ($self, $dir) = @_;
397              
398             # like cpan-outdated => lib/App/cpanminus.pm
399 0           my $pat2 = "App-" . do {
400 0           local $_ = $dir;
401 0           s!-!!;
402 0           $_;
403             };
404 0           for my $path ("App-$dir", $pat2, $dir) {
405 0           $path =~ s!::!/!g;
406 0           $path =~ s!-!/!g;
407 0           $path = "lib/${path}.pm";
408              
409 0 0         return $path if -f $path;
410              
411 0           $path = _case_insensitive_match($path);
412 0 0         return $path if defined($path);
413             }
414              
415 0           return undef;
416             }
417              
418             sub load_cpanfile {
419 0     0 0   my $self = shift;
420 0           Module::CPANfile->load(catfile($self->dir, 'cpanfile'));
421             }
422              
423             sub cpan_meta {
424 0     0 0   my ($self, $release_status) = @_;
425 0 0 0       $release_status ||= ($self->version =~ /_/ ? 'unstable' : 'stable');
426              
427 0           my $cpanfile = $self->load_cpanfile;
428 0           my $merged_prereqs = $cpanfile->prereqs->with_merged_prereqs(
429             CPAN::Meta::Prereqs->new($self->module_maker->prereqs($self))
430             );
431 0           $merged_prereqs = $merged_prereqs->with_merged_prereqs(
432             CPAN::Meta::Prereqs->new(Minilla::ReleaseTest->prereqs)
433             );
434 0 0         if ($self->metadata->perl_version) {
435 0           $merged_prereqs = $merged_prereqs->with_merged_prereqs(
436             CPAN::Meta::Prereqs->new(+{
437             runtime => {
438             requires => {
439             perl => $self->metadata->perl_version,
440             }
441             }
442             })
443             );
444             }
445 0           $merged_prereqs = $merged_prereqs->as_string_hash;
446              
447 0   0       my $dat = {
448             "meta-spec" => {
449             "version" => "2",
450             "url" => "http://search.cpan.org/perldoc?CPAN::Meta::Spec"
451             },
452             license => [ $self->license->meta2_name ],
453             abstract => $self->abstract,
454             dynamic_config => 0,
455             version => $self->version,
456             name => $self->dist_name,
457             prereqs => $merged_prereqs,
458             generated_by => "Minilla/$Minilla::VERSION",
459             release_status => $release_status || 'stable',
460             no_index => $self->no_index,
461             x_static_install => $self->static_install,
462             };
463 0 0         unless ($dat->{abstract}) {
464 0           errorf("Cannot retrieve 'abstract' from %s. You need to write POD in your main module.\n", $self->dir);
465             }
466 0 0         if ($self->authors) {
467 0           $dat->{author} = $self->authors;
468             } else {
469 0           errorf("Cannot determine 'author' from %s\n", $self->dir);
470             }
471 0 0 0       if ($self->contributors && @{$self->contributors} > 0) {
  0            
472 0           $dat->{x_contributors} = $self->contributors;
473             }
474 0 0         if (my $authority = $self->config->{authority}) {
475 0           $dat->{x_authority} = $authority;
476             }
477 0 0         if (my $metadata = $self->config->{Metadata}) {
478 0           $dat->{$_} = $metadata->{$_} for keys %$metadata;
479             }
480              
481             # fill 'provides' section
482 0 0         if ($release_status ne 'unstable') {
483 0           my $provides = Module::Metadata->provides(
484             dir => File::Spec->catdir($self->dir, 'lib'),
485             version => 2
486             );
487 0 0         unless (%$provides) {
488 0           errorf("%s does not provides any package. Abort.\n", $self->dir);
489             }
490 0           $dat->{provides} = $provides;
491             }
492              
493             # fill repository information
494 0           my $git_info = $self->extract_git_info;
495 0 0         if ($git_info->{bugtracker}) {
496 0           $dat->{resources}->{bugtracker} = $git_info->{bugtracker};
497             }
498 0 0         if ($git_info->{repository}) {
499 0           $dat->{resources}->{repository} = $git_info->{repository};
500             }
501 0 0         if ($git_info->{homepage}) {
502 0           $dat->{resources}->{homepage} = $git_info->{homepage};
503             }
504              
505             # optional features
506 0 0         if ($cpanfile->features) {
507 0           my $optional_features = {};
508 0           foreach my $feature ($cpanfile->features) {
509 0           $optional_features->{$feature->identifier} = {
510             description => $feature->description,
511             prereqs => $feature->prereqs->as_string_hash,
512             }
513             }
514 0           $dat->{optional_features} = $optional_features;
515             }
516              
517 0           my $meta = CPAN::Meta->new($dat);
518 0           return $meta;
519             }
520              
521             sub extract_git_info {
522 0     0 0   my $self = shift;
523              
524 0           my $guard = pushd($self->dir);
525              
526 0           my $bugtracker;
527             my $repository;
528 0           my $homepage;
529 0 0         if ( my $registered_url = `git config --get remote.origin.url` ) {
530 0           $registered_url =~ s/\n//g;
531             # XXX Make it public clone URL, but this only works with github
532 0 0 0       if ($registered_url !~ m{^file://} && $registered_url =~ /(?:github|gitlab)\.com/) {
533 0           my ($git_service, $user, $repo) = $registered_url =~ m{
534             (github\.com|gitlab\.com)
535             (?:(?::[0-9]+)?/|:)([^/]+)
536             /
537             (.+?)(?:\.git)?
538             $
539             }ix;
540 0           my $git_url = "https://$git_service/$user/$repo.git";
541 0           my $http_url = "https://$git_service/$user/$repo";
542 0 0         unless ($self->config->{no_github_issues}) {
543 0           $bugtracker = +{
544             web => "$http_url/issues",
545             };
546             }
547             $repository = +{
548 0           type => "git",
549             url => $git_url,
550             web => $http_url,
551             };
552 0   0       $homepage = $self->config->{homepage} || $http_url;
553             } else {
554 0 0         if ($registered_url !~ m{^(?:https?|ssh|git)://}) {
555             # We can't do much more than this, but we need to fix
556             # user@host:path/to/repo.git to git://$host/path/to/repo.git in
557             # order to work with CPAN::Meta
558 0           $registered_url =~ s{
559             \A
560             [^@]+ # user name, which we toss away
561             @
562             ([^:]+) # anything other than a ":"
563             :
564             (.+) # anything, which is the repository
565             \Z
566             }{git://$1/$2}gx;
567             }
568              
569             # normal repository
570 0 0         if ($registered_url !~ m{^file://}) {
571 0           $repository = +{
572             type => "git",
573             url => $registered_url,
574             };
575             }
576             }
577             }
578              
579             return +{
580 0           bugtracker => $bugtracker,
581             repository => $repository,
582             homepage => $homepage,
583             }
584             }
585              
586             sub readme_from {
587 0     0 0   my $self = shift;
588 0 0         $self->config->{readme_from} || $self->main_module_path;
589             }
590              
591             sub regenerate_files {
592 0     0 0   my $self = shift;
593              
594 0           $self->regenerate_meta_json();
595 0           $self->regenerate_readme_md();
596 0           $self->module_maker->generate($self);
597 0 0         if (Cwd::getcwd() ne $self->dir) {
598 0           my $guard = pushd($self->dir);
599 0           $self->module_maker->generate($self);
600             }
601             }
602              
603             sub regenerate_meta_json {
604 0     0 0   my $self = shift;
605              
606 0           my $meta = $self->cpan_meta('unstable');
607 0           $meta->save(File::Spec->catfile($self->dir, 'META.json'), {
608             version => '2.0'
609             });
610             }
611              
612             sub generate_minil_toml {
613 0     0 0   my ($self, $profile) = @_;
614              
615 0           my $fname = File::Spec->catfile($self->dir, 'minil.toml');
616 0           my $project_name = $self->_detect_project_name_from_dir;
617 0           my $content = join("\n",
618             qq{name = "$project_name"},
619             qq{badges = ["github-actions/test.yml"]},
620             );
621              
622 0 0         if ($profile eq 'ModuleBuild') {
    0          
623 0           $content .= qq{\nmodule_maker="ModuleBuild"\n};
624             } elsif ($profile eq 'ExtUtilsMakeMaker') {
625 0           $content .= qq{\nmodule_maker="ExtUtilsMakeMaker"\n};
626             } else {
627 0           $content .= qq{\nmodule_maker="ModuleBuildTiny"\n};
628             }
629 0           $content .= qq{static_install = "auto"\n};
630              
631 0           spew_raw($fname, $content . "\n");
632             }
633              
634             sub regenerate_readme_md {
635 0     0 0   my $self = shift;
636              
637 0   0       my $markdown_maker = $self->config->{markdown_maker} || 'Pod::Markdown';
638 0           require_module($markdown_maker);
639 0 0         if ($markdown_maker eq 'Pod::Markdown') {
640 0           $markdown_maker->VERSION('1.322');
641             }
642              
643 0 0         my $parser = $markdown_maker->new( %{ $self->config->{markdown_maker_opts} || {} } );;
  0            
644 0 0         if (not $parser->isa('Pod::Markdown')) {
645 0           errorf("'markdown_maker' config key must be a subclass of Pod::Markdown\n");
646             }
647 0           $parser->parse_from_file($self->readme_from);
648              
649 0           my $fname = File::Spec->catfile($self->dir, 'README.md');
650 0           my $markdown = $parser->as_markdown;
651              
652 0 0 0       if (ref $self->badges eq 'ARRAY' && scalar @{$self->badges} > 0) {
  0            
653 0           my $user_name;
654             my $repository_name;
655              
656 0           my $git_info = $self->extract_git_info;
657 0 0         if (my $web_url = $git_info->{repository}->{web}) {
658 0           ($user_name, $repository_name) = $web_url =~ m!https://.+/(.+)/(.+)!;
659             }
660 0           my $branch = $self->release_branch;
661 0           my @badges;
662 0 0 0       if ($user_name && $repository_name) {
663 0           for my $badge (@{$self->badges}) {
  0            
664 0           my $uri = URI->new( $badge );
665 0           my $service_name = $uri->path;
666 0 0         if ($service_name =~ /^travis(?:-ci\.(?:org|com))?$/) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
667 0           my $build_uri = $uri->clone;
668 0           $build_uri->scheme('https');
669 0           $build_uri->path("$user_name/$repository_name");
670 0           $build_uri->query_form({});
671 0           my $image_uri = $uri->clone;
672 0           $image_uri->scheme('https');
673 0           $image_uri->path("$user_name/$repository_name.svg");
674 0           my %image_uri_qs = $image_uri->query_form;
675 0 0         $image_uri_qs{branch} = $branch if !defined($image_uri_qs{branch});
676 0 0         if ($service_name =~ /^travis(?:-ci\.(?:org|com))$/) {
    0          
677 0           $_->host($service_name) foreach ($build_uri, $image_uri);
678             } elsif (!defined($image_uri_qs{token})) {
679 0           $_->host("travis-ci.org") foreach ($build_uri, $image_uri);
680             } else {
681 0           $_->host("travis-ci.com") foreach ($build_uri, $image_uri);
682             }
683             # Sort the query params so that the end URL is
684             # deterministic and easier to test.
685 0           $image_uri->query_form( map { ( $_, $image_uri_qs{$_} ) } sort keys %image_uri_qs );
  0            
686 0           push @badges, "[![Build Status]($image_uri)]($build_uri)";
687             } elsif ($service_name eq 'appveyor') {
688 0           ( my $appveyor_repository_name = $repository_name ) =~ s/\./-/g;
689 0           push @badges, "[![Build Status](https://img.shields.io/appveyor/ci/$user_name/$appveyor_repository_name/$branch.svg?logo=appveyor)](https://ci.appveyor.com/project/$user_name/$appveyor_repository_name/branch/$branch)";
690             } elsif ($service_name eq 'coveralls') {
691 0           push @badges, "[![Coverage Status](https://img.shields.io/coveralls/$user_name/$repository_name/$branch.svg?style=flat)](https://coveralls.io/r/$user_name/$repository_name?branch=$branch)"
692             } elsif ($service_name eq 'codecov') {
693 0           push @badges, "[![Coverage Status](http://codecov.io/github/$user_name/$repository_name/coverage.svg?branch=$branch)](https://codecov.io/github/$user_name/$repository_name?branch=$branch)";
694             } elsif ($service_name eq 'gitter') {
695 0           push @badges, "[![Gitter chat](https://badges.gitter.im/$user_name/$repository_name.png)](https://gitter.im/$user_name/$repository_name)";
696             } elsif ($service_name eq 'circleci') {
697 0           push @badges, "[![Build Status](https://circleci.com/gh/$user_name/$repository_name.svg)](https://circleci.com/gh/$user_name/$repository_name)";
698             } elsif ($service_name eq 'metacpan') {
699 0   0       my $module_name = $self->config->{name} || $repository_name;
700 0           push @badges, "[![MetaCPAN Release](https://badge.fury.io/pl/$module_name.svg)](https://metacpan.org/release/$module_name)";
701             } elsif ($service_name eq 'kritika') {
702 0           my $build_uri = $uri->clone;
703 0           $build_uri->scheme('https');
704 0           $build_uri->host('kritika.io');
705 0           $build_uri->path("/users/$user_name/repos/$user_name+$repository_name");
706 0           $build_uri->query_form({});
707 0           my $image_uri = $uri->clone;
708 0           $image_uri->scheme('https');
709 0           $image_uri->host('kritika.io');
710 0           $image_uri->path("/users/$user_name/repos/$user_name+$repository_name/heads/$branch/status.svg");
711 0           push @badges, "[![Kritika Status]($image_uri)]($build_uri)";
712             } elsif ($service_name =~ m!^github-actions(?:/(.+))?$!) {
713             # ref. https://docs.github.com/en/actions/how-tos/monitor-workflows/add-a-status-badge
714 0   0       my $workflow_file = $1 || 'test.yml';
715 0 0         if ($workflow_file !~ /\.(?:yml|yaml)$/) {
716 0           $workflow_file .= '.yml';
717             }
718 0           my $workflow_name = $workflow_file;
719 0           $workflow_name =~ s/\.ya?ml$//;
720 0           push @badges, "[![Actions Status](https://github.com/$user_name/$repository_name/actions/workflows/$workflow_file/badge.svg?branch=$branch)](https://github.com/$user_name/$repository_name/actions?workflow=$workflow_name)";
721             } elsif ($service_name eq 'gitlab-pipeline') {
722 0           push @badges, "[![Gitlab pipeline](https://gitlab.com/$user_name/$repository_name/badges/$branch/pipeline.svg)](https://gitlab.com/$user_name/$repository_name/-/commits/$branch)";
723             } elsif ($service_name eq 'gitlab-coverage') {
724 0           push @badges, "[![Gitlab coverage](https://gitlab.com/$user_name/$repository_name/badges/$branch/coverage.svg)](https://gitlab.com/$user_name/$repository_name/-/commits/$branch)";
725             }
726             }
727             }
728              
729 0           $markdown = "\n" . $markdown;
730 0           $markdown = join(' ', @badges) . $markdown
731             }
732              
733 0           spew_utf8($fname, $markdown);
734             }
735              
736             sub verify_prereqs {
737 0     0 0   my ($self) = @_;
738              
739 0 0         if ($Minilla::AUTO_INSTALL) {
740 0           system('cpanm', '--quiet', '--installdeps', '--with-develop', '.');
741             }
742             }
743              
744             sub _build_contributors {
745 0     0     my $self = shift;
746              
747 0 0         return [] unless (`git show-ref --head`);
748              
749             my $normalize = sub {
750 0     0     local $_ = shift;
751 0 0         if (/<([^>]+)>/) {
752 0           $1;
753             } else {
754 0           $_;
755             }
756 0           };
757 0           my @lines = do {
758 0           my %uniq;
759 0           reverse grep { !$uniq{$normalize->($_)}++ } split /\n/, `git log --format="%aN <%aE>"`
  0            
760             };
761 0           my %is_author = map { $normalize->($_) => 1 } @{$self->authors};
  0            
  0            
762 0           @lines = map { decode_utf8($_) } @lines;
  0            
763 0           @lines = grep { !$is_author{$normalize->($_)} } @lines;
  0            
764 0           @lines = grep { $_ ne 'Your Name ' } @lines;
  0            
765 0           @lines = grep { ! /^\(no author\) <\(no author\)\@[\d\w\-]+>$/ } @lines;
  0            
766 0           [sort @lines];
767             }
768              
769             sub _build_work_dir {
770 0     0     my $self = shift;
771 0           Minilla::WorkDir->new(
772             project => $self,
773             );
774             }
775              
776             sub _build_files {
777 0     0     my $self = shift;
778 0           my $conf = $self->config->{'FileGatherer'};
779             my @files = Minilla::FileGatherer->new(
780             exclude_match => $conf->{exclude_match},
781 0 0         exists $conf->{include_dotfiles} ? (include_dotfiles => $conf->{include_dotfiles}) : (),
782             )->gather_files(
783             $self->dir
784             );
785 0           \@files;
786             }
787              
788             sub _build_release_branch {
789 0     0     my $self = shift;
790 0 0         if (my $br = $self->config->{release}->{branch}) {
791 0           return $br;
792             }
793 0           my $show = `git remote show origin`;
794 0           my ($br) = $show =~ /^\s*HEAD branch:\s(\S+)$/m;
795             # For backward compatibility, fallback to 'master' just in case,
796             # but it's unlikely to be used in practice.
797 0   0       return $br || 'master';
798             }
799              
800             sub perl_files {
801 0     0 0   my $self = shift;
802 0           my @files = @{$self->files};
  0            
803             grep {
804 0 0         $_ =~ /\.(?:pm|pl|t)$/i || slurp_raw($_) =~ m{ ^ \#\! .* perl }ix
  0            
805             } @files;
806             }
807              
808 0 0   0 0   sub PL_files { shift->config->{PL_files} || +{} }
809              
810             sub requires_external_bin {
811 0     0 0   my $self = shift;
812 0           return $self->config->{requires_external_bin};
813             }
814              
815             # @return true if the project is valid, false otherwise.
816             sub validate {
817 0     0 0   my $self = shift;
818 0           my $module_maker = $self->module_maker;
819 0 0         if ($module_maker->can('validate')) {
820 0           return $module_maker->validate();
821             } else {
822 0           return 1;
823             }
824             }
825              
826             1;