File Coverage

blib/lib/Module/Starter/Smart.pm
Criterion Covered Total %
statement 136 150 90.6
branch 39 58 67.2
condition 9 19 47.3
subroutine 20 21 95.2
pod 8 8 100.0
total 212 256 82.8


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