File Coverage

blib/lib/Tree/File.pm
Criterion Covered Total %
statement 181 191 94.7
branch 87 104 83.6
condition 9 11 81.8
subroutine 34 35 97.1
pod 18 18 100.0
total 329 359 91.6


line stmt bran cond sub pod time code
1             package Tree::File;
2              
3 17     17   71309 use warnings;
  17         38  
  17         449  
4 17     17   77 use strict;
  17         40  
  17         517  
5              
6 17     17   100 use Carp qw(croak);
  17         37  
  17         1075  
7 17     17   94 use File::Path ();
  17         27  
  17         38590  
8              
9             =head1 NAME
10              
11             Tree::File - (DEPRECATED) store a data structure in a file tree
12              
13             =head1 VERSION
14              
15             version 0.112
16              
17             =cut
18              
19             our $VERSION = '0.112';
20              
21             =head1 SYNOPSIS
22              
23             use Tree::File::Subclass;
24              
25             my $tree = Tree::File::Subclass->new($treerot);
26              
27             die "death mandated" if $tree->get("/master/die")
28              
29             print "Hello, ", $tree->get("/login/user/name");
30              
31             $tree->set("/login/user/lastlogin", time);
32             $tree->write;
33              
34             =head1 DESCRIPTION
35              
36             This module stores configuration in a series of files spread across a directory
37             tree, and provides uniform access to the data structure.
38              
39             It can load a single file or a directory tree containing files as leaves. The
40             tree's branches can be returned as data structures, and the tree can be
41             modified and rewritten. Directory-based branches can be collapsed back into
42             files and file-based branches can be exploded into directories.
43              
44             =head1 METHODS
45              
46             =head2 C<< Tree::File->new($treeroot, \%arg) >>
47              
48             This loads the tree at the named root, which may be a file or a directory. The
49             C<%arg> hash is optional, the following options are recognized:
50              
51             readonly - if true, set and delete methods croak (default: false)
52             preload - the number of levels of directories to preload (default: none)
53             pass -1 to preload as deep as required
54             found - a closure called when a node or value is found; it is passed the
55             Tree::File object, the id requested, and the data retrieved; it
56             should apply any transformations and return the 'real' value desired.
57             not_found - a closure called if a node cannot be found; it is passed the id
58             requested and the root of the last node reached; by default,
59             Tree::File will return undef in this situation
60              
61             =cut
62              
63             sub new {
64 24     24 1 7512 my ($class, $root, $arg) = @_;
65              
66 24         165 $arg->{lock_mgr} = bless { root => $root } => "Tree::File::LockManager";
67              
68 24         281 my $self = $class->_load(q{}, $arg->{preload}, {%$arg, basedir => $root});
69              
70 22         132 return $self;
71             }
72              
73             sub _as_arg {
74 11     11   17 my $self = shift;
75             return {
76 11         18 map { $_ => $self->{$_} } qw(basedir lock_mgr readonly found not_found)
  55         148  
77             };
78             }
79              
80             sub _new_node {
81 225     225   375 my ($self, $root, $data, $arg) = @_;
82 225 100       445 my $class = ref $self ? ref $self : $self;
83              
84 225 100 100     545 if (ref $self and not $arg) {
85 11         43 $arg = $self->_as_arg;
86             }
87              
88 225 100       994 return $data if ref $data ne 'HASH';
89              
90 136         544 my $processed_data = {
91 75         241 map { $_ => $self->_new_node("$root/$_", $data->{$_}, $arg) }
92             keys %$data
93             };
94              
95 75         1300 bless {
96             root => $root,
97             data => $processed_data,
98             %$arg
99             } => $class;
100             }
101              
102             =head2 C<< $tree->load_file($filename) >>
103              
104             This method is used internally by Tree::File subclasses, which must implement
105             it. Given the name of a file on disk, this method returns the data structure
106             contained in the file.
107              
108             =cut
109              
110 1     1 1 210 sub load_file { croak "load_file method unimplemented" }
111              
112             sub _load {
113 80     80   193 my ($self, $root, $preload, $arg) = @_;
114 80         152 my $lock_mgr = $arg->{lock_mgr};
115              
116 80         118 eval { $lock_mgr->lock() };
  80         264  
117 80 50       191 if ($@) {
118 0 0       0 if ($@ =~ /couldn.t open lockfile/i) {
119 0         0 $arg->{readonly}++;
120 0         0 $lock_mgr->no_op;
121             } else {
122 0         0 die; ## no critic Carping
123             }
124             }
125              
126 80 100       294 my $file = $root ? "$arg->{basedir}/$root" : $arg->{basedir};
127              
128 80 100       1957 if (-f $file) {
    100          
129 43         212 my $data = $self->load_file($file);
130 42         159 $lock_mgr->unlock();
131 42         329 return $self->_new_node($root, $data, \%$arg);
132             }
133              
134             elsif (-d $file) {
135 36         46 my $dir;
136 36 50       1340 opendir $dir, $file or croak "can't open branch directory $dir: $!";
137              
138 36         71 my $tree = {};
139 36 100 100     889 for my $twig (grep { $_ !~ /\A\./ && ! -l "$file/$_" && $_ ne 'CVS' } readdir $dir) {
  145         1925  
140             $tree->{$twig} = $preload
141             ? $self->_load("$root/$twig", $preload-1, { %$arg, preload => $preload-1})
142 71 100   49   494 : sub { $self->_load("$root/$twig", 0, { %$arg, preload => 0 }) };
  49         321  
143             }
144 36         133 $lock_mgr->unlock();
145 36         365 return $self->_new_node($root, $tree, { %$arg, type => 'dir' });
146             }
147              
148             else {
149 1         6 $lock_mgr->unlock();
150 1         237 croak "$file doesn't exist or isn't a normal file or directory";
151             }
152             }
153              
154             =head2 C<< $tree->get($id) >>
155              
156             This returns the branch with the given name. If the name contains slashes,
157             they indicate recursive fetches, so that these two calls are identical:
158              
159             $tree->get("foo")->get("bar")->get("baz");
160              
161             $tree->get("foo/bar/baz");
162              
163             Leading slashes are ignored.
164              
165             If a second, true argument is passed to C, any missing data structures
166             will be autovivified as needed to get to the leaf.
167              
168             =cut
169              
170             sub _not_found {
171 11     11   19 my ($self) = shift;
172 11 100       29 if ($self->{not_found}) { return $self->{not_found}->(@_) }
  2         8  
173 9         46 return;
174             }
175              
176             sub _found {
177 324     324   419 my ($self) = shift;
178 324 50       1221 return $self->{found} ? $self->{found}->($self, @_) : $_[1];
179             }
180              
181             sub get {
182 365     365 1 5829 my ($self, $id, $autovivify) = @_;
183              
184 365 100       840 $id && $id =~ s|\A/+||;
185 365         397 my $rest;
186              
187 365 100       883 croak "get called on $self without property identifier" unless defined $id;
188              
189 364         863 ($id, $rest) = split m|/|, $id, 2;
190 364 100       707 if ($rest) {
191 27         95 my $head = $self->get($id, $autovivify);
192 27 100       76 return $self->_not_found($id, $self->{root}) unless $head;
193 24         62 return $head->get($rest, $autovivify);
194             }
195              
196 337 100       865 if (exists $self->{data}{$id}) {
197 324 100       3673 if (ref $self->{data}{$id} eq 'CODE') {
198 49         119 $self->{data}{$id} = $self->{data}{$id}->();
199             }
200 324         1297 return $self->_found($id, $self->{data}{$id});
201             }
202              
203 13 100       34 if ($autovivify) {
204 5         20 return $self->{data}{$id} =
205             $self->_new_node("$self->{root}/$id", {});
206             }
207              
208 8         64 return $self->_not_found($id, $self->{root});
209             }
210              
211             =head2 C<< $tree->set($id, $value) >>
212              
213             This sets the identified branch's value to the given value. Hash references
214             are automatically expanded into trees.
215              
216             =cut
217              
218             sub set { ## no critic Ambiguous
219 18     18 1 1759 my ($self, $id, $value, $root) = @_;
220              
221 18 100       25 $value = $value->data if eval { $value->isa("Tree::File") };
  18         123  
222              
223 18 100       288 croak "set called on readonly tree" if $self->{readonly};
224              
225 16 100       46 $id && $id =~ s|\A/+||;
226 16 100       35 $root = $id unless $root;
227 16         17 my $rest;
228              
229 16 100       111 croak "set called on $self without property identifier" unless defined $id;
230              
231 15         42 ($id, $rest) = split m|/|, $id, 2;
232 15 100       33 if ($rest) { return $self->get($id, 1)->set($rest, $value, $root); }
  9         24  
233              
234 6         16 return $self->{data}{$id} =
235             $self->_new_node($root, $value);
236             }
237              
238             =head2 C<< $tree->delete($id) >>
239              
240             This method deletes the identified branch (and returns the deleted value).
241              
242             =cut
243              
244             sub delete { ## no critic Homonym
245 10     10 1 1541 my ($self, $id) = @_;
246              
247 10 100       197 croak "delete called on readonly tree" if $self->{readonly};
248              
249 8 100       21 $id && $id =~ s|\A/+||;
250 8         11 my $rest;
251              
252 8 100       95 croak "delete called on $self without property identifier" unless defined $id;
253              
254 7         22 ($id, $rest) = split m|/|, $id, 2;
255 7 100       15 if ($rest) { return $self->get($id)->delete($rest); }
  4         12  
256              
257 3         18 return delete $self->{data}{$id};
258             }
259              
260             =head2 C<< $tree->move($old_id, $new_id) >>
261              
262             This method deletes the value at the old id and places it at the new id.
263              
264             =cut
265              
266             sub move {
267 2     2 1 37 my ($self, $old_id, $new_id) = @_;
268              
269 2         10 $self->set($new_id, $self->delete($old_id));
270             }
271              
272             =head2 C<< $tree->path() >>
273              
274             This method returns the path to this node from the root.
275              
276             =cut
277              
278             sub path {
279 2     2 1 3 my ($self) = @_;
280 2         10 return $self->{root};
281             }
282              
283             =head2 C<< $tree->basename() >>
284              
285             This method retuns the base name of the node. (If, for example, the path to
286             the node is "/things/good/all" then its base name is "all".)
287              
288             =cut
289              
290             sub basename {
291 1     1 1 2 my ($self) = @_;
292 1         5 my @parts = split m{/}, $self->path();
293 1         8 return $parts[-1];
294             }
295              
296             sub _handoff {
297 1     1   3 my $self = shift;
298 1         8 my $method = (caller(1))[3];
299 1         7 $method =~ s/.*:://;
300 1         3 my $node = $self->get(@_);
301 1 50       5 unless ($node) {
302 0         0 return $self->_not_found(@_);
303             }
304             #warn "handing off $method to " . $node->path . "\n";
305 1         6 $node->$method;
306             }
307              
308             =head2 C<< $tree->node_names() >>
309              
310             This method returns the names of all the nodes beneath this branch.
311              
312             =cut
313              
314             sub node_names {
315 140     140 1 1428 my $self = shift;
316 140 100       436 return $self->_handoff(@_) if @_;
317 139         149 return sort keys %{$self->{data}};
  139         755  
318             }
319              
320             =head2 C<< $tree->nodes() >>
321              
322             This method returns each node beneath this branch.
323              
324             =cut
325              
326             sub nodes {
327 1     1 1 3 my $self = shift;
328 1 50       3 return $self->_handoff(@_) if @_;
329 1         4 return map { $self->get($_) } $self->node_names();
  3         7  
330             }
331              
332             =head2 C<< $tree->branch_names >>
333              
334             =cut
335              
336             sub branch_names {
337 2     2 1 1815 my $self = shift;
338 2 50       6 return $self->_handoff(@_) if @_;
339 2         5 return grep { eval { $self->get($_)->isa("Tree::File") } } $self->node_names;
  6         8  
  6         10  
340             }
341              
342             =head2 C<< $tree->branches >>
343              
344             This method returns all the nodes on this branch which are also branches (that
345             is, are also Tree::File objects).
346              
347             =cut
348              
349             sub branches {
350 1     1 1 3 my $self = shift;
351 1 50       5 return $self->_handoff(@_) if @_;
352 1         9 return map { $self->get($_) } $self->branch_names();
  2         4  
353             }
354              
355             =head2 C<< $tree->data() >>
356              
357             This method returns the entire tree of data as an unblessed Perl data
358             structure.
359              
360             =cut
361              
362             sub data {
363 123     123 1 6284 my ($self) = @_;
364 123         132 my %data;
365              
366 123         246 for ($self->node_names) {
367 238         539 my $datum = $self->get($_);
368              
369 238 100       301 $data{$_} = eval { $datum->isa("Tree::File") } ? $datum->data
  238         1719  
370             : $datum;
371             }
372              
373 123         487 return \%data;
374             }
375              
376             =head2 C<< $tree->write($basedir) >>
377              
378             This method forces the object to write itself out to disk. It will write out
379             branches to directories if a directory for the branch already exists, or if it
380             was orginally loaded as a directory.
381              
382             =cut
383              
384             sub write { ## no critic Homonym
385 20     20 1 2946 my $self = shift;
386 20   66     67 my $basedir = shift || $self->{basedir};
387 20 50       75 my $root = $basedir ? "$basedir/$self->{root}" : $self->{root};
388 20         34 my $lock_mgr = $self->{lock_mgr};
389              
390 20         50 $self->data; # force load of all data now
391              
392 20   50     78 my $type = $self->type
393             || (-d $root && 'dir')
394             || 'file';
395              
396 20         56 $lock_mgr->lock();
397              
398 20 100       165 if ($type eq 'dir') {
399 10 100       2315 File::Path::rmtree($root) if -d $root;
400 10         1860 File::Path::mkpath($root);
401 10         31 for ($self->node_names) {
402 19         12111 my $datum = $self->get($_);
403 19 100       34 if (eval { $datum->isa("Tree::File") }) { $datum->write($basedir) }
  19         116  
  13         51  
404 6         34 else { $self->write_file("$root/$_", $datum) }
405             }
406             } else {
407 10 100       1429 File::Path::rmtree($root) if -d $root;
408 10         28 $self->write_file($root, $self->data);
409             }
410              
411 20         51462 $lock_mgr->unlock();
412              
413 20         71 1;
414             }
415              
416             =head2 C<< $tree->write_file($filename) >>
417              
418             This method is used by Tree::File's C method. It must be implement in
419             subclasses of Tree::File. Given the name of a file on disk and a data
420             structure, this method writes the data structure to the file.
421              
422             =cut
423              
424 1     1 1 1244 sub write_file { croak "write_file method unimplemented" }
425              
426             =head2 C<< $tree->type($type) >>
427              
428             This method returns the branch type for the given branch. If C<$type> is
429             defined and one of "dir" or "file" it will set the type and return the new
430             value.
431              
432             =cut
433              
434             sub type {
435 29     29 1 1312 my $self = shift;
436 29 100       346 return $self->{type} unless @_;
437              
438 7         11 my $type = shift;
439 7 100       25 return $self->{type} = undef unless defined $type;
440              
441 6 100       104 croak "invalid branch type: $type" unless $type =~ /\A(?:dir|file)\Z/;
442              
443 5         29 $self->{type} = $type;
444             }
445              
446             =head2 C<< $tree->explode() >>
447              
448             =head2 C<< $tree->collapse() >>
449              
450             These methods set the type of the branch to "dir" and "file" respectively.
451              
452             =cut
453              
454 1     1 1 8 sub explode { (shift)->type("dir") }
455 2     2 1 14 sub collapse { (shift)->type("file") }
456              
457             package Tree::File::LockManager;
458              
459 17     17   144 use Carp ();
  17         41  
  17         372  
