File Coverage

blib/lib/Text/Repository.pm
Criterion Covered Total %
statement 74 104 71.1
branch 9 20 45.0
condition 3 7 42.8
subroutine 16 20 80.0
pod 0 1 0.0
total 102 152 67.1


line stmt bran cond sub pod time code
1             package Text::Repository;
2              
3             #----------------------------------------------------------------------
4             # $Id: Repository.pm,v 1.4 2002/01/18 14:25:11 dlc Exp $
5             #----------------------------------------------------------------------
6             # Text::Repository - A simple way to store and retrieve text
7             # Copyright (C) 2002 darren chamberlain
8             #
9             # This program is free software; you can redistribute it and/or
10             # modify it under the terms of the GNU General Public License as
11             # published by the Free Software Foundation; version 2.
12             #
13             # This program is distributed in the hope that it will be useful, but
14             # WITHOUT ANY WARRANTY; without even the implied warranty of
15             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16             # General Public License for more details.
17             #
18             # You should have received a copy of the GNU General Public License
19             # along with this program; if not, write to the Free Software
20             # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21             # 02111-1307 USA
22             #----------------------------------------------------------------------
23              
24 4     4   138739 use strict;
  4         10  
  4         183  
25 4     4   23 use vars qw($VERSION);
  4         9  
  4         284  
26 4         31 use subs qw(new add_path add_paths paths remove_path replace_paths
27 4     4   4709 reset fetch cached cache clear_cache);
  4         4175  
28              
29             $VERSION = sprintf "%d.%02d", q$Revision: 1.4 $ =~ /(\d+)\.(\d+)/;
30              
31 4     4   1501 use File::Spec;
  4         9  
  4         126  
32 4     4   8507 use IO::File;
  4         115465  
  4         1050  
33 4     4   48 use Carp;
  4         10  
  4         327  
34              
35             *isa = \&UNIVERSAL::isa;
36 4     4   29 use constant CACHE => 0;
  4         19  
  4         350  
37 4     4   23 use constant PATHS => 1;
  4         10  
  4         186  
38 4     4   23 use constant ORIGINAL => 2;
  4         8  
  4         18183  
