File Coverage

blib/lib/App/Module/Template.pm
Criterion Covered Total %
statement 145 146 99.3
branch 25 26 96.1
condition 9 9 100.0
subroutine 28 28 100.0
pod 1 1 100.0
total 208 210 99.0


line stmt bran cond sub pod time code
1             package App::Module::Template;
2              
3 18     18   22057 use 5.016;
  18         66  
  18         567  
4              
5 18     18   102 use strict;
  18         30  
  18         586  
6 18     18   106 use warnings;
  18         32  
  18         984  
7              
8             our $VERSION = '0.11';
9              
10 18     18   105 use base qw(Exporter);
  18         30  
  18         3407  
11              
12 18     18   13204 use App::Module::Template::Initialize;
  18         55  
  18         950  
13              
14 18     18   108 use Carp;
  18         38  
  18         1237  
15 18     18   29189 use Config::General;
  18         608089  
  18         1334  
16 18     18   19931 use File::Copy;
  18         49196  
  18         1718  
17 18     18   132 use File::HomeDir;
  18         45  
  18         911  
18 18     18   101 use File::Path qw/make_path/;
  18         35  
  18         810  
19 18     18   92 use File::Spec;
  18         396  
  18         339  
20 18     18   51808 use Getopt::Std;
  18         877  
  18         1398  
21 18     18   16444 use POSIX qw(strftime);
  18         152656  
  18         204  
22 18     18   281786 use Template;
  18         870693  
  18         590  
23 18     18   29899 use Try::Tiny;
  18         32301  
  18         36459  
