File Coverage

blib/lib/Minilla/Project.pm
Criterion Covered Total %
statement 78 436 17.8
branch 0 194 0.0
condition 0 53 0.0
subroutine 26 71 36.6
pod 0 30 0.0
total 104 784 13.2


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