1#============================================================= -*-Perl-*-
2#
3# Template::Plugin::Directory
4#
5# DESCRIPTION
6#   Plugin for encapsulating information about a file system directory.
7#
8# AUTHORS
9#   Michael Stevens <michael@etla.org>, with some mutilations from
10#   Andy Wardley <abw@wardley.org>.
11#
12# COPYRIGHT
13#   Copyright (C) 2000-2007 Michael Stevens, Andy Wardley.
14#
15#   This module is free software; you can redistribute it and/or
16#   modify it under the same terms as Perl itself.
17#
18#============================================================================
19
20package Template::Plugin::Directory;
21
22use strict;
23use warnings;
24use Cwd;
25use File::Spec;
26use Template::Plugin::File;
27use base 'Template::Plugin::File';
28
29our $VERSION = 2.70;
30
31
32#------------------------------------------------------------------------
33# new(\%config)
34#
35# Constructor method.
36#------------------------------------------------------------------------
37
38sub new {
39    my $config = ref($_[-1]) eq 'HASH' ? pop(@_) : { };
40    my ($class, $context, $path) = @_;
41
42    return $class->throw('no directory specified')
43        unless defined $path and length $path;
44
45    my $self = $class->SUPER::new($context, $path, $config);
46    my ($dir, @files, $name, $item, $abs, $rel, $check);
47    $self->{ files } = [ ];
48    $self->{ dirs  } = [ ];
49    $self->{ list  } = [ ];
50    $self->{ _dir  } = { };
51
52    # don't read directory if 'nostat' or 'noscan' set
53    return $self if $config->{ nostat } || $config->{ noscan };
54
55    $self->throw("$path: not a directory")
56        unless $self->{ isdir };
57
58    $self->scan($config);
59
60    return $self;
61}
62
63
64#------------------------------------------------------------------------
65# scan(\%config)
66#
67# Scan directory for files and sub-directories.
68#------------------------------------------------------------------------
69
70sub scan {
71    my ($self, $config) = @_;
72    $config ||= { };
73    local *DH;
74    my ($dir, @files, $name, $abs, $rel, $item);
75
76    # set 'noscan' in config if recurse isn't set, to ensure Directories
77    # created don't try to scan deeper
78    $config->{ noscan } = 1 unless $config->{ recurse };
79
80    $dir = $self->{ abs };
81    opendir(DH, $dir) or return $self->throw("$dir: $!");
82
83    @files = readdir DH;
84    closedir(DH)
85        or return $self->throw("$dir close: $!");
86
87    my ($path, $files, $dirs, $list) = @$self{ qw( path files dirs list ) };
88    @$files = @$dirs = @$list = ();
89
90    foreach $name (sort @files) {
91        next if $name =~ /^\./;
92        $abs = File::Spec->catfile($dir, $name);
93        $rel = File::Spec->catfile($path, $name);
94
95        if (-d $abs) {
96            $item = Template::Plugin::Directory->new(undef, $rel, $config);
97            push(@$dirs, $item);
98        }
99        else {
100            $item = Template::Plugin::File->new(undef, $rel, $config);
101            push(@$files, $item);
102        }
103        push(@$list, $item);
104        $self->{ _dir }->{ $name } = $item;
105    }
106
107    return '';
108}
109
110
111#------------------------------------------------------------------------
112# file($filename)
113#
114# Fetch a named file from this directory.
115#------------------------------------------------------------------------
116
117sub file {
118    my ($self, $name) = @_;
119    return $self->{ _dir }->{ $name };
120}
121
122
123#------------------------------------------------------------------------
124# present($view)
125#
126# Present self to a Template::View
127#------------------------------------------------------------------------
128
129sub present {
130    my ($self, $view) = @_;
131    $view->view_directory($self);
132}
133
134
135#------------------------------------------------------------------------
136# content($view)
137#
138# Present directory content to a Template::View.
139#------------------------------------------------------------------------
140
141sub content {
142    my ($self, $view) = @_;
143    return $self->{ list } unless $view;
144    my $output = '';
145    foreach my $file (@{ $self->{ list } }) {
146        $output .= $file->present($view);
147    }
148    return $output;
149}
150
151
152#------------------------------------------------------------------------
153# throw($msg)
154#
155# Throw a 'Directory' exception.
156#------------------------------------------------------------------------
157
158sub throw {
159    my ($self, $error) = @_;
160    die (Template::Exception->new('Directory', $error));
161}
162
1631;
164
165__END__
166
167=head1 NAME
168
169Template::Plugin::Directory - Plugin for generating directory listings
170
171=head1 SYNOPSIS
172
173    [% USE dir = Directory(dirpath) %]
174
175    # files returns list of regular files
176    [% FOREACH file = dir.files %]
177       [% file.name %] [% file.path %] ...
178    [% END %]
179
180    # dirs returns list of sub-directories
181    [% FOREACH subdir = dir.dirs %]
182       [% subdir.name %] [% subdir.path %] ...
183    [% END %]
184
185    # list returns both interleaved in order
186    [% FOREACH item = dir.list %]
187       [% IF item.isdir %]
188          Directory: [% item.name %]
189       [% ELSE %]
190          File: [% item.name %]
191       [% END %]
192    [% END %]
193
194    # define a VIEW to display dirs/files
195    [% VIEW myview %]
196       [% BLOCK file %]
197       File: [% item.name %]
198       [% END %]
199
200       [% BLOCK directory %]
201       Directory: [% item.name %]
202       [% item.content(myview) | indent -%]
203       [% END %]
204    [% END %]
205
206    # display directory content using view
207    [% myview.print(dir) %]
208
209=head1 DESCRIPTION
210
211This Template Toolkit plugin provides a simple interface to directory
212listings.  It is derived from the L<Template::Plugin::File> module and
213uses L<Template::Plugin::File> object instances to represent files within
214a directory.  Sub-directories within a directory are represented by
215further C<Template::Plugin::Directory> instances.
216
217The constructor expects a directory name as an argument.
218
219    [% USE dir = Directory('/tmp') %]
220
221It then provides access to the files and sub-directories contained within
222the directory.
223
224    # regular files (not directories)
225    [% FOREACH file IN dir.files %]
226       [% file.name %]
227    [% END %]
228
229    # directories only
230    [% FOREACH file IN dir.dirs %]
231       [% file.name %]
232    [% END %]
233
234    # files and/or directories
235    [% FOREACH file IN dir.list %]
236       [% file.name %] ([% file.isdir ? 'directory' : 'file' %])
237    [% END %]
238
239The plugin constructor will throw a C<Directory> error if the specified
240path does not exist, is not a directory or fails to C<stat()> (see
241L<Template::Plugin::File>).  Otherwise, it will scan the directory and
242create lists named 'C<files>' containing files, 'C<dirs>' containing
243directories and 'C<list>' containing both files and directories combined.
244The C<nostat> option can be set to disable all file/directory checks
245and directory scanning.
246
247Each file in the directory will be represented by a
248L<Template::Plugin::File> object instance, and each directory by another
249C<Template::Plugin::Directory>.  If the C<recurse> flag is set, then those
250directories will contain further nested entries, and so on.  With the
251C<recurse> flag unset, as it is by default, then each is just a place
252marker for the directory and does not contain any further content
253unless its C<scan()> method is explicitly called.  The C<isdir> flag can
254be tested against files and/or directories, returning true if the item
255is a directory or false if it is a regular file.
256
257    [% FOREACH file = dir.list %]
258       [% IF file.isdir %]
259          * Directory: [% file.name %]
260       [% ELSE %]
261          * File: [% file.name %]
262       [% END %]
263    [% END %]
264
265This example shows how you might walk down a directory tree, displaying
266content as you go.  With the recurse flag disabled, as is the default,
267we need to explicitly call the C<scan()> method on each directory, to force
268it to lookup files and further sub-directories contained within.
269
270    [% USE dir = Directory(dirpath) %]
271    * [% dir.path %]
272    [% INCLUDE showdir %]
273
274    [% BLOCK showdir -%]
275      [% FOREACH file = dir.list -%]
276        [% IF file.isdir -%]
277        * [% file.name %]
278          [% file.scan -%]
279          [% INCLUDE showdir dir=file FILTER indent(4) -%]
280        [% ELSE -%]
281        - [% f.name %]
282        [% END -%]
283      [% END -%]
284     [% END %]
285
286This example is adapted (with some re-formatting for clarity) from
287a test in F<t/directry.t> which produces the following output:
288
289    * test/dir
290        - file1
291        - file2
292        * sub_one
293            - bar
294            - foo
295        * sub_two
296            - waz.html
297            - wiz.html
298        - xyzfile
299
300The C<recurse> flag can be set (disabled by default) to cause the
301constructor to automatically recurse down into all sub-directories,
302creating a new C<Template::Plugin::Directory> object for each one and
303filling it with any further content.  In this case there is no need
304to explicitly call the C<scan()> method.
305
306    [% USE dir = Directory(dirpath, recurse=1) %]
307       ...
308
309        [% IF file.isdir -%]
310        * [% file.name %]
311          [% INCLUDE showdir dir=file FILTER indent(4) -%]
312        [% ELSE -%]
313           ...
314
315The directory plugin also provides support for views. A view can be defined as
316a C<VIEW ... END> block and should contain C<BLOCK> definitions for files
317('C<file>') and directories ('C<directory>').
318
319    [% VIEW myview %]
320    [% BLOCK file %]
321       - [% item.name %]
322    [% END %]
323
324    [% BLOCK directory %]
325       * [% item.name %]
326         [% item.content(myview) FILTER indent %]
327    [% END %]
328    [% END %]
329
330The view C<print()> method can then be called, passing the
331C<Directory> object as an argument.
332
333    [% USE dir = Directory(dirpath, recurse=1) %]
334    [% myview.print(dir) %]
335
336When a directory is presented to a view, either as C<[% myview.print(dir) %]>
337or C<[% dir.present(view) %]>, then the C<directory> C<BLOCK> within the
338C<myview> C<VIEW> is processed. The C<item> variable will be set to alias the
339C<Directory> object.
340
341    [% BLOCK directory %]
342       * [% item.name %]
343         [% item.content(myview) FILTER indent %]
344    [% END %]
345
346In this example, the directory name is first printed and the content(view)
347method is then called to present each item within the directory to the view.
348Further directories will be mapped to the C<directory> block, and files will be
349mapped to the C<file> block.
350
351With the recurse option disabled, as it is by default, the C<directory>
352block should explicitly call a C<scan()> on each directory.
353
354    [% VIEW myview %]
355    [% BLOCK file %]
356       - [% item.name %]
357    [% END %]
358
359    [% BLOCK directory %]
360       * [% item.name %]
361         [% item.scan %]
362         [% item.content(myview) FILTER indent %]
363    [% END %]
364    [% END %]
365
366    [% USE dir = Directory(dirpath) %]
367    [% myview.print(dir) %]
368
369=head1 AUTHORS
370
371Michael Stevens wrote the original Directory plugin on which this is based.
372Andy Wardley split it into separate L<File|Template::Plugin::File> and
373L<Directory|Template::Plugin::Directory> plugins, added some extra code and
374documentation for C<VIEW> support, and made a few other minor tweaks.
375
376=head1 COPYRIGHT
377
378Copyright (C) 2000-2007 Michael Stevens, Andy Wardley.
379
380This module is free software; you can redistribute it and/or
381modify it under the same terms as Perl itself.
382
383=head1 SEE ALSO
384
385L<Template::Plugin>, L<Template::Plugin::File>, L<Template::View>
386
387