File Coverage

blib/lib/Module/Starter/Smart.pm
Criterion Covered Total %
statement 149 150 99.3
branch 41 58 70.6
condition 9 19 47.3
subroutine 21 21 100.0
pod 8 8 100.0
total 228 256 89.0


line stmt bran cond sub pod time code
1             package Module::Starter::Smart;
2              
3 4     4   342588 use warnings;
  4         40  
  4         156  
4 4     4   30 use strict;
  4         11  
  4         231  
5              
6             =encoding utf8
7              
8             =head1 NAME
9              
10             Module::Starter::Smart - A Module::Starter plugin for adding new modules into
11             an existing distribution
12              
13             =head1 VERSION
14              
15             version 0.0.9
16              
17             =cut
18              
19             our $VERSION = '0.0.9';
20              
21             =head1 SYNOPSIS
22              
23             use Module::Starter qw/Module::Starter::Smart/;
24             Module::Starter->create_distro(%args);
25              
26             # or in ~/.module-starter/config
27             plugin: Module::Starter::Smart
28              
29             # create a new distribution named 'Foo-Bar'
30             $ module-starter --module=Foo::Bar
31              
32             # ... then add a new module
33             $ module-starter --module=Foo::Bar::Me --distro=Foo-Bar
34              
35             =head1 DESCRIPTION
36              
37             Module::Starter::Smart is a simple helper plugin for L. It
38             subclasses L and provides its own implementation for
39             several file creation subroutines, such as C, C,
40             C, and so on. These new implementations were designed to work
41             with existing distributions.
42              
43             When invoked, the plugin checks if the distribution is already created. If so,
44             the plugin would bypass C) and go ahead pull in all the
45             existing modules and test files; these information would be used later in the
46             corresponding file creation subroutines for skipping already-created files.
47              
48             B: This plugin only covers the simplest use cases. For advanced usage,
49             check out L.
50              
51              
52             =head2 Example
53              
54             Say you have an existing distro, Goof-Ball, and you want to add a new module,
55             Goof::Troop.
56              
57             % ls -R Goof-Ball
58             Build.PL Changes MANIFEST README lib/ t/
59              
60             Goof-Ball/lib:
61             Goof/
62              
63             Goof-Ball/lib/Goof:
64             Ball.pm
65              
66             Goof-Ball/t:
67             00.load.t perlcritic.t pod-coverage.t pod.t
68              
69             Go to the directory containing your existing distribution and run
70             module-starter, giving it the names of the existing distribution and the new
71             module:
72              
73             % module-starter --distro=Goof-Ball --module=Goof::Troop
74             Created starter directories and files
75              
76             % ls -R Goof-Ball
77             Build.PL Changes MANIFEST README lib/ t/
78              
79             Goof-Ball/lib:
80             Goof/
81              
82             Goof-Ball/lib/Goof:
83             Ball.pm Troop.pm
84              
85             Goof-Ball/t:
86             00.load.t perlcritic.t pod-coverage.t pod.t
87              
88             Troop.pm has been added to Goof-Ball/lib/Goof.
89              
90             =cut
91              
92 4     4   402 use parent qw(Module::Starter::Simple);
  4         430  
  4         32  
93              
94 4     4   42446 use ExtUtils::Command qw/mkpath/;
  4         13  
  4         251  
95 4     4   30 use File::Spec;
  4         8  
  4         120  
96              
97             # Module implementation here
98 4     4   1800 use subs qw/_unique_sort _pull_modules _list_modules _pull_t _list_t/;
  4         128  
  4         29  
