File Coverage

blib/lib/App/ModuleBuildTiny.pm
Criterion Covered Total %
statement 53 185 28.6
branch 0 72 0.0
condition 0 34 0.0
subroutine 18 39 46.1
pod 1 21 4.7
total 72 351 20.5


line stmt bran cond sub pod time code
1             package App::ModuleBuildTiny;
2              
3 1     1   191717 use 5.014;
  1         3  
4 1     1   4 use warnings;
  1         2  
  1         76  
5             our $VERSION = '0.051';
6              
7 1     1   4 use Exporter 5.57 'import';
  1         12  
  1         37  
8             our @EXPORT = qw/modulebuildtiny/;
9              
10 1     1   4 use Config;
  1         1  
  1         46  
11 1     1   4 use CPAN::Meta;
  1         1  
  1         19  
12 1     1   445 use Data::Section::Simple 'get_data_section';
  1         589  
  1         62  
13 1     1   5 use Encode qw/encode_utf8 decode_utf8/;
  1         2  
  1         68  
14 1     1   573 use ExtUtils::Manifest 1.75 qw/manifind maniskip maniread/;
  1         10709  
  1         135  
15 1     1   11 use File::Basename qw/dirname/;
  1         4  
  1         72  
16 1     1   8 use File::Path qw/mkpath/;
  1         2  
  1         69  
17 1     1   1431 use File::Slurper qw/write_text write_binary read_binary/;
  1         9467  
  1         112  
18 1     1   826 use File::Spec::Functions qw/catfile rel2abs/;
  1         1728  
  1         93  
19 1     1   865 use Getopt::Long 2.36 'GetOptionsFromArray';
  1         14391  
  1         22  
20 1     1   1132 use JSON::MaybeXS qw/decode_json/;
  1         10336  
  1         124  
21 1     1   710 use Module::Runtime 'require_module';
  1         2938  
  1         5  
22 1     1   714 use Text::Template;
  1         4979  
  1         70  
23              
24 1     1   650 use App::ModuleBuildTiny::Dist;
  1         7  
  1         61  
25              
26 1     1   7 use Env qw/$AUTHOR_TESTING $RELEASE_TESTING $AUTOMATED_TESTING $EXTENDED_TESTING $NONINTERACTIVE_TESTING $SHELL $HOME $USERPROFILE/;
  1         2  
  1         8  