39              
40             =head1 NAME
41              
42             Text::Repository - A simple way to manage text without mixing it with Perl
43              
44             =head1 ABSTRACT
45              
46             Text::Repository attempts to simplify storing shared text between
47             multple Perl modules, scripts, templating systems, etc. It does this
48             by allowing chunks of text to be stored with symbolic names.
49             Text::Repository was originally designed to store SQL queries, but can
50             of course be used with any kind of text that needs to be shared.
51              
52             =head1 SYNOPSIS
53              
54             use Text::Repository;
55              
56             my @paths = ("/www/library", "$ENV{'HOME'}/text");
57             my $rep = Text::Repository->new(@paths);
58              
59             (See EXAMPLES for more.)
60              
61             =head1 DESCRIPTION
62              
63             Text::Repository provides the capability to store, use, and manage
64             text without having to mix them with Perl scripts and modules. These
65             pieces of text can then be shared by multiple modules, scripts, or
66             templating systems with a minimum of fuss.
67              
68             Text::Repository uses a series of one or more directories (specified
69             either when the class is instantiated or when needed) as a search
70             path; when a piece of text is requested using the instance's B
71             method, Text::Repository looks in each of the directories in turn
72             until it finds a file with that name. If the file is found, it is
73             opened and read, and the contents are returned to the caller as a
74             string. Furthermore, the contents of the file are cached. Successive
75             calls to B to retrieve the same piece of text return this
76             cached copy, provided the copy on disk has not changed more recently
77             than the copy in the cache.
78              
79             Text::Repository was originally written to share complex SQL queries
80             among multiple modules; when the usage grew to include printf formats,
81             I realized it could be generalized to store any kind of text. Because
82             no processing is done on the text before it is returned, the text in
83             the file can have any kind of markup. In fact, the contents of the
84             file don't even have to be text; the caller decides how to use the
85             results returned from the B.
86              
87             =head1 CONSTRUCTOR
88              
89             The constructor is called B, and can be optionally passed a list
90             of directories to be added to the search path (directories can also be
91             added using the B object method).
92              
93             =cut
94              
95             #
96             # Instantiates a new instance. There is very little setup here;
97             # all the work (adding paths, etc) is handled by add_path.
98             #
99             sub new {
100 3     3   28671 my $class = shift;
101 3         44 my $self = bless [ { }, [ ], \@_, ] => $class;
102 3         50 $self->add_path(@_);
103              
104 3         20 return $self;
105             }
106              
107             =head1 INSTANCE METHODS
108              
109             =head2 B
110              
111             Adds a search path or paths to the instance. The search path defines
112             where the instance looks for text snippets. This can be called
113             multiple times, and this module imposes no limits on the number of
114             search paths.
115              
116             B is an alias for B, and should be used wherever
117             it makes the intent clearer. For example, use B to add a
118             single path, but B when assigning more than one:
119              
120             $rep->add_paths($new_path);
121              
122             $rep->add_paths(@new_paths);
123              
124             Some steps are taken to ensure that a path only appears in the search
125             path once; any subsequent additions of an existing path are ignored.
126              
127             =cut
128              
129             #
130             # add_path pushes one or more paths onto the object; these are
131             # searched when fetch is called. Should add_path check -d first?
132             #
133             sub add_path {
134 6     6   3136 my $self = shift;
135 6 100       73 my $paths = isa($_[0], "ARRAY") ? shift : \@_;
136 6         15 my %paths;
137              
138 6         32 @{$self->[PATHS]} =
  16         637  
139 17         69 grep { -d }
140 6         41 grep { ++$paths{$_} == 1 }
141 6         30 (@{$self->[PATHS]}, @{$paths});
  6         29  
142              
143 6         36 return $self;
144             }
145             *add_paths = *add_path;
146              
147             =head2 B
148              
149             The paths method returns a list of the paths in the object (or a
150             reference to a list of the paths if called in scalar context).
151              
152             =cut
153              
154             sub paths {
155 9     9   33 my $self = shift;
156 9 50       29 return @{$self->[PATHS]} if wantarray;
  9         42  
157 0         0 return [ @{$self->[PATHS]} ];
  0         0  
158             }
159              
160             =head2 B
161            
162             remove_path deletes a path from the instance's search path.
163              
164             =cut
165              
166             sub remove_path {
167 1     1   1320 my $self = shift;
168 1 50       10 my %paths = map { $_ => 1 } isa($_[0], "ARRAY") ? @{shift()} : @_;
  1         6  
  0         0  
169              
170 1         2 @{$self->[PATHS]} = grep { not defined $paths{$_} } @{$self->[PATHS]};
  1         3  
  3         10  
  1         32  
171              
172 1         4 return $self;
173             }
174              
175             =head2 B
176              
177             B provides a shortcut to reset the list of paths to a
178             new value. It is equivalent to:
179              
180             for my $p ($rep->paths()) {
181             $rep->remove_path($p);
182             }
183             $rep->clear_cache();
184             $rep->add_paths(@new_paths);
185              
186             B returns the Text::Repository instance.
187              
188             =cut
189              
190             sub replace_paths {
191 0     0   0 my $self = shift;
192              
193 0         0 for my $p ($self->paths) {
194 0         0 $self->remove_path($p);
195             }
196              
197 0         0 $self->clear_cache->add_paths(@_);
198              
199 0         0 return $self;
200             }
201              
202             =head2 B
203              
204             The B method returns the instance to the state it had when it
205             was created. B returns the Text::Repository instance.
206              
207             =cut
208              
209             sub reset {
210 0     0   0 my $self = shift;
211              
212 0         0 $self->replace_paths($self->original_paths);
213              
214 0         0 return $self;
215             }
216              
217             sub original_paths {
218 0     0 0 0 my $self = shift;
219 0         0 my @orig = @{$self->[ORIGINAL]};
  0         0  
220 0 0       0 return wantarray ? @orig : \@orig;
221             }
222              
223             =head2 B
224              
225             The B method does the actual fetching of the text.
226              
227             B is designed to be called with a keyword; this keyword
228             is turned into a filename that gets appended to each directory in
229             paths (as defined by $self->paths) in order until it finds a match.
230              
231             Once fetch finds a match, the contents of the file is returned as a
232             single string.
233              
234             If the file is not found, B returns undef.
235              
236             =cut
237              
238             sub fetch {
239 5     5   10267 my $self = shift;
240 5   50     22 my $text = shift || return;
241 5         6 my ($fh, $filename);
242              
243             #
244             # Check that $text doesn't begin with "../" or "/";
245             # relative paths only
246             #
247 5         32 $text =~ s:^[./]*::;
248              
249 5         18 for my $path ($self->paths) {
250 5         97 $filename = File::Spec->catfile($path, $text);
251              
252             # The caching mechanism
253 5 50       23 if (my $cached = $self->cached($filename)) {
254 0         0 return $cached;
255             }
256              
257 5 50 33     202 unless (-e $filename && -r _) {
258 0         0 $filename = "";
259 0         0 next;
260             }
261            
262 5 50       36 unless ($fh = IO::File->new($filename)) {
263 0         0 carp "Can't open '$filename'";
264 0         0 $filename = "";
265 0         0 next;
266             } else {
267 5         500 local $/ = undef;
268 5         219 my $content = $fh->getline;
269 5         303 return $self->cache($filename, \$content);
270             }
271             }
272             }
273              
274             sub cached {
275 5     5   9 my $self = shift;
276 5   50     19 my $filename = shift || return;
277              
278 5 50       18 if (defined $self->[CACHE]->{$filename}) {
279 0 0       0 if (-M $filename > $self->[CACHE]->{$filename}->{'timestamp'}) {
280 0         0 delete $self->[CACHE]->{$filename};
281 0         0 return;
282             } else {
283 0         0 return ${$self->[CACHE]->{$filename}->{'content'}};
  0         0  
284             }
285             }
286              
287 5         97 return;
288             }
289              
290             sub cache {
291 5     5   12 my ($self, $filename, $content) = @_;
292 5         5 my $cref;
293              
294 5 50       19 if (ref $content eq 'SCALAR') {
295 5         9 $cref = $content;
296             } else {
297 0         0 $cref = \$content;
298             }
299              
300 5         167 $self->[CACHE]->{$filename} = {
301             timestamp => -M $filename,
302             content => $cref,
303             };
304              
305 5         50 return $$cref;
306             }
307              
308             =head2 B
309              
310             The B method clears out the internal cache. The only
311             times this becomes necessary to call is when the internal paths are
312             changed to the point where cached files will never be found again
313             (they become orphaned, in this case). Note that B
314             calls this method for you.
315              
316             This method returns the Text::Repository instance, for chaining.
317              
318             =cut
319              
320             sub clear_cache {
321 0     0     my $self = shift;
322 0           $self->[CACHE] = { };
323 0           return $self;
324             }
325              
326             1;
327             __END__