99              
100             =head1 INTERFACE
101              
102             No public methods. The module works by subclassing Module::Starter::Simple and
103             rewiring its internal behaviors.
104              
105             =cut
106              
107             sub create_distro {
108 3     3 1 1374 my $class = shift;
109 3 50       38 my $self = ref $class? $class: $class->new(@_);
110              
111             my $basedir =
112             $self->{dir} ||
113             $self->{distro} ||
114 3   0     56 do {
115             (my $first = $self->{modules}[0]) =~ s/::/-/g;
116             $first;
117             };
118              
119 3         21 $self->{modules} = [ _unique_sort _pull_modules($basedir), @{$self->{modules}} ];
  3         18  
120 3         28 $self->SUPER::create_distro;
121             }
122              
123             sub create_basedir {
124 3     3 1 298 my $self = shift;
125 3 100 66     64 return $self->SUPER::create_basedir(@_) unless -e $self->{basedir} && !$self->{force};
126 1         11 $self->progress( "Found $self->{basedir}. Use --force if you want to stomp on it." );
127             }
128              
129             sub create_modules {
130 3     3 1 500 my $self = shift;
131 3         20 $self->SUPER::create_modules(@_);
132             }
133              
134             sub _create_module {
135 4     4   128 my $self = shift;
136 4         13 my $module = shift;
137 4         11 my $rtname = shift;
138              
139 4         18 my @parts = split( /::/, $module );
140 4         13 my $filepart = (pop @parts) . ".pm";
141 4         23 my @dirparts = ( $self->{basedir}, 'lib', @parts );
142 4         16 my $manifest_file = join( "/", "lib", @parts, $filepart );
143 4 50       17 if ( @dirparts ) {
144 4         42 my $dir = File::Spec->catdir( @dirparts );
145 4 100       76 if ( not -d $dir ) {
146 2         9 local @ARGV = $dir;
147 2         11 mkpath @ARGV;
148 2         410 $self->progress( "Created $dir" );
149             }
150             }
151              
152 4         72 my $module_file = File::Spec->catfile( @dirparts, $filepart );
153              
154 4         46 $self->{module_file}{$module} =
155             File::Spec->catfile('lib', @parts, $filepart);
156              
157 4 100       74 if (-e $module_file) {
158 1         9 $self->progress( "Skipped $module_file" );
159             } else {
160 3 50       172 open( my $fh, ">", $module_file ) or die "Can't create $module_file: $!\n";
161 3         36 print $fh $self->module_guts( $module, $rtname );
162 3         853 close $fh;
163 3         24 $self->progress( "Created $module_file" );
164             }
165              
166 4         63 return $manifest_file;
167             }
168              
169             sub create_t {
170 3     3 1 37 my $self = shift;
171 3         23 _unique_sort $self->SUPER::create_t(@_), _pull_t $self->{basedir};
172             }
173              
174             sub _create_t {
175 18     18   1980 my $self = shift;
176 18 100       61 my $testdir = @_ == 2 ? 't' : shift;
177 18         39 my $filename = shift;
178 18         32 my $content = shift;
179              
180 18         54 my @dirparts = ( $self->{basedir}, $testdir );
181 18         128 my $tdir = File::Spec->catdir( @dirparts );
182 18 100       317 if ( not -d $tdir ) {
183 7         27 local @ARGV = $tdir;
184 7         34 mkpath();
185 7         905 $self->progress( "Created $tdir" );
186             }
187              
188 18         255 my $fname = File::Spec->catfile( @dirparts, $filename );
189              
190 18 100       258 if (-e $fname) {
191 5         33 $self->progress( "Skipped $fname" );
192             } else {
193 13 50       535 open( my $fh, ">", $fname ) or die "Can't create $fname: $!\n";
194 13         85 print $fh $content;
195 13         251 close $fh;
196 13         81 $self->progress( "Created $fname" );
197             }
198              
199 18         432 return File::Spec->catfile( $testdir, $filename );
200             }
201              
202             sub create_Makefile_PL {
203 2     2 1 1065 my $self = shift;
204 2         7 my $main_module = shift;
205              
206 2         10 my @parts = split( /::/, $main_module );
207 2         7 my $pm = pop @parts;
208 2         32 my $main_pm_file = File::Spec->catfile( "lib", @parts, "${pm}.pm" );
209 2         10 $main_pm_file =~ s{\\}{/}g; # even on Win32, use forward slash
210              
211 2         24 my $fname = File::Spec->catfile( $self->{basedir}, "Makefile.PL" );
212              
213 2 100       43 if (-e $fname) {
214 1         10 $self->progress( "Skipped $fname" );
215             } else {
216 1 50       55 open( my $fh, ">", $fname ) or die "Can't create $fname: $!\n";
217 1         16 print $fh $self->Makefile_PL_guts($main_module, $main_pm_file);
218 1         74 close $fh;
219 1         10 $self->progress( "Created $fname" );
220             }
221              
222 2         29 return "Makefile.PL";
223             }
224              
225             sub create_Build_PL {
226 1     1 1 315 my $self = shift;
227 1         2 my $main_module = shift;
228              
229 1         4 my @parts = split( /::/, $main_module );
230 1         2 my $pm = pop @parts;
231 1         9 my $main_pm_file = File::Spec->catfile( "lib", @parts, "${pm}.pm" );
232 1         3 $main_pm_file =~ s{\\}{/}g; # even on Win32, use forward slash
233              
234 1         6 my $fname = File::Spec->catfile( $self->{basedir}, "Build.PL" );
235              
236 1 50       17 if (-e $fname) {
237 0         0 $self->progress( "Skipped $fname" );
238             } else {
239 1 50       34 open( my $fh, ">", $fname ) or die "Can't create $fname: $!\n";
240 1         9 print $fh $self->Build_PL_guts($main_module, $main_pm_file);
241 1         42 close $fh;
242 1         5 $self->progress( "Created $fname" );
243             }
244              
245 1         10 return "Build.PL";
246             }
247              
248             sub create_Changes {
249 3     3 1 147 my $self = shift;
250              
251 3         37 my $fname = File::Spec->catfile( $self->{basedir}, "Changes" );
252              
253 3 100       51 if (-e $fname) {
254 1         7 $self->progress( "Skipped $fname" );
255             } else {
256 2 50       86 open( my $fh, ">", $fname ) or die "Can't create $fname: $!\n";
257 2         23 print $fh $self->Changes_guts();
258 2         60 close $fh;
259 2         13 $self->progress( "Created $fname" );
260             }
261              
262 3         35 return "Changes";
263             }
264              
265             sub create_README {
266 3     3 1 21 my $self = shift;
267 3         5 my $build_instructions = shift;
268              
269 3         33 my $fname = File::Spec->catfile( $self->{basedir}, "README" );
270              
271 3 100       43 if (-e $fname) {
272 1         5 $self->progress( "Skipped $fname" );
273             } else {
274 2 50       82 open( my $fh, ">", $fname ) or die "Can't create $fname: $!\n";
275 2         23 print $fh $self->README_guts($build_instructions);
276 2         327 close $fh;
277 2         22 $self->progress( "Created $fname" );
278             }
279              
280 3         32 return "README";
281             }
282              
283             # Utility functions
284             sub _pull_modules {
285 3     3   10 my $basedir = shift;
286 3 50       12 return unless $basedir;
287 3         33 my $libdir = File::Spec->catdir($basedir, "lib");
288 3 100 66     75 return unless $libdir && -d $libdir;
289 1         8 return _list_modules($libdir);
290             }
291              
292             sub _list_modules {
293 2     2   8 my $dir = shift;
294 2   100     13 my $prefix = shift || '';
295              
296 2 50       74 opendir my $dh, $dir or die "Cannot opendir $dir: $!";
297 2         35 my @entries = grep { !/^\.{1,2}/ } readdir $dh;
  6         37  
298 2         8 close $dh;
299              
300 2         6 my @modules = ();
301 2         8 for (@entries) {
302 2         34 my $name = File::Spec->catfile($dir, $_);
303 2 50 50     52 push @modules, _list_modules($name, $prefix ? "$prefix\:\:$_": $_) and next if -d $name;
    100          
304 1 50 33     30 $_ =~ s/\.pm$// and push @modules, $prefix ? "$prefix\:\:$_": $_ if $name =~ /\.pm$/;
    50          
305             }
306              
307 2         30 return sort @modules;
308             }
309              
310             sub _pull_t {
311 3     3   36 my $basedir = shift;
312 3 50       12 return unless $basedir;
313 3         25 my $tdir = File::Spec->catdir($basedir, "t");
314 3 50 33     62 return unless $tdir && -d $tdir;
315 3         14 return _list_t($tdir);
316             }
317              
318             sub _list_t {
319 3     3   9 my $dir = shift;
320              
321 3 50       96 opendir my $dh, $dir or die "Cannot opendir $dir: $!";
322 3 100       64 my @entries = grep { !/^\.{1,2}/ && /\.t$/ } readdir $dh;
  18         129  
323 3         14 close $dh;
324              
325 3         7 map { "t/$_" } @entries;
  12         81  
326             }
327              
328             # Remove duplicated entries
329             sub _unique_sort {
330 6     6   21 my %bag = map { $_ => 1 } @_;
  31         91  
331 6         56 sort keys %bag;
332             }
333              
334             # Magic true value required at end of module
335             1;
336              
337             __END__