File Coverage

blib/lib/Any/Template/ProcessDir.pm
Criterion Covered Total %
statement 86 88 97.7
branch 21 28 75.0
condition 3 5 60.0
subroutine 21 23 91.3
pod 1 5 20.0
total 132 149 88.5


line stmt bran cond sub pod time code
1             package Any::Template::ProcessDir;
2 3     3   16753 use 5.006;
  3         10  
  3         126  
3 3     3   17 use File::Basename;
  3         4  
  3         291  
4 3     3   1759 use File::Find::Wanted;
  3         751  
  3         160  
5 3     3   17 use File::Path qw(make_path remove_tree);
  3         4  
  3         205  
6 3     3   1775 use File::Slurp qw(read_file write_file);
  3         44361  
  3         294  
7 3     3   2126 use File::Spec::Functions qw(catfile catdir);
  3         2534  
  3         221  
8 3     3   1906 use Moose;
  3         1334060  
  3         25  
9 3     3   20533 use Moose::Util::TypeConstraints;
  3         7  
  3         87  
10 3     3   5359 use Try::Tiny;
  3         6  
  3         239  
11 3     3   43 use strict;
  3         5  
  3         91  
12 3     3   17 use warnings;
  3         2  
  3         2853  
13             our $VERSION = '0.08'; #VERSION
14              
15             has 'dest_dir' => ( is => 'ro' );
16             has 'dir' => ( is => 'ro' );
17             has 'dir_create_mode' => ( is => 'ro', isa => 'Int', default => oct(775) );
18             has 'file_create_mode' => ( is => 'ro', isa => 'Int', default => oct(444) );
19             has 'ignore_files' => ( is => 'ro', isa => 'CodeRef', default => sub { sub { 0 } } );
20             has 'process_file' => ( is => 'ro', isa => 'CodeRef', lazy_build => 1 );
21             has 'process_text' => ( is => 'ro', isa => 'CodeRef', lazy_build => 1 );
22             has 'readme_filename' => ( is => 'ro', default => 'README' );
23             has 'same_dir' => ( is => 'ro', init_arg => undef );
24             has 'source_dir' => ( is => 'ro' );
25             has 'template_file_regex' => ( is => 'ro', lazy_build => 1 );
26             has 'template_file_suffix' => ( is => 'ro', default => '.src' );
27              
28             sub BUILD {
29 4     4 0 28622 my ( $self, $params ) = @_;
30              
31 4 50 66     154 die "you must pass one of dir and source_dir/dest_dir"
32             if (
33             defined( $self->dir ) ==
34             ( defined( $self->source_dir ) && defined( $self->dest_dir ) ) );
35 4 100       117 if ( defined( $self->dir ) ) {
36 2         6 $self->{same_dir} = 1;
37 2         62 $self->{source_dir} = $self->{dest_dir} = $self->dir;
38             }
39             }
40              
41             sub _build_template_file_regex {
42 4     4   8 my $self = shift;
43 4         121 my $template_file_suffix = $self->template_file_suffix;
44             return
45 4 50       164 defined($template_file_suffix) ? qr/\Q$template_file_suffix\E$/ : qr/.|/;
46             }
47              
48             sub process_dir {
49 8     8 1 9714 my ($self) = @_;
50              
51 8         264 my $source_dir = $self->source_dir;
52 8         221 my $dest_dir = $self->dest_dir;
53              
54 8 100       212 if ( !$self->same_dir ) {
55 4         1613 remove_tree($dest_dir);
56 4 50       52 die "could not remove '$dest_dir'" if -d $dest_dir;
57             }
58              
59 8         303 my $ignore_files = $self->ignore_files;
60             my @source_files =
61 56 100   56   3659 find_wanted( sub { -f && !$ignore_files->($File::Find::name) },
62 8         65 $source_dir );
63 8         692 my $template_file_suffix = $self->template_file_suffix;
64              
65 8         19 foreach my $source_file (@source_files) {
66 36         74 $self->generate_dest_file($source_file);
67             }
68              
69 8 100       250 if ( !$self->same_dir ) {
70 4         18 $self->generate_readme();
71 4     4   593 try { $self->generate_source_symlink() };
  4         109  
72             }
73             }
74              
75             sub generate_dest_file {
76 36     36 0 46 my ( $self, $source_file ) = @_;
77              
78 36         1082 my $template_file_regex = $self->template_file_regex;
79 36         932 substr( ( my $dest_file = $source_file ), 0, length( $self->source_dir ) ) =
80             $self->dest_dir;
81              
82 36         39 my $dest_text;
83 36 100       765 if ( $source_file =~ $template_file_regex ) {
    100          
84 14   50     434 $dest_file =
85             substr( $dest_file, 0,
86             -1 * length( $self->template_file_suffix || '' ) );
87 14         403 my $code = $self->process_file;
88 14         40 $dest_text = $code->( $source_file, $self );
89             }
90             elsif ( !$self->same_dir ) {
91 6         20 $dest_text = read_file($source_file);
92             }
93             else {
94 16         26 return;
95             }
96              
97 20 100       2479 if ( $self->same_dir ) {
98 8         293 unlink($dest_file);
99             }
100             else {
101 12         1491 make_path( dirname($dest_file) );
102 12 50       449 chmod( $self->dir_create_mode(), dirname($dest_file) )
103             if defined( $self->dir_create_mode() );
104             }
105              
106 20         77 write_file( $dest_file, $dest_text );
107 20 50       3839 chmod( $self->file_create_mode(), $dest_file )
108             if defined( $self->file_create_mode() );
109             }
110              
111             sub _build_process_file {
112             return sub {
113 7     7   9 my ( $file, $self ) = @_;
114              
115 7         224 my $code = $self->process_text;
116 7         24 return $code->( read_file($file), $self );
117             }
118 2     2   69 }
119              
120             sub _build_process_text {
121 0     0   0 return sub { die "must specify one of process_file or process_text" }
122 0     0   0 }
123              
124             sub generate_readme {
125 4     4 0 8 my $self = shift;
126              
127 4 50       144 if ( defined( $self->readme_filename ) ) {
128 4         126 my $readme_file = catfile( $self->dest_dir, $self->readme_filename );
129 4         87 unlink($readme_file);
130 4         161 write_file(
131             $readme_file,
132             "Files in this directory generated from "
133             . $self->source_dir . ".\n",
134             "Do not edit files here, as they will be overwritten. Edit the source instead!"
135             );
136             }
137             }
138              
139             sub generate_source_symlink {
140 4     4 0 7 my $self = shift;
141              
142             # Create symlink from dest dir back to source dir.
143             #
144 4         136 my $source_link = catdir( $self->dest_dir, "source" );
145 4 50       76 unlink($source_link) if -e $source_link;
146 4         119 symlink( $self->source_dir, $source_link );
147             }
148              
149             1;
150              
151             __END__
152              
153             =pod
154              
155             =head1 NAME
156              
157             Any::Template::ProcessDir -- Process a directory of templates
158              
159             =head1 VERSION
160              
161             version 0.08
162              
163             =head1 SYNOPSIS
164              
165             use Any::Template::ProcessDir;
166              
167             # Process templates and generate result files in a single directory
168             #
169             my $pd = Any::Template::ProcessDir->new(
170             dir => '/path/to/dir',
171             process_text => sub {
172             my $template = Any::Template->new( Backend => '...', String => $_[0] );
173             $template->process({ ... });
174             }
175             );
176             $pd->process_dir();
177              
178             # Process templates and generate result files to a separate directory
179             #
180             my $pd = Any::Template::ProcessDir->new(
181             source_dir => '/path/to/source/dir',
182             dest_dir => '/path/to/dest/dir',
183             process_file => sub {
184             my $file = $_[0];
185             # do something with $file, return content
186             }
187             );
188             $pd->process_dir();
189              
190             =head1 DESCRIPTION
191              
192             Recursively processes a directory of templates, generating a set of result
193             files in the same directory or in a parallel directory. Each file in the source
194             directory may be template-processed, copied, or ignored depending on its
195             pathname.
196              
197             =head1 CONSTRUCTOR
198              
199             =head2 Specifying directory/directories
200              
201             =over
202              
203             =item *
204              
205             If you want to generate the result files in the B<same> directory as the
206             templates, just specify I<dir>.
207              
208             my $pd = Any::Template::ProcessDir->new(
209             dir => '/path/to/dir',
210             ...
211             );
212              
213             =item *
214              
215             If you want to generate the result files in a B<separate> directory from the
216             templates, specify I<source_dir> and I<dest_dir>.
217              
218             my $pd = Any::Template::ProcessDir->new(
219             source_dir => '/path/to/source/dir',
220             dest_dir => '/path/to/dest/dir',
221             ...
222             );
223              
224             =back
225              
226             =head2 Specifying how to process templates
227              
228             =over
229              
230             =item process_file
231              
232             A code reference that takes the full template filename and the
233             C<Any::Template::ProcessDir> object as arguments, and returns the result
234             string. This can use L<Any::Template> or another method altogether. By default
235             it calls L</process_text> on the contents of the file.
236              
237             =item process_text
238              
239             A code reference that takes the template text and the
240             C<Any::Template::ProcessDir> object as arguments, and returns the result
241             string. This can use L<Any::Template> or another method altogether.
242              
243             =back
244              
245             =head2 Optional parameters
246              
247             =over
248              
249             =item dir_create_mode
250              
251             Permissions mode to use when creating destination directories. Defaults to
252             0775. No effect if you are using a single directory.
253              
254             =item file_create_mode
255              
256             Permissions mode to use when creating destination files. Defaults to 0444
257             (read-only), so that destination files are not accidentally edited.
258              
259             =item ignore_files
260              
261             Coderef which takes a full pathname and returns true if the file should be
262             ignored. By default, all files will be considered.
263              
264             =item readme_filename
265              
266             Name of a README file to generate in the destination directory - defaults to
267             "README". No file will be generated if you pass undef or if you are using a
268             single directory.
269              
270             =item template_file_suffix
271              
272             Suffix of template files in source directory. Defaults to ".src". This will be
273             removed from the destination file name.
274              
275             Any file in the source directory that does not have this suffix (or
276             L</ignore_file_suffix>) will simply be copied to the destination.
277              
278             =back
279              
280             =head1 METHODS
281              
282             =over
283              
284             =item process_dir
285              
286             Process the directory. If using multiple directories, the destination directory
287             will be removed completely and recreated, to eliminate any old files from
288             previous processing.
289              
290             =back
291              
292             =head1 SEE ALSO
293              
294             L<Any::Template>
295              
296             =head1 COPYRIGHT AND LICENSE
297              
298             This software is copyright (c) 2011 by Jonathan Swartz.
299              
300             This is free software; you can redistribute it and/or modify it under
301             the same terms as the Perl 5 programming language system itself.
302              
303             =cut