460 17     17   97 use Fcntl qw(:DEFAULT :flock);
  17         32  
  17         9529  
461 17     17   249 use File::Basename ();
  17         34  
  17         5696  
462              
463             sub lock {
464 100     100   169 my ($self, $tree) = @_;
465 100 50       341 return if $self->{_no_op};
466              
467 100 100       275 unless ($self->{_lockfile}) {
468 24         1478 my $lockfile = File::Basename::dirname($self->{root}) . "/.lock";
469 24 50       610 unless (-e $lockfile) {
470 0 0       0 open(my $lock, '>', $lockfile)
471             or Carp::croak("couldn't create lockfile $lockfile");
472 0         0 print $lock time, "\n";
473 0         0 close $lock;
474             }
475 24         358 $self->{_locks} = 0;
476 24 50       1029 open($self->{_lockfile}, "+<", $lockfile)
477             or Carp::croak("couldn't open lockfile $lockfile");
478             }
479 100         707 flock($self->{_lockfile}, LOCK_EX);
480 100         219 ++$self->{_locks};
481             }
482              
483             sub unlock {
484 99     99   179 my ($self, $tree) = @_;
485 99 50       292 return if $self->{_no_op};
486 99 50       263 return unless $self->{_lockfile};
487 99 100       899 flock($self->{_lockfile}, LOCK_UN) if (--$self->{_locks} == 0);
488 99         210 return $self->{_locks};
489             }
490              
491             sub no_op {
492 0     0     my $self = shift;
493 0           $self->{_no_op}++;
494             }
495              
496             =head1 TODO
497              
498             =over
499              
500             =item * symlinks and references
501              
502             =item * serialization through delegation, not inheritance
503              
504             =item * make locking methods pluggable
505              
506             =item * callback for determining which files to skip
507              
508             =back
509              
510             =head1 AUTHOR
511              
512             Ricardo SIGNES, C<< >>
513              
514             =head1 BUGS
515              
516             Please report any bugs or feature requests to C, or
517             through the web interface at L. I will be notified, and
518             then you'll automatically be notified of progress on your bug as I make
519             changes.
520              
521             =head1 COPYRIGHT
522              
523             Copyright 2005 Ricardo Signes, All Rights Reserved.
524              
525             This program is free software; you can redistribute it and/or modify it
526             under the same terms as Perl itself.
527              
528             =cut
529              
530             1;