File Coverage

blib/lib/Any/Template/ProcessDir.pm
Criterion Covered Total %
statement 20 22 90.9
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 28 30 93.3


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