24              
25             our (@EXPORT_OK, %EXPORT_TAGS);
26             @EXPORT_OK = qw(
27             run
28             _get_config
29             _get_config_path
30             _get_module_dirs
31             _get_module_fqfn
32             _get_template_path
33             _module_path_exists
34             _process_dirs
35             _process_file
36             _process_template
37             _validate_module_name
38             );
39             %EXPORT_TAGS = (
40             ALL => [ @EXPORT_OK ],
41             );
42              
43             #-------------------------------------------------------------------------------
44             sub run {
45 7     7 1 26649 my $class = shift;
46              
47 7         28 my %opt;
48             # -c config file
49             # -m module name
50             # -t template dir, location of template files
51 7         44 getopts('c:m:t:', \%opt);
52              
53 7 100 100     441 unless ( ( exists $opt{m} ) and ( defined $opt{m} ) ) {
54 2         35 croak "-m is required. exiting...\n";
55             }
56              
57 5         14 my $module = $opt{m};
58 5         12 my $dist = $module; $dist =~ s/::/-/gmsx;
  5         22  
59 5         12 my $file = $module; $file =~ s/.*:://msx; $file .= '.pm';
  5         23  
  5         11  
60 5         65 my $dist_dir = File::Spec->catdir( File::Spec->curdir, $dist );
61 5         11 my $tmpl_vars;
62              
63             try {
64 5     5   329 _validate_module_name($module);
65             } catch {
66 1     1   890 croak "$_ module-template. exiting...";
67 5         49 };
68              
69 4 100       88 if ( _module_path_exists($dist_dir) ) {
70 1         24 croak "Destination directory $dist_dir exists. exiting...";
71             }
72              
73 3         16 my $template_dir = _get_template_path($opt{t});
74              
75 3         19 my $config_file = _get_config_path($opt{c}, $template_dir);
76              
77 3         12 my $cfg = _get_config($config_file);
78              
79             # Setting this lets TT2 handle creating the destination files/directories
80 3         13 $cfg->{template_toolkit}{OUTPUT_PATH} = $dist_dir;
81              
82 3         53 my $tt2 = Template->new( $cfg->{template_toolkit} );
83              
84             # don't need this in the $tmpl_vars
85 3         64034 delete $cfg->{template_toolkit};
86              
87 3         51 my $dirs = _get_module_dirs( $module );
88              
89             # Template Vars
90 3         6 $tmpl_vars = $cfg;
91 3         9 $tmpl_vars->{module} = $module;
92 3         515 $tmpl_vars->{today} = strftime('%Y-%m-%d', localtime());
93 3         142 $tmpl_vars->{year} = strftime('%Y', localtime());
94 3         9 $tmpl_vars->{module_path} = File::Spec->catfile( @{$dirs}, $file );
  3         58  
95              
96 3         25 _process_dirs($tt2, $tmpl_vars, $template_dir, $template_dir);
97              
98             # add the distribution dir to the front so our module ends up in the
99             # right place
100 3         6 unshift @{$dirs}, $dist_dir;
  3         38  
101              
102 3         14 my $fqfn = _get_module_fqfn( $dirs, $file );
103              
104             # create the module directory to receive the named module.pm
105 3         9 make_path( File::Spec->catdir( @{$dirs} ) );
  3         659  
106              
107             # rename the template file with the module file name
108 3         47 move( File::Spec->catfile( $dist_dir, 'lib', 'Module.pm' ), $fqfn );
109              
110 3         403 return 1;
111             }
112              
113             #-------------------------------------------------------------------------------
114             sub _get_config {
115 5     5   4021 my ($config_file) = @_;
116              
117 5 100       68 my %cfg = Config::General->new(
118             -ConfigFile => $config_file,
119             -MergeDuplicateBlocks => 1,
120             -MergeDuplicateOptions => 1,
121             -AutoLaunder => 1,
122             -SplitPolicy => 'equalsign',
123             -InterPolateVars => 1,
124             -UTF8 => 1,
125             )->getall() or croak "Could not read configuration file $config_file";
126              
127 4         19526 return \%cfg;
128             }
129              
130             #-------------------------------------------------------------------------------
131             sub _get_config_path {
132 6     6   2792 my ($opt, $template_dir) = @_;
133              
134 6         12 my $config_file;
135              
136 6 100       18 if ( defined $opt ) {
137 2         5 $config_file = $opt;
138             }
139             else {
140 4         48 $config_file = File::Spec->catfile( $template_dir, '../config' );
141             }
142              
143 6 100       123 unless ( -f $config_file ) {
144 1         31 croak "Could not locate configuration file $config_file\n";
145             }
146              
147 5         18 return $config_file;
148             }
149              
150             #-------------------------------------------------------------------------------
151             # Split the module name into directories
152             #-------------------------------------------------------------------------------
153             sub _get_module_dirs {
154 4     4   2434 my ($module) = @_;
155              
156 4         29 my @dirs = split( /::/msx, $module );
157              
158             # remove the last part of the module name because that will be the filename
159 4         11 pop @dirs;
160              
161 4         13 unshift @dirs, 'lib';
162              
163 4         20 return \@dirs;
164             }
165              
166             #-------------------------------------------------------------------------------
167             # Return the path to the fully qualified file name
168             #-------------------------------------------------------------------------------
169             sub _get_module_fqfn {
170 4     4   6690 my ($dirs, $file_name) = @_;
171              
172 4         8 return File::Spec->catfile( @{$dirs}, $file_name );
  4         64  
173             }
174              
175             #-------------------------------------------------------------------------------
176             sub _get_template_path {
177 5     5   3784 my ($opt) = @_;
178              
179 5         8 my $template_dir;
180              
181 5 100       20 if ( defined $opt ) {
182              
183 3 100       65 unless ( -d $opt ) {
184 1         41 croak "Template directory $opt does not exist";
185             }
186              
187 2         4 $template_dir = $opt;
188             }
189             else {
190 2         14 $template_dir = File::Spec->catdir( File::HomeDir->my_home(), '.module-template', 'templates' );
191              
192 2 100       114 unless ( -d $template_dir ) {
193             # initialize .module-template in user's home directory
194 1         7 App::Module::Template::Initialize::module_template();
195             }
196             }
197              
198 4         19 return $template_dir;
199             }
200              
201             #-------------------------------------------------------------------------------
202             sub _module_path_exists {
203 7     7   1700 my ($module_path) = @_;
204              
205 7 100 100     118 if ( ( defined $module_path ) and ( -d $module_path ) ) {
206 2         8 return 1;
207             }
208              
209 5         20 return;
210             }
211              
212             #-------------------------------------------------------------------------------
213             # Walk the template directory
214             #-------------------------------------------------------------------------------
215             sub _process_dirs {
216 103     103   104153 my ($tt2, $tmpl_vars, $template_dir, $source) = @_;
217              
218 103 100       2235 if ( -d $source ) {
219 36         52 my $dir;
220              
221 36 50       1103 unless ( opendir $dir, $source ) {
222 0         0 croak "Couldn't open directory $source: $!; skipping.\n";
223             }
224              
225 36         645 while ( my $file = readdir $dir ) {
226 169 100 100     1060 next if $file eq '.' or $file eq '..';
227              
228 97         1254 my $target = File::Spec->catfile($source, $file);
229              
230 97         386 _process_dirs($tt2, $tmpl_vars, $template_dir, $target);
231             }
232              
233 36         565 closedir $dir;
234             }
235             else {
236 67         151 my $output = _process_file($template_dir, $source);
237              
238 67         161 _process_template($tt2, $tmpl_vars, $source, $output);
239             }
240              
241 103         624 return $source;
242             }
243              
244             #-------------------------------------------------------------------------------
245             # Return the output path for TT2
246             #-------------------------------------------------------------------------------
247             sub _process_file {
248 71     71   4884 my ($template_dir, $source_file) = @_;
249              
250             # regex matches paths on *nix or *dos
251 71         542 my ($stub) = $source_file =~ m{\A$template_dir[/\\](.*)\z}mosx;
252              
253 71         180 return $stub;
254             }
255              
256             #-------------------------------------------------------------------------------
257             sub _process_template {
258 69     69   25849 my ($tt2, $tmpl_vars, $template, $output) = @_;
259              
260 69 100       314 $tt2->process($template, $tmpl_vars, $output) or croak $tt2->error();
261              
262 68         419006 return $template;
263             }
264              
265             #-------------------------------------------------------------------------------
266             # Validate the module naming convention
267             #
268             # 1. No top-level namespaces
269             # 2. No all lower case names
270             # 3. Match XXX::XXX
271             #-------------------------------------------------------------------------------
272             sub _validate_module_name {
273 13     13   1230 my ($module_name) = @_;
274              
275 13         25 given ( $module_name ) {
276             when ( $module_name =~ m/\A[A-Za-z]+\z/msx )
277 13         53 {
278 1         24 croak "'$module_name' is a top-level namespace";
279             }
280             when ( $module_name =~ m/\A[a-z]+\:\:[a-z]+/msx )
281 12         30 {
282 3         60 croak "'$module_name' is an all lower-case namespace";
283             }
284             # module name conforms
285             when ( $module_name =~ m/\A[A-Z][A-Za-z]+(?:\:\:[A-Z][A-Za-z]+)+\z/msx )
286 9         36 {
287 7         29 return 1;
288             }
289 2         3 default {
290 2         23 croak "'$module_name' does not meet naming requirements";
291             }
292             }
293             }
294              
295             1;
296              
297             __END__