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