27              
28             Getopt::Long::Configure(qw/require_order gnu_compat bundling/);
29              
30             sub prompt {
31 0     0 0   my($mess, $def) = @_;
32              
33 0 0         my $dispdef = defined $def ? " [$def]" : "";
34              
35 0           local $|=1;
36 0           local $\;
37 0           print "$mess$dispdef ";
38              
39 0   0       my $ans = // '';
40 0           chomp $ans;
41 0 0 0       return $ans ne '' ? decode_utf8($ans) : $def // '';
42             }
43              
44             sub prompt_yn {
45 0     0 0   my ($description, $default) = @_;
46 0           my $result;
47 0   0       do {
48 0 0         $result = prompt("$description [y/n]", $default ? 'y' : 'n');
49             } while (length $result and $result !~ /^(y|n|-)/i);
50 0           return lc(substr $result, 0 , 1) eq 'y';
51             }
52              
53             sub create_license_for {
54 0     0 0   my ($license_name, $author) = @_;
55 0           my $module = "Software::License::$license_name";
56 0           require_module($module);
57 0           return $module->new({ holder => $author });
58             }
59              
60             sub fill_in {
61 0     0 0   my ($template, $hash) = @_;
62 0           return Text::Template->new(TYPE => 'STRING', SOURCE => $template)->fill_in(HASH => $hash);
63             }
64              
65             sub write_module {
66 0     0 0   my %opts = @_;
67 0           my $template = get_data_section('Module.pm') =~ s/ ^ % (\w+) /=$1/gxmsr;
68 0           my $filename = catfile('lib', split /::/, $opts{module_name}) . '.pm';
69 0           my $content = fill_in($template, \%opts);
70 0           mkpath(dirname($filename));
71 0           write_text($filename, $content);
72 0           return $filename;
73             }
74              
75             sub write_changes {
76 0     0 0   my %opts = @_;
77 0           my $template = get_data_section('Changes');
78 0           my $content = fill_in($template, \%opts);
79 0           write_text('Changes', $content);
80             }
81              
82             sub write_maniskip {
83 0     0 0   my $distname = shift;
84 0           write_text('MANIFEST.SKIP', "#!include_default\n$distname-.*\nREADME.pod\n");
85             }
86              
87             sub write_readme {
88 0     0 0   my %opts = @_;
89 0           my $template = get_data_section('README');
90 0           write_text('README', fill_in($template, \%opts));
91             }
92              
93             sub read_json {
94 0     0 0   my $filename = shift;
95 0 0         -f $filename or return;
96 0           return decode_json(read_binary($filename));
97             }
98              
99             sub write_json {
100 0     0 0   my ($filename, $content) = @_;
101 0           my $dirname = dirname($filename);
102 0 0         mkdir $dirname if not -d $dirname;
103 0           my $json = JSON::MaybeXS->new->utf8->pretty->canonical->encode($content);
104 0           return write_binary($filename, $json);
105             }
106              
107             sub bump_versions {
108 0     0 0   my (%opts) = @_;
109 0           require App::RewriteVersion;
110 0           my $app = App::RewriteVersion->new(%opts);
111 0           my $trial = delete $opts{trial};
112 0 0         my $new_version = defined $opts{version} ? delete $opts{version} : $app->bump_version($app->current_version);
113 0           $app->rewrite_versions($new_version, is_trial => $trial);
114             }
115              
116             sub insert_options {
117 0     0 0   my ($opts, $config) = @_;
118 0           $opts->{add_repository} = !!$config->{auto_repo};
119 0           $opts->{add_bugtracker} = !!$config->{auto_tracker};
120             }
121              
122             sub regenerate {
123 0     0 0   my ($files, $config, %opts) = @_;
124 0           my %files = map { $_ => 1 } @{$files};
  0            
  0            
125 0           my @dirty = @{$files};
  0            
126              
127 0 0         if ($opts{bump}) {
128 0           bump_versions(%opts);
129 0           $files{'Changes'}++;
130 0           push @dirty, 'Changes';
131             }
132              
133 0           insert_options(\%opts, $config);
134 0           my $dist = App::ModuleBuildTiny::Dist->new(%opts, regenerate => \%files);
135 0           my @generated = grep { $files{$_} } $dist->files;
  0            
136 0           for my $filename (@generated) {
137 0 0         say "Updating $filename" if $opts{verbose};
138 0 0         write_binary($filename, $dist->get_file($filename)) if !$opts{dry_run};
139             }
140              
141 0 0         if ($opts{commit}) {
142 0           require Git::Wrapper;
143 0           my $git = Git::Wrapper->new('.');
144 0 0         if ($opts{bump}) {
145 0           push @dirty, 'lib';
146 0 0         push @dirty, 'script' if -d 'script';
147             }
148 0           my $allowed = join '|', map qr{^\Q$_\E$}, @dirty;
149 0           my @modified = grep /$allowed/, $git->ls_files({ modified => 1 });
150              
151 0 0         if (@modified) {
152 0           my @changes = $dist->get_changes;
153 0           my $version = 'v' . $dist->version;
154 0   0       my $message = $opts{message} || ($opts{bump} ? join '', $version, "\n\n", @changes : 'Regenerate');
155 0           $git->commit({ m => $message }, @dirty);
156             } else {
157 0           say "No modifications to commit";
158             }
159             }
160             }
161              
162             my %prompt_for = (
163             open => \&prompt,
164             yn => \&prompt_yn,
165             );
166              
167             my @config_items = (
168             [ 'author' , 'What is the author\'s name?', 'open' ],
169             [ 'email' , 'What is the author\'s email?', 'open', ],
170             [ 'license' , 'What license do you want to use?', 'open', 'Perl_5' ],
171              
172             [ 'write_buildpl' , 'Do you want to write your Build.PL file to your filesystem?', 'yn', !!1],
173             [ 'write_meta' , 'Do you want to write your meta files to your filesystem?', 'yn', !!1],
174             [ 'write_manifest', 'Do you want to write your manifest files to your filesystem?', 'yn', !!1],
175             [ 'write_license' , 'Do you want to write your LICENSE file to your filesystem?', 'yn', !!1],
176             [ 'write_readme' , 'Do you want to write your README file to your filesystem?', 'yn', !!1],
177              
178             [ 'auto_git' , 'Do you want mbtiny to automatically handle git for you?', 'yn', !!1 ],
179             [ 'auto_bump' , 'Do you want mbtiny to automatically bump on regenerate for you?', 'yn', !!1 ],
180             [ 'auto_scan' , 'Do you want mbtiny to automatically scan dependencies for you?', 'yn', !!1 ],
181             [ 'auto_repo' , 'Do you want mbtiny to automatically add a repository link to the metadata', 'yn', !!1 ],
182             [ 'auto_tracker' , 'Do you want mbtiny to automatically add a bugtracker link to the metadata', 'yn', !!1 ],
183             );
184              
185             my %fallback_config = (
186             'write_buildpl' => 'write_build',
187             'write_meta' => 'write_build',
188             'write_manifest' => 'write_build',
189             );
190              
191             my @delete_config = qw/write_build/;
192              
193             sub ask {
194 0     0 0   my ($config, $item, $local_default) = @_;
195 0           my ($key, $description, $type, $global_default) = @{$item};
  0            
196 0   0       my $value = $prompt_for{$type}->($description, $local_default // $global_default);
197              
198 0 0         if ($value ne '-') {
199 0 0         $config->{$key} = $type eq 'open' ? $value : $value ? JSON::MaybeXS::true : JSON::MaybeXS::false;
    0          
200             }
201             else {
202 0           delete $config->{$key};
203             }
204             }
205              
206             sub show_item {
207 0     0 0   my ($config, $key, $type) = @_;
208 0 0         return defined $config->{$key} ? $type eq 'open' ? $config->{$key} : $config->{$key} ? 'true' : 'false' : '(undefined)';
    0          
    0          
209             }
210              
211             sub get_settings_file {
212 0 0   0 0   local $HOME = $USERPROFILE if $^O eq 'MSWin32';
213 0           return catfile(glob('~'), qw/.mbtiny conf/);
214             }
215              
216             my %default_settings = (
217             auto_bump => 1,
218             auto_git => 1,
219             auto_scan => 1,
220              
221             write_build => 1,
222             write_license => 1,
223             write_readme => 1,
224             );
225              
226             sub get_settings {
227 0   0 0 0   my $default = shift // {};
228 0           my $settings_file = get_settings_file;
229 0 0         my $settings = -f $settings_file ? read_json($settings_file) : $default;
230 0           for my $item (@config_items) {
231 0           my ($key, $description, $type, $default) = @{$item};
  0            
232 0 0         next unless exists $settings->{$key};
233 0 0         next unless $type eq 'yn';
234 0           $settings->{$key} = !!$settings->{$key};
235             }
236 0           return $settings;
237             }
238              
239             my $config_file = 'dist.json';
240              
241             sub get_config {
242 0 0   0 0   my $config = -f $config_file ? read_json($config_file) : {};
243 0           for my $item (@config_items) {
244 0           my ($key, $description, $type, $default) = @{$item};
  0            
245 0 0         next unless exists $config->{$key};
246 0 0         next unless $type eq 'yn';
247 0           $config->{$key} = !!$config->{$key};
248             }
249 0           return $config;
250             }
251              
252             sub extra_tests {
253 0     0 0   my @dirs;
254 0 0         if ($AUTHOR_TESTING) {
255 0           push @dirs, 'xt/author';
256 0           push @dirs, glob 'xt/*.t';
257             }
258 0 0         push @dirs, 'xt/release' if $RELEASE_TESTING;
259 0 0         push @dirs, 'xt/extended' if $EXTENDED_TESTING;
260 0           return grep -e, @dirs;
261             }
262              
263             my @meta_files = qw/META.json META.yml/;
264              
265             sub regenerate_files {
266 0     0 0   my $config = shift;
267 0           my @result;
268 0 0 0       push @result, 'Build.PL' if $config->{write_buildpl} // $config->{write_build} // 1;
      0        
269 0 0 0       push @result, @meta_files if $config->{write_meta} // $config->{write_build} // 1;
      0        
270 0 0 0       push @result, 'MANIFEST' if $config->{write_manifest} // $config->{write_build} // 1;
      0        
271 0 0 0       push @result, 'LICENSE' if $config->{write_license} // 1;
272 0 0 0       push @result, 'README' if $config->{write_readme} // 1;
273 0           return @result;
274             }
275              
276             my %boolean = (
277             true => !!1,
278             false => !!0,
279             );
280              
281             my %actions = (
282             dist => sub {
283             my @arguments = @_;
284             GetOptionsFromArray(\@arguments, \my %opts, qw/trial verbose!/) or return 2;
285             my $dist = App::ModuleBuildTiny::Dist->new(%opts);
286             insert_options(\%opts, get_config);
287             die "Trial mismatch" if $opts{trial} && $dist->release_status ne 'testing';
288             $dist->preflight_check(%opts);
289             my $filename = $dist->archivename;
290             printf "tar czf %s %s\n", $filename, join ' ', $dist->files if $opts{verbose};
291             $dist->write_tarball($filename);
292             return 0;
293             },
294             distdir => sub {
295             my @arguments = @_;
296             GetOptionsFromArray(\@arguments, \my %opts, qw/trial verbose!/) or return 2;
297             insert_options(\%opts, get_config);
298             my $dist = App::ModuleBuildTiny::Dist->new(%opts);
299             $dist->write_dir($dist->fullname, $opts{verbose});
300             return 0;
301             },
302             test => sub {
303             my @arguments = @_;
304             $AUTHOR_TESTING = 1;
305             GetOptionsFromArray(\@arguments, 'release!' => \$RELEASE_TESTING, 'author!' => \$AUTHOR_TESTING, 'automated!' => \$AUTOMATED_TESTING,
306             'extended!' => \$EXTENDED_TESTING, 'non-interactive!' => \$NONINTERACTIVE_TESTING, 'jobs|j=i' => \my $jobs, 'inc|I=s@' => \my @inc)
307             or return 2;
308             insert_options(\my %opts, get_config);
309             my $dist = App::ModuleBuildTiny::Dist->new(%opts);
310             my @args;
311             push @args, '-j', $jobs if defined $jobs;
312             push @args, map {; '-I', rel2abs($_) } @inc;
313             push @args, 't' if -e 't';
314             push @args, extra_tests();
315             return $dist->run(commands => [ [ 'prove', '-br', @args ] ], build => 1, verbose => 1);
316             },
317             upload => sub {
318             my @arguments = @_;
319             my $config = get_config;
320             my %opts = $config->{auto_git} ? (tag => 1, push => '') : ();
321             GetOptionsFromArray(\@arguments, \%opts, qw/trial config=s silent tag! push:s nopush|no-push/) or return 2;
322             insert_options(\%opts, get_config);
323              
324             my $dist = App::ModuleBuildTiny::Dist->new;
325             $dist->preflight_check(%opts);
326             local ($AUTHOR_TESTING, $RELEASE_TESTING) = (1, 1);
327             my @commands = ([ './Build', 'test' ]);
328             my @extra_tests = extra_tests;
329             push @commands, [ 'prove', '-br', @extra_tests ] if @extra_tests;
330             $dist->run(commands => \@commands, build => 1, verbose => !$opts{silent}) or return 1;
331              
332             my $sure = prompt_yn('Do you want to continue the release process?', !!0);
333             if ($sure) {
334             my $file = $dist->write_tarball($dist->archivename);
335             require CPAN::Upload::Tiny;
336             CPAN::Upload::Tiny->VERSION('0.009');
337             my $uploader = CPAN::Upload::Tiny->new_from_config_or_stdin($opts{config});
338             $uploader->upload_file($file);
339             print "Successfully uploaded $file\n" if not $opts{silent};
340              
341             if ($opts{tag}) {
342             require Git::Wrapper;
343             my $git = Git::Wrapper->new('.');
344             my $version = 'v' . $dist->version;
345             $git->tag('-m' => $version, $version);
346             }
347              
348             if (defined $opts{push} and not $opts{nopush}) {
349             require Git::Wrapper;
350             my $git = Git::Wrapper->new('.');
351              
352             my @remote = length $opts{push} ? $opts{push} : ();
353             $git->push(@remote);
354             $git->push({ tags => 1 }, @remote) if $opts{tag};
355             }
356             }
357             return 0;
358             },
359             run => sub {
360             my @arguments = @_;
361             die "No arguments given to run\n" if not @arguments;
362             GetOptionsFromArray(\@arguments, 'build!' => \(my $build = 1), 'allow_failure|allow-failure!' => \my $allow_failure) or return 2;
363             insert_options(\my %opts, get_config);
364             my $dist = App::ModuleBuildTiny::Dist->new(%opts);
365             return $dist->run(commands => [ [ $SHELL ] ], build => $build, verbose => 0, allow_failure => $allow_failure);
366             },
367             shell => sub {
368             my @arguments = @_;
369             GetOptionsFromArray(\@arguments, 'build!' => \my $build, 'allow_failure|allow-failure!' => \my $allow_failure) or return 2;
370             insert_options(\my %opts, get_config);
371             my $dist = App::ModuleBuildTiny::Dist->new(%opts);
372             return $dist->run(commands => [ [ $SHELL ] ], build => $build, verbose => 0, allow_failure => $allow_failure);
373             },
374             listdeps => sub {
375             my @arguments = @_;
376             GetOptionsFromArray(\@arguments, \my %opts, qw/json only_missing|only-missing|missing omit_core|omit-core=s author versions/) or return 2;
377             insert_options(\%opts, get_config);
378             my $dist = App::ModuleBuildTiny::Dist->new(%opts);
379              
380             require CPAN::Meta::Prereqs::Filter;
381             my $prereqs = CPAN::Meta::Prereqs::Filter::filter_prereqs($dist->meta->effective_prereqs, %opts);
382              
383             if (!$opts{json}) {
384             my @phases = qw/build test configure runtime/;
385             push @phases, 'develop' if $opts{author};
386              
387             my $reqs = $prereqs->merged_requirements(\@phases);
388             $reqs->clear_requirement('perl');
389              
390             my @modules = sort { lc $a cmp lc $b } $reqs->required_modules;
391             if ($opts{versions}) {
392             say "$_ = ", $reqs->requirements_for_module($_) for @modules;
393             }
394             else {
395             say for @modules;
396             }
397             }
398             else {
399             print JSON::MaybeXS->new->ascii->canonical->pretty->encode($prereqs->as_string_hash);
400             }
401             return 0;
402             },
403             regenerate => sub {
404             my @arguments = @_;
405             my $config = get_config;
406             my %opts;
407             GetOptionsFromArray(\@arguments, \%opts, qw/trial bump! version=s verbose dry_run|dry-run commit! scan! message=s/) or return 2;
408             my @files = @arguments ? @arguments : regenerate_files($config);
409             if (!@arguments) {
410             $opts{bump} //= $config->{auto_bump};
411             $opts{commit} //= $config->{auto_git};
412             $opts{scan} //= $config->{auto_scan};
413             }
414              
415             regenerate(\@files, $config, %opts);
416              
417             return 0;
418             },
419             scan => sub {
420             my @arguments = @_;
421             my %opts = (sanitize => 1);
422             GetOptionsFromArray(\@arguments, \%opts, qw/omit_core|omit-core=s sanitize! omit=s@/) or return 2;
423             insert_options(\%opts, get_config);
424             my $dist = App::ModuleBuildTiny::Dist->new(%opts, regenerate => { 'META.json' => 1 });
425             my $prereqs = $dist->scan_prereqs(%opts);
426             write_json('prereqs.json', $prereqs->as_string_hash);
427             return 0;
428             },
429             setup => sub {
430             my @arguments = @_;
431             my $config_file = get_settings_file();
432             my $config = -f $config_file ? read_json($config_file) : {};
433              
434             my $mode = @arguments ? shift @arguments : 'upgrade';
435              
436             if ($mode eq 'upgrade') {
437             for my $item (@config_items) {
438             next if defined $config->{ $item->[0] };
439             my $default = $config->{ $fallback_config{ $item->[0] } // '' };
440             ask($config, $item, $default);
441             }
442             # delete $config->{$_} for @delete_config;
443             write_json($config_file, $config);
444             }
445             elsif ($mode eq 'minimal') {
446             for my $item (@config_items) {
447             next if defined $config->{ $item->[0] };
448             if (defined $item->[3]) {
449             $config->{ $item->[0] } = $item->[3];
450             } else {
451             ask($config, $item);
452             }
453             }
454             delete $config->{$_} for @delete_config;
455             write_json($config_file, $config);
456             }
457             elsif ($mode eq 'all') {
458             for my $item (@config_items) {
459             my $default = $config->{ $item->[0] } // $config->{ $fallback_config{ $item->[0] } // '' };
460             ask($config, $item, $default);
461             }
462             delete $config->{$_} for @delete_config;
463             write_json($config_file, $config);
464             }
465             elsif ($mode eq 'get') {
466             my ($key, $value) = @arguments;
467             my ($item) = grep { $_->[0] eq $key } @config_items;
468             die "No such known key $key" if not $item;
469             my (undef, $description, $type, $default) = @{$item};
470             say show_item($config, $key, $type);
471             }
472             elsif ($mode eq 'set') {
473             my ($key, $value) = @arguments;
474             my $item = grep { $_->[0] eq lc $key } @config_items;
475             die "No such known key $key" if not $item;
476             if ($item->[2] eq 'yn') {
477             $config->{$key} = $boolean{$value} // die "Unknown boolean value '$value'\n";
478             } else {
479             $config->{$key} = $value;
480             }
481             write_json($config_file, $config);
482             }
483             elsif ($mode eq 'list') {
484             for my $item (@config_items) {
485             my ($key, $description, $type, $default) = @{$item};
486             say "$key: " . show_item($config, $key, $type);
487             }
488             }
489             elsif ($mode eq 'reset') {
490             return not unlink $config_file;
491             }
492             return 0;
493             },
494             config => sub {
495             my @arguments = @_;
496             my $settings = get_settings;
497             my $config = get_config;
498              
499             my $mode = @arguments ? shift @arguments : 'upgrade';
500              
501             my @items = grep { $_->[2] ne 'open' } @config_items;
502             if ($mode eq 'upgrade') {
503             for my $item (@items) {
504             next if defined $config->{ $item->[0] };
505             my $default = $config->{ $fallback_config{ $item->[0] } // '' };
506             ask($config, $item, $default);
507             }
508             delete $config->{$_} for @delete_config;
509             write_json($config_file, $config);
510             }
511             elsif ($mode eq 'all') {
512             for my $item (@items) {
513             my $default = $config->{ $item->[0] } // $config->{ $fallback_config{ $item->[0] } // '' } // $settings->{ $item->[0] };
514             ask($config, $item, $default);
515             }
516             delete $config->{$_} for @delete_config;
517             write_json($config_file, $config);
518             }
519             elsif ($mode eq 'copy') {
520             for my $item (@items) {
521             my ($key) = @{$item};
522             $config->{$key} = $settings->{$key} if exists $settings->{$key};
523             }
524             write_json($config_file, $config);
525             }
526             elsif ($mode eq 'get') {
527             my ($key, $value) = @arguments;
528             my ($item) = grep { $_->[0] eq $key } @config_items;
529             die "No such known key $key" if not $item;
530             my (undef, $description, $type, $default) = @{$item};
531             say show_item($config, $key, $type);
532             }
533             elsif ($mode eq 'set') {
534             my ($key, $value) = @arguments;
535             my $item = grep { $_->[0] eq lc $key } @config_items;
536             die "No such known key $key" if not $item;
537             $config->{$key} = $boolean{$value} // die "Unknown boolean value '$value'\n";
538             write_json($config_file, $config);
539             }
540             elsif ($mode eq 'list') {
541             for my $item (@items) {
542             my ($key, $description, $type, $default) = @{$item};
543             say "$key: " . show_item($config, $key, $type);
544             }
545             }
546             elsif ($mode eq 'reset') {
547             return not unlink $config_file;
548             }
549             return 0;
550             },
551             mint => sub {
552             my @arguments = @_;
553              
554             my $settings = get_settings(\%default_settings);
555              
556             my $distname = decode_utf8(shift @arguments // die "No distribution name given\n") =~ s/::/-/gr;
557              
558             my %args = (
559             author => $settings->{author},
560             email => $settings->{email},
561             license => $settings->{license},
562             version => '0.000',
563             dirname => $distname,
564             abstract => 'INSERT YOUR ABSTRACT HERE',
565             init_git => $settings->{auto_git},
566             );
567             my %config;
568             my @options = qw/version=s abstract=s dirname=s init_git|init-git/;
569             for my $config_item (@config_items) {
570             my $entry = $config_item->[0] =~ s{^(\w+_\w+)\K$}{ '|' . $1 =~ tr/_/-/r }er;
571             push @options, $entry . ($config_item->[2] eq 'yn' ? '!' : '=s');
572             }
573             GetOptionsFromArray(\@arguments, \%args, @options) or return 2;
574             for my $item (@config_items) {
575             my ($key, $description, $type, $default) = @{$item};
576             if ($type eq 'open') {
577             $args{$key} //= prompt($description, $default);
578             }
579             else {
580             $config{$key} = $args{$key} // $settings->{$key} // prompt_yn($description, $default);
581             }
582             }
583              
584             my $license = create_license_for(delete $args{license}, $args{author});
585              
586             die "Directory $args{dirname} already exists\n" if -e $args{dirname};
587             mkdir $args{dirname};
588             chdir $args{dirname};
589             $args{module_name} = $distname =~ s/-/::/gr;
590              
591             my $module_file = write_module(%args, notice => $license->notice);
592             write_changes(%args, distname => $distname);
593             write_maniskip($distname);
594             write_json('dist.json', \%config);
595             mkdir 't';
596              
597             write_json('metamerge.json', { name => $distname }) if $distname ne $args{dirname};
598              
599             my @regenerate_files = regenerate_files(\%config);
600             regenerate(\@regenerate_files, \%args, scan => $config{auto_scan});
601              
602             if ($args{init_git}) {
603             my $ignore = join "\n", qw/*.bak *.swp *.swo *.tdy *.tar.gz/, "$distname-*", '';
604             write_text('.gitignore', $ignore);
605              
606             require Git::Wrapper;
607             my $git = Git::Wrapper->new('.');
608             $git->init;
609             $git->add(@regenerate_files, 'Changes', 'MANIFEST.SKIP', 'dist.json', '.gitignore', $module_file, grep -e, 'metamerge.json');
610             $git->commit({ message => 'Initial commit' });
611             }
612              
613             return 0;
614             },
615             version => sub {
616             say $VERSION;
617             },
618             );
619              
620             sub modulebuildtiny {
621 0     0 1   my ($action, @arguments) = @_;
622 0 0         die "No action given\n" unless defined $action;
623 0           my $call = $actions{$action};
624 0 0         die "No such action '$action' known\n" if not $call;
625 0           return $call->(@arguments);
626             }
627              
628             1;
629              
630             =head1 NAME
631              
632             App::ModuleBuildTiny - A standalone authoring tool for Module::Build::Tiny and Dist::Build
633              
634             =head1 DESCRIPTION
635              
636             App::ModuleBuildTiny contains the implementation of the L tool.
637              
638             =head1 FUNCTIONS
639              
640             =over 4
641              
642             =item * modulebuildtiny($action, @arguments)
643              
644             This function runs a modulebuildtiny command. It expects at least one argument: the action. It may receive additional ARGV style options dependent on the command.
645              
646             The actions are documented in the L documentation.
647              
648             =back
649              
650             =head1 SEE ALSO
651              
652             =head2 Similar programs
653              
654             =over 4
655              
656             =item * L
657              
658             An extremely powerful but somewhat heavy authoring tool.
659              
660             =item * L
661              
662             A more minimalistic than Dist::Zilla but still somewhat customizable authoring tool.
663              
664             =back
665              
666             =head1 AUTHOR
667              
668             Leon Timmermans
669              
670             =head1 COPYRIGHT AND LICENSE
671              
672             This software is copyright (c) 2011 by Leon Timmermans.
673              
674             This is free software; you can redistribute it and/or modify it under
675             the same terms as the Perl 5 programming language system itself.
676              
677             =cut
678              
679             __DATA__