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