1#============================================================= -*-Perl-*-
2#
3# Template::Plugin::File
4#
5# DESCRIPTION
6#  Plugin for encapsulating information about a system file.
7#
8# AUTHOR
9#   Originally written by Michael Stevens <michael@etla.org> as the
10#   Directory plugin, then mutilated by Andy Wardley <abw@kfs.org>
11#   into separate File and Directory plugins, with some additional
12#   code for working with views, etc.
13#
14# COPYRIGHT
15#   Copyright 2000-2007 Michael Stevens, Andy Wardley.
16#
17#   This module is free software; you can redistribute it and/or
18#   modify it under the same terms as Perl itself.
19#
20#============================================================================
21
22package Template::Plugin::File;
23
24use strict;
25use warnings;
26use Cwd;
27use File::Spec;
28use File::Basename;
29use base 'Template::Plugin';
30
31our $VERSION = 2.71;
32
33our @STAT_KEYS = qw( dev ino mode nlink uid gid rdev size
34                     atime mtime ctime blksize blocks );
35
36
37#------------------------------------------------------------------------
38# new($context, $file, \%config)
39#
40# Create a new File object.  Takes the pathname of the file as
41# the argument following the context and an optional
42# hash reference of configuration parameters.
43#------------------------------------------------------------------------
44
45sub new {
46    my $config = ref($_[-1]) eq 'HASH' ? pop(@_) : { };
47    my ($class, $context, $path) = @_;
48    my ($root, $home, @stat, $abs);
49
50    return $class->throw('no file specified')
51        unless defined $path and length $path;
52
53    # path, dir, name, root, home
54
55    if (File::Spec->file_name_is_absolute($path)) {
56        $root = '';
57    }
58    elsif (($root = $config->{ root })) {
59        # strip any trailing '/' from root
60        $root =~ s[/$][];
61    }
62    else {
63        $root = '';
64    }
65
66    my ($name, $dir, $ext) = fileparse($path, '\.\w+');
67    # fixup various items
68    $dir  =~ s[/$][];
69    $dir  = '' if $dir eq '.';
70    $name = $name . $ext;
71    $ext  =~ s/^\.//g;
72
73    my @fields = File::Spec->splitdir($dir);
74    shift @fields if @fields && ! length $fields[0];
75    $home = join('/', ('..') x @fields);
76    $abs = File::Spec->catfile($root ? $root : (), $path);
77
78    my $self = {
79        path  => $path,
80        name  => $name,
81        root  => $root,
82        home  => $home,
83        dir   => $dir,
84        ext   => $ext,
85        abs   => $abs,
86        user  => '',
87        group => '',
88        isdir => '',
89        stat  => defined $config->{ stat }
90                       ? $config->{ stat }
91                       : ! $config->{ nostat },
92        map { ($_ => '') } @STAT_KEYS,
93    };
94
95    if ($self->{ stat }) {
96        (@stat = stat( $abs ))
97            || return $class->throw("$abs: $!");
98
99        @$self{ @STAT_KEYS } = @stat;
100
101        unless ($config->{ noid }) {
102            $self->{ user  } = eval { getpwuid( $self->{ uid }) || $self->{ uid } };
103            $self->{ group } = eval { getgrgid( $self->{ gid }) || $self->{ gid } };
104        }
105        $self->{ isdir } = -d $abs;
106    }
107
108    bless $self, $class;
109}
110
111
112#-------------------------------------------------------------------------
113# rel($file)
114#
115# Generate a relative filename for some other file relative to this one.
116#------------------------------------------------------------------------
117
118sub rel {
119    my ($self, $path) = @_;
120    $path = $path->{ path } if ref $path eq ref $self;  # assumes same root
121    return $path if $path =~ m[^/];
122    return $path unless $self->{ home };
123    return $self->{ home } . '/' . $path;
124}
125
126
127#------------------------------------------------------------------------
128# present($view)
129#
130# Present self to a Template::View.
131#------------------------------------------------------------------------
132
133sub present {
134    my ($self, $view) = @_;
135    $view->view_file($self);
136}
137
138
139sub throw {
140    my ($self, $error) = @_;
141    die (Template::Exception->new('File', $error));
142}
143
1441;
145
146__END__
147
148=head1 NAME
149
150Template::Plugin::File - Plugin providing information about files
151
152=head1 SYNOPSIS
153
154    [% USE File(filepath) %]
155    [% File.path %]         # full path
156    [% File.name %]         # filename
157    [% File.dir %]          # directory
158
159=head1 DESCRIPTION
160
161This plugin provides an abstraction of a file.  It can be used to
162fetch details about files from the file system, or to represent abstract
163files (e.g. when creating an index page) that may or may not exist on
164a file system.
165
166A file name or path should be specified as a constructor argument.  e.g.
167
168    [% USE File('foo.html') %]
169    [% USE File('foo/bar/baz.html') %]
170    [% USE File('/foo/bar/baz.html') %]
171
172The file should exist on the current file system (unless C<nostat>
173option set, see below) as an absolute file when specified with as
174leading 'C</>' as per 'C</foo/bar/baz.html>', or otherwise as one relative
175to the current working directory.  The constructor performs a C<stat()>
176on the file and makes the 13 elements returned available as the plugin
177items:
178
179    dev ino mode nlink uid gid rdev size
180    atime mtime ctime blksize blocks
181
182e.g.
183
184    [% USE File('/foo/bar/baz.html') %]
185
186    [% File.mtime %]
187    [% File.mode %]
188    ...
189
190In addition, the C<user> and C<group> items are set to contain the user
191and group names as returned by calls to C<getpwuid()> and C<getgrgid()> for
192the file C<uid> and C<gid> elements, respectively.  On Win32 platforms
193on which C<getpwuid()> and C<getgrid()> are not available, these values are
194undefined.
195
196    [% USE File('/tmp/foo.html') %]
197    [% File.uid %]      # e.g. 500
198    [% File.user %]     # e.g. abw
199
200This user/group lookup can be disabled by setting the C<noid> option.
201
202    [% USE File('/tmp/foo.html', noid=1) %]
203    [% File.uid %]      # e.g. 500
204    [% File.user %]     # nothing
205
206The C<isdir> flag will be set if the file is a directory.
207
208    [% USE File('/tmp') %]
209    [% File.isdir %]    # 1
210
211If the C<stat()> on the file fails (e.g. file doesn't exists, bad
212permission, etc) then the constructor will throw a C<File> exception.
213This can be caught within a C<TRY...CATCH> block.
214
215    [% TRY %]
216       [% USE File('/tmp/myfile') %]
217       File exists!
218    [% CATCH File %]
219       File error: [% error.info %]
220    [% END %]
221
222Note the capitalisation of the exception type, 'C<File>', to indicate an
223error thrown by the C<File> plugin, to distinguish it from a regular
224C<file> exception thrown by the Template Toolkit.
225
226Note that the C<File> plugin can also be referenced by the lower case
227name 'C<file>'.  However, exceptions are always thrown of the C<File>
228type, regardless of the capitalisation of the plugin named used.
229
230    [% USE file('foo.html') %]
231    [% file.mtime %]
232
233As with any other Template Toolkit plugin, an alternate name can be
234specified for the object created.
235
236    [% USE foo = file('foo.html') %]
237    [% foo.mtime %]
238
239The C<nostat> option can be specified to prevent the plugin constructor
240from performing a C<stat()> on the file specified.  In this case, the
241file does not have to exist in the file system, no attempt will be made
242to verify that it does, and no error will be thrown if it doesn't.
243The entries for the items usually returned by C<stat()> will be set
244empty.
245
246    [% USE file('/some/where/over/the/rainbow.html', nostat=1)
247    [% file.mtime %]     # nothing
248
249=head1 METHODS
250
251All C<File> plugins, regardless of the C<nostat> option, have set a number
252of items relating to the original path specified.
253
254=head2 path
255
256The full, original file path specified to the constructor.
257
258    [% USE file('/foo/bar.html') %]
259    [% file.path %]     # /foo/bar.html
260
261=head2 name
262
263The name of the file without any leading directories.
264
265    [% USE file('/foo/bar.html') %]
266    [% file.name %]     # bar.html
267
268=head2 dir
269
270The directory element of the path with the filename removed.
271
272    [% USE file('/foo/bar.html') %]
273    [% file.name %]     # /foo
274
275=head2 ext
276
277The file extension, if any, appearing at the end of the path following
278a 'C<.>' (not included in the extension).
279
280    [% USE file('/foo/bar.html') %]
281    [% file.ext %]      # html
282
283=head2 home
284
285This contains a string of the form 'C<../..>' to represent the upward path
286from a file to its root directory.
287
288    [% USE file('bar.html') %]
289    [% file.home %]     # nothing
290
291    [% USE file('foo/bar.html') %]
292    [% file.home %]     # ..
293
294    [% USE file('foo/bar/baz.html') %]
295    [% file.home %]     # ../..
296
297=head2 root
298
299The C<root> item can be specified as a constructor argument, indicating
300a root directory in which the named file resides.  This is otherwise
301set empty.
302
303    [% USE file('foo/bar.html', root='/tmp') %]
304    [% file.root %]     # /tmp
305
306=head2 abs
307
308This returns the absolute file path by constructing a path from the
309C<root> and C<path> options.
310
311    [% USE file('foo/bar.html', root='/tmp') %]
312    [% file.path %]     # foo/bar.html
313    [% file.root %]     # /tmp
314    [% file.abs %]      # /tmp/foo/bar.html
315
316=head2 rel(path)
317
318This returns a relative path from the current file to another path specified
319as an argument.  It is constructed by appending the path to the 'C<home>'
320item.
321
322    [% USE file('foo/bar/baz.html') %]
323    [% file.rel('wiz/waz.html') %]      # ../../wiz/waz.html
324
325=head1 EXAMPLES
326
327    [% USE file('/foo/bar/baz.html') %]
328
329    [% file.path  %]      # /foo/bar/baz.html
330    [% file.dir   %]      # /foo/bar
331    [% file.name  %]      # baz.html
332    [% file.home  %]      # ../..
333    [% file.root  %]      # ''
334    [% file.abs   %]      # /foo/bar/baz.html
335    [% file.ext   %]      # html
336    [% file.mtime %]      # 987654321
337    [% file.atime %]      # 987654321
338    [% file.uid   %]      # 500
339    [% file.user  %]      # abw
340
341    [% USE file('foo.html') %]
342
343    [% file.path %]           # foo.html
344    [% file.dir  %]       # ''
345    [% file.name %]           # foo.html
346    [% file.root %]       # ''
347    [% file.home %]       # ''
348    [% file.abs  %]       # foo.html
349
350    [% USE file('foo/bar/baz.html') %]
351
352    [% file.path %]           # foo/bar/baz.html
353    [% file.dir  %]       # foo/bar
354    [% file.name %]           # baz.html
355    [% file.root %]       # ''
356    [% file.home %]       # ../..
357    [% file.abs  %]       # foo/bar/baz.html
358
359    [% USE file('foo/bar/baz.html', root='/tmp') %]
360
361    [% file.path %]           # foo/bar/baz.html
362    [% file.dir  %]       # foo/bar
363    [% file.name %]           # baz.html
364    [% file.root %]       # /tmp
365    [% file.home %]       # ../..
366    [% file.abs  %]       # /tmp/foo/bar/baz.html
367
368    # calculate other file paths relative to this file and its root
369    [% USE file('foo/bar/baz.html', root => '/tmp/tt2') %]
370
371    [% file.path('baz/qux.html') %]         # ../../baz/qux.html
372    [% file.dir('wiz/woz.html')  %]     # ../../wiz/woz.html
373
374=head1 AUTHORS
375
376Michael Stevens wrote the original C<Directory> plugin on which this is based.
377Andy Wardley split it into separate C<File> and C<Directory> plugins, added
378some extra code and documentation for C<VIEW> support, and made a few other
379minor tweaks.
380
381=head1 COPYRIGHT
382
383Copyright 2000-2007 Michael Stevens, Andy Wardley.
384
385This module is free software; you can redistribute it and/or
386modify it under the same terms as Perl itself.
387
388=head1 SEE ALSO
389
390L<Template::Plugin>, L<Template::Plugin::Directory>, L<Template::View>
391
392