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   85599 use warnings;
  3         7  
  3         80  
4 3     3   14 use strict;
  3         6  
  3         127  
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.8
16              
17             =cut
18              
19             our $VERSION = '0.0.8';
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 3     3   388 use parent qw(Module::Starter::Simple);
  3         228  
  3         17  
93              
94 3     3   25093 use ExtUtils::Command qw/mkpath/;
  3         7  
  3         120  
95 3     3   16 use File::Spec;
  3         7  
  3         60  
96              
97             # Module implementation here
98 3     3   1305 use subs qw/_unique_sort _pull_modules _list_modules _pull_t _list_t/;
  3         65  
  3         15  
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 2     2 1 781 my $class = shift;
109 2 50       25 my $self = ref $class? $class: $class->new(@_);
110              
111             my $basedir =
112             $self->{dir} ||
113             $self->{distro} ||
114 2   0     32 do {
115             (my $first = $self->{modules}[0]) =~ s/::/-/g;
116             $first;
117             };
118              
119 2         11 $self->{modules} = [ _unique_sort _pull_modules($basedir), @{$self->{modules}} ];
  2         10  
120 2         14 $self->SUPER::create_distro;
121             }
122              
123             sub create_basedir {
124 2     2 1 156 my $self = shift;
125 2 100 66     40 return $self->SUPER::create_basedir(@_) unless -e $self->{basedir} && !$self->{force};
126 1         23 $self->progress( "Found $self->{basedir}. Use --force if you want to stomp on it." );
127             }
128              
129             sub create_modules {
130 2     2 1 265 my $self = shift;
131 2         12 $self->SUPER::create_modules(@_);
132             }
133              
134             sub _create_module {
135 3     3   52 my $self = shift;
136 3         6 my $module = shift;
137 3         7 my $rtname = shift;
138              
139 3         10 my @parts = split( /::/, $module );
140 3         9 my $filepart = (pop @parts) . ".pm";
141 3         10 my @dirparts = ( $self->{basedir}, 'lib', @parts );
142 3         8 my $manifest_file = join( "/", "lib", @parts, $filepart );
143 3 50       10 if ( @dirparts ) {
144 3         17 my $dir = File::Spec->catdir( @dirparts );
145 3 100       51 if ( not -d $dir ) {
146 1         4 local @ARGV = $dir;
147 1         10 mkpath @ARGV;
148 1         218 $self->progress( "Created $dir" );
149             }
150             }
151              
152 3         33 my $module_file = File::Spec->catfile( @dirparts, $filepart );
153              
154 3         21 $self->{module_file}{$module} =
155             File::Spec->catfile('lib', @parts, $filepart);
156              
157 3 100       52 if (-e $module_file) {
158 1         5 $self->progress( "Skipped $module_file" );
159             } else {
160 2 50       107 open( my $fh, ">", $module_file ) or die "Can't create $module_file: $!\n";
161 2         21 print $fh $self->module_guts( $module, $rtname );
162 2         612 close $fh;
163 2         13 $self->progress( "Created $module_file" );
164             }
165              
166 3         34 return $manifest_file;
167             }
168              
169             sub create_t {
170 2     2 1 26 my $self = shift;
171 2         13 _unique_sort $self->SUPER::create_t(@_), _pull_t $self->{basedir};
172             }
173              
174             sub _create_t {
175 13     13   2150 my $self = shift;
176 13 100       40 my $testdir = @_ == 2 ? 't' : shift;
177 13         26 my $filename = shift;
178 13         20 my $content = shift;
179              
180 13         38 my @dirparts = ( $self->{basedir}, $testdir );
181 13         73 my $tdir = File::Spec->catdir( @dirparts );
182 13 100       217 if ( not -d $tdir ) {
183 5         17 local @ARGV = $tdir;
184 5         20 mkpath();
185 5         650 $self->progress( "Created $tdir" );
186             }
187              
188 13         147 my $fname = File::Spec->catfile( @dirparts, $filename );
189              
190 13 100       222 if (-e $fname) {
191 5         20 $self->progress( "Skipped $fname" );
192             } else {
193 8 50       392 open( my $fh, ">", $fname ) or die "Can't create $fname: $!\n";
194 8         57 print $fh $content;
195 8         181 close $fh;
196 8         46 $self->progress( "Created $fname" );
197             }
198              
199 13         222 return File::Spec->catfile( $testdir, $filename );
200             }
201              
202             sub create_Makefile_PL {
203 2     2 1 758 my $self = shift;
204 2         4 my $main_module = shift;
205              
206 2         7 my @parts = split( /::/, $main_module );
207 2         6 my $pm = pop @parts;
208 2         19 my $main_pm_file = File::Spec->catfile( "lib", @parts, "${pm}.pm" );
209 2         8 $main_pm_file =~ s{\\}{/}g; # even on Win32, use forward slash
210              
211 2         12 my $fname = File::Spec->catfile( $self->{basedir}, "Makefile.PL" );
212              
213 2 100       41 if (-e $fname) {
214 1         6 $self->progress( "Skipped $fname" );
215             } else {
216 1 50       52 open( my $fh, ">", $fname ) or die "Can't create $fname: $!\n";
217 1         13 print $fh $self->Makefile_PL_guts($main_module, $main_pm_file);
218 1         57 close $fh;
219 1         7 $self->progress( "Created $fname" );
220             }
221              
222 2         18 return "Makefile.PL";
223             }
224              
225             sub create_Build_PL {
226 0     0 1 0 my $self = shift;
227 0         0 my $main_module = shift;
228              
229 0         0 my @parts = split( /::/, $main_module );
230 0         0 my $pm = pop @parts;
231 0         0 my $main_pm_file = File::Spec->catfile( "lib", @parts, "${pm}.pm" );
232 0         0 $main_pm_file =~ s{\\}{/}g; # even on Win32, use forward slash
233              
234 0         0 my $fname = File::Spec->catfile( $self->{basedir}, "Build.PL" );
235              
236 0 0       0 if (-e $fname) {
237 0         0 $self->progress( "Skipped $fname" );
238             } else {
239 0 0       0 open( my $fh, ">", $fname ) or die "Can't create $fname: $!\n";
240 0         0 print $fh $self->Build_PL_guts($main_module, $main_pm_file);
241 0         0 close $fh;
242 0         0 $self->progress( "Created $fname" );
243             }
244              
245 0         0 return "Build.PL";
246             }
247              
248             sub create_Changes {
249 2     2 1 90 my $self = shift;
250              
251 2         17 my $fname = File::Spec->catfile( $self->{basedir}, "Changes" );
252              
253 2 100       36 if (-e $fname) {
254 1         5 $self->progress( "Skipped $fname" );
255             } else {
256 1 50       46 open( my $fh, ">", $fname ) or die "Can't create $fname: $!\n";
257 1         11 print $fh $self->Changes_guts();
258 1         32 close $fh;
259 1         6 $self->progress( "Created $fname" );
260             }
261              
262 2         20 return "Changes";
263             }
264              
265             sub create_README {
266 2     2 1 14 my $self = shift;
267 2         4 my $build_instructions = shift;
268              
269 2         15 my $fname = File::Spec->catfile( $self->{basedir}, "README" );
270              
271 2 100       34 if (-e $fname) {
272 1         9 $self->progress( "Skipped $fname" );
273             } else {
274 1 50       44 open( my $fh, ">", $fname ) or die "Can't create $fname: $!\n";
275 1         14 print $fh $self->README_guts($build_instructions);
276 1         167 close $fh;
277 1         6 $self->progress( "Created $fname" );
278             }
279              
280 2         20 return "README";
281             }
282              
283             # Utility functions
284             sub _pull_modules {
285 2     2   4 my $basedir = shift;
286 2 50       7 return unless $basedir;
287 2         17 my $libdir = File::Spec->catdir($basedir, "lib");
288 2 100 66     54 return unless $libdir && -d $libdir;
289 1         4 return _list_modules($libdir);
290             }
291              
292             sub _list_modules {
293 2     2   5 my $dir = shift;
294 2   100     10 my $prefix = shift || '';
295              
296 2 50       60 opendir my $dh, $dir or die "Cannot opendir $dir: $!";
297 2         21 my @entries = grep { !/^\.{1,2}/ } readdir $dh;
  6         36  
298 2         7 close $dh;
299              
300 2         6 my @modules = ();
301 2         5 for (@entries) {
302 2         24 my $name = File::Spec->catfile($dir, $_);
303 2 50 50     42 push @modules, _list_modules($name, $prefix ? "$prefix\:\:$_": $_) and next if -d $name;
    100          
304 1 50 33     14 $_ =~ s/\.pm$// and push @modules, $prefix ? "$prefix\:\:$_": $_ if $name =~ /\.pm$/;
    50          
305             }
306              
307 2         18 return sort @modules;
308             }
309              
310             sub _pull_t {
311 2     2   17 my $basedir = shift;
312 2 50       6 return unless $basedir;
313 2         9 my $tdir = File::Spec->catdir($basedir, "t");
314 2 50 33     39 return unless $tdir && -d $tdir;
315 2         9 return _list_t($tdir);
316             }
317              
318             sub _list_t {
319 2     2   3 my $dir = shift;
320              
321 2 50       56 opendir my $dh, $dir or die "Cannot opendir $dir: $!";
322 2 100       40 my @entries = grep { !/^\.{1,2}/ && /\.t$/ } readdir $dh;
  12         69  
323 2         6 close $dh;
324              
325 2         6 map { "t/$_" } @entries;
  8         38  
326             }
327              
328             # Remove duplicated entries
329             sub _unique_sort {
330 4     4   11 my %bag = map { $_ => 1 } @_;
  21         48  
331 4         29 sort keys %bag;
332             }
333              
334             # Magic true value required at end of module
335             1;
336              
337             __END__