File Coverage

blib/lib/File/Pairtree.pm
Criterion Covered Total %
statement 68 240 28.3
branch 8 134 5.9
condition 6 82 7.3
subroutine 14 27 51.8
pod 0 14 0.0
total 96 497 19.3


line stmt bran cond sub pod time code
1             package File::Pairtree;
2              
3 2     2   69074 use 5.006;
  2         7  
  2         100  
4 2     2   13 use strict;
  2         3  
  2         64  
5 2     2   17 use warnings;
  2         9  
  2         3015  
6              
7             our $VERSION;
8             $VERSION = sprintf "%d.%02d", q$Name: Release-1-02 $ =~ /Release-(\d+)-(\d+)/;
9             #$VERSION = sprintf "%s", q$Name: Release-v0.304.0$ =~ /Release-(v\d+\.\d+\.\d+)/;
10             #our $NVERSION; # pure numeric 2-part equivalent version
11             #($NVERSION = $VERSION) =~ s/v(\d+\.\d+)\.\d+/$1/;
12              
13             require Exporter;
14             our @ISA = qw(Exporter);
15              
16             our @EXPORT = qw();
17             our @EXPORT_OK = qw(
18             id2ppath ppath2id s2ppchars id2pairpath pairpath2id
19             pt_lsid pt_lstree pt_mkid pt_mktree pt_rmid
20             pt_budstr get_prefix
21             $pfixtail
22             $pair $pairp1 $pairm1
23             );
24             our %EXPORT_TAGS = (all => [ @EXPORT_OK ]);
25              
26             our @EXPORT_FAIL = qw(
27             pair=1 pair=2 pair=3 pair=4 pair=5 pair=6 pair=7 pair=8 pair=9
28             );
29             push @EXPORT_OK, @EXPORT_FAIL; # add pseudo-symbols we will trap
30              
31             # This is a magic routine that the Exporter calls for any unknown symbols.
32             # We use it to permit export of pseudo-symbols "pair=1", "pair=2", ...,
33             # "pair=9" so the caller can define a pair to mean 1, 2, ..., or 9 octets.
34             #
35 0     0 0 0 sub export_fail { my( $class, @symbols )=@_;
36              
37 0         0 my @unknowns;
38 0         0 for (@symbols) {
39 0 0       0 ! s/^pair=([1-9])$/$1/ and
40             push(@unknowns, $_),
41             next;
42 0         0 pair_means($_); # define how many octets form a pair
43             }
44 0         0 return @unknowns;
45             }
46              
47             # xxx Config?
48             my $default_pathcomp_sep = '/';
49              
50             # In case we want to experiment with different cardinality of "pair",
51             # eg, 3 chars, 1 char, 4 chars. This is mostly untested. XXX
52             #
53             our ($pair, $pairp1, $pairm1);
54              
55 2     2 0 4 sub pair_means{ my( $n )=@_;
56              
57 2 50       9 die "the number meant by 'pair' must be a positive integer"
58             if ($n < 1);
59 2         5 $pair = $n;
60 2         4 $pairp1 = $pair + 1;
61 2         6 $pairm1 = $pair - 1; # xxx what if $pairm1 is zero?
62 2         4 return 1;
63             }
64             # XXXXXXXX arrange to call this at compile time?? punish the user if
65             # they call it themselves???
66              
67             # If not done so on import, define now how many octets in a pair.
68             #
69             defined($pair) or pair_means(2);
70             #
71             # Now it's safe to define compiled regexps based on
72             # constant values for $pair, $pairp1, and $pairm1.
73              
74             # this regexp matches a valid base ppath with bud attached
75             our $proper_ppath_re = "([^/]{$pair}/)*[^/]{1,$pair}";
76             our $root = "pairtree_root";
77              
78             my $R = $root;
79             my $P = $proper_ppath_re;
80              
81             # Pairtree - Pairtree support software (Perl module)
82             #
83             # Author: John A. Kunze, jak@ucop.edu, California Digital Library, 2008
84             # based on three lines of code originally from Sebastien Korner:
85             # $pt_objid =~ s/(\"|\*|\+|,|<|=|>|\?|\^|\|)/sprintf("^%x", ord($1))/eg;
86             # $pt_objid =~ tr/\/:./=+,/;
87             # my $pt_prefix = $namespace."/pairtree_root/".join('/', $pt_objid =~ /..|.$/g);
88              
89             # id2ppath - return /-terminated ppath corresponding to id
90             #
91             # For Perl, the platform's path component separator ('/' or '\') is
92             # automagically converted when needed to do filesystem things; in fact,
93             # trying to use the correct separator can get you into trouble. So we
94             # make it possible to specify the path component separator, but we won't
95             # do it for you. Instead we assume '/'.
96             #
97             # The return path starts with /pairtree_root to encourage good habits --
98             # this could backfire. We use the symbol 'pathcomp_sep' because
99             # 'path_sep' is already taken by the Config module to designate the
100             # character that separates entire pathnames, eg, ':' in the PATH
101             # environment variable.
102             #
103             # XXXXX this $pathcomp_sep -- is it worth a variable or is it better to
104             # let it be constant so we can compile the regexp?
105 30     30 0 14418 sub id2ppath{ my( $id, $pathcomp_sep )=@_; # single arg form, second
106             # arg not advertized
107              
108 30   66     108 $pathcomp_sep ||= $default_pathcomp_sep;
109             # $id =~ s{
110             # (["*+,<=>?\\^|] # some visible ASCII and
111             # |[^\x21-\x7e]) # all non-visible ASCII
112             # }{
113             # sprintf("^%02x", ord($1)) # replacement hex code
114             # }xeg;
115             #
116             # # Now do the single-char to single-char mapping.
117             # # The / translated next is not to be confused with $pathcomp_sep.
118             # #
119             # $id =~ tr /\/:./=+,/; # per spec, /:. become =+,
120              
121 30         56 $id = s2ppchars($id, $pathcomp_sep);
122              
123 30         493 return $root
124             . $pathcomp_sep
125             . join($pathcomp_sep, $id =~ /.{1,$pair}/g)
126             . $pathcomp_sep;
127             # . join($pathcomp_sep, $id =~ /..|.$/g)
128             }
129              
130 31     31 0 36 sub s2ppchars{ my( $s, $pathcomp_sep )=@_;
131              
132 31   66     58 $pathcomp_sep ||= $default_pathcomp_sep;
133 31         116 $s =~ s{
134             (["*+,<=>?\\^|] # some visible ASCII and
135             |[^\x21-\x7e]) # all non-visible ASCII
136             }{
137 118         348 sprintf("^%02x", ord($1)) # replacement hex code
138             }xeg;
139              
140             # Now do the single-char to single-char mapping.
141             # The / translated next is not to be confused with $pathcomp_sep.
142             #
143 31         43 $s =~ tr /\/:./=+,/; # per spec, /:. become =+,
144 31         65 return $s;
145             }
146              
147             # XXX ditch 2-arg forms?
148             # This 2-arg form exists for parallelism with other language interfaces
149             # (that don't may not have optional arguments). Perl users would
150             # normally prefer the id2ppath form for full functionality and speed.
151             #
152 0     0 0 0 sub id2pairpath{ my( $id, $pathcomp_sep )=@_; # two-argument form
153              
154 0         0 return id2ppath($id, $pathcomp_sep);
155             }
156              
157             # ppath2id - return id corresponding to ppath, or string of the form
158             # "error: "
159             # There is more error checking required for ppath2id than id2ppath,
160             # as the domain is more constrained.
161             #
162 35     35 0 12770 sub ppath2id{ my( $path, $pathcomp_sep )=@_; # single arg form, second
163             # arg not advertized
164 35         44 my $id = $path; # initialize $id with $path
165 35   66     124 my $p = $pathcomp_sep || $default_pathcomp_sep;
166              
167 35         32 my $expect_hexenc; # chars expected to be hex encoded
168 35 100       84 if ($p eq '\\') { # \ is a common, problemmatic case
169 1         2 $expect_hexenc = '"*<>?|'; # don't need to encode \
170 1         2 $p = '\\\\'; # and double escape for use in regex
171             } else {
172 34         38 $expect_hexenc = '"*<>?|\\\\'; # do need to encode \
173             }
174              
175             # Trim everything from the beginning up to the last instance of
176             # $root (via a greedy match). If there's a pairpath to the right
177             # of a given pairpath, assume that the most fine grained path
178             # (rightmost) is the one the user's interested in.
179             #
180 35         185 $id =~ s/^.*$root//;
181              
182             # Normalize so there's no initial or final whitespace, no
183             # repeated $pathcomp_sep chars, and exactly one $pathcomp_sep
184             # at the beginning and end.
185             #
186 35         94 $id =~ s/^\s*/$p/;
187 35         306 $id =~ s/\s*$/$p/;
188 35         480 $id =~ s/$p+/$p/g;
189              
190             # Also trim any final junk, eg, anpath extension that is really
191             # internal to an object directory.
192             #
193 35         304 $id =~ s/[^$p]{$pairp1,}.*$//; # trim final junk
194              
195             # Finally, trim anything that follows a one-char path component,
196             # a one-char component being another signal of the end of a ppath.
197             # In a general sense, "one" here really means "one less than the
198             # number of chars in a 'pair'".
199             #
200 35         381 $id =~ s/($p([^$p]){1,$pairm1}$p).*$/$1/; # trim after 1-char comp.
201              
202             # Reject if there are any non-visible chars.
203             #
204 35 100       128 return "error: non-visible chars in $path" if
205             $id =~ /[^\x21-\x7e]/;
206              
207             # Reject if there are any other chars that should be hex-encoded.
208             #
209 34 50       135 return "error: found chars expected to be hex-encoded in $path" if
210             $id =~ /[$expect_hexenc]/;
211              
212             # Now remove the path component separators.
213             #
214 34         178 $id =~ s/$p//g;
215              
216             # Reverse the single-char to single-char mapping.
217             # This might add formerly hex-encoded chars back in.
218             #
219 34         55 $id =~ tr /=+,/\/:./; # per spec, =+, become /:.
220              
221             # Reject if there are any ^'s not followed by two hex digits.
222             #
223 34 100       135 return "error: impossible hex-encoding in $path" if
224             $id =~ /\^($|.$|[^0-9a-fA-F].|.[^0-9a-fA-F])/;
225              
226             # Now reverse the hex conversion.
227             #
228 30         42 $id =~ s{
229             \^([0-9a-fA-F]{2})
230             }{
231 118         301 chr(hex("0x"."$1"))
232             }xeg;
233              
234 30         131 return $id;
235             }
236              
237 2     2   13 use Carp;
  2         5  
  2         240  
238 2     2   12 use File::Spec;
  2         3  
  2         42  
239 2     2   11 use File::Find;
  2         4  
  2         148  
240 2     2   11 use File::Path;
  2         4  
  2         115  
241 2     2   2108 use File::Namaste qw( nam_add );
  2         66591  
  2         151  
242 2     2   2080 use File::Value ':all';
  2         18951  
  2         374  
243 2     2   27 use File::Glob ':glob'; # standard use of module, which we need
  2         4  
  2         6250  
244             # as vanilla glob won't match whitespace
245              
246             our $Win; # whether we're running on Windows
247             # xxx should probably test directly for symlink capacity
248             defined($Win) or # if we're on a Windows platform avoid -l
249             $Win = grep(/Win32|OS2/i, @File::Spec::ISA);
250              
251             my $pfixtail = 'pairtree_prefix';
252              
253             # Return empty string unless we find a prefix value.
254 0     0 0   sub get_prefix { my( $parent_dir )=@_;
255              
256 0           my $prefix = "";
257 0           my $pxfile = $parent_dir . $pfixtail;
258 0 0         return $prefix unless -e $pxfile;
259 0           my $msg = file_value("< $pxfile", $prefix);
260 0 0         die "$pxfile: $msg" if $msg;
261 0           return $prefix;
262             }
263              
264             # caller can define inputs $$r_opt{prefix} and $$r_opt{parent_dir} for speed
265             # we return under keys: msg, ppath, bud
266             # return 0 on success, 1 on soft fail, >1 on hard fail
267             # xxxxxxxxx get consistent on these return codes/croaks
268             #
269 0     0 0   sub pt_lsid { my( $dir, $id, $r_opt )=@_;
270              
271 0 0         $dir or croak "no dir or empty dir";
272 0 0         $id or croak "no id or empty id";
273 0 0         ref($r_opt) eq "HASH" or
274             croak "r_opt must reference a hash (for input/output)";
275              
276 0           $dir = fiso_dname($dir, $R); # make sure we have descender
277 0   0       my $parent_dir = $$r_opt{parent_dir}
278             || fiso_uname($dir);
279 0   0       my $prefix = $$r_opt{prefix}
280             || get_prefix($parent_dir);
281              
282             # prefix substitution is optional unless -f ??? XXXXX
283             # xxx test
284 0 0 0       $prefix and ! ($id =~ s/^$prefix//) and $$r_opt{force} and
      0        
285             ($$r_opt{msg} = "no prefix present in: $id"),
286             return 2;
287              
288 0           my $ppath = $parent_dir . id2ppath($id);
289 0 0         -e $ppath or
290             ($$r_opt{msg} = "non-existent ppath ($ppath)"),
291             ($$r_opt{ppath} = ""),
292             return 1; # softer failure than return 2
293 0           $$r_opt{ppath} = $ppath;
294              
295             # Now that we have a valid ppath, we still don't know what
296             # the encapsulating directory (or anything else for that
297             # matter) looks like, so we use glob to look for things.
298             # Recall that $ppath ends in a '/' (from id2ppath).
299             # xxx sepchar better than / ?
300             #
301 0           my @buds = grep ! m{(^|/)\.\.?$}, # except for . and ..
302             bsd_glob($ppath . "{*,.*}"); # look for all files
303 0           my $nbuds = scalar @buds; # how many buds?
304 0 0         $nbuds == 0 and # empty ppath, not a node
305             ($$r_opt{msg} = "no bud: $ppath"),
306             ($$r_opt{bud} = ""),
307             return 2;
308              
309             # If we get here, there's one or more things at end of ppath.
310             #
311 0 0         $nbuds > 1 and
312             ($$r_opt{msg} = "expected one bud but got $nbuds buds"),
313             ($$r_opt{bud} = join " ", @buds),
314             return 2;
315              
316             # If we get here, only one thing at end of ppath (the common case).
317             #
318 0           $$r_opt{bud} = shift @buds;
319              
320             # XXXXX ??? $$r_opt{oxum}, $$r_opt{details} ? $$r_opt{long}
321             # ? $$r_opt{all}
322 0           return 0;
323             }
324              
325             my $homily = "(pairpath end should be followed by only one thing -- " .
326             "a directory name more than $pair characters long)";
327              
328             # Create a closure to hold a stateful node-visiting subroutine and other
329             # options suitable to be passed as the options parameter to File::Find.
330             # Returns the small hash { 'wanted' => $visitor, 'follow' => 1 } and a
331             # subroutine $visit_over that can be called to summarize the visit.
332             #
333 0     0 0   sub make_visitor { my( $r_opt )=@_;
334              
335 0 0         my $om = $r_opt->{om} or
336             return undef;
337              
338 0           my $objectcount = 0; # number of objects encountered
339 0           my $filecount = 0; # number of files encountered xxx
340 0           my $dircount = 0; # number of directories encountered xxx
341 0           my $symlinkcount = 0; # number of symlinks encountered xxx
342 0           my $irregularcount = 0; # non file, non dir fs items to report
343              
344 0           my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $sze);
345 0           my ($pdname, $tpname, $wpname);
346 0           my $symlinks_followed = 1;
347              
348             # yyy do as $curobj = {...}; ?
349 0           my %curobj = (
350             'ppath' => '',
351             'encaperr' => 0,
352             'octets' => 0,
353             'streams' => 0,
354             );
355              
356             # xxx move this outside closure and give it a $currobj arg?
357             # or is this much more efficient as-is?
358             # xxx nail down everything that won't change during lstree
359 0     0     my $newobj = sub { my( $ppath, $encaperr, $octets, $streams )=@_;
360              
361             # warning: ugly code ahead
362 0 0         if ($curobj{'ppath'}) { # print record of previous obj
363 0           $_ = ppath2id($curobj{'ppath'});
364 0           s/^/$r_opt->{prefix}/; # uses global set in lstree()
365 0 0 0       $r_opt->{long} and
366             $om->elem('node',
367             join(" ", $_, $curobj{'ppath'},
368             "$curobj{'octets'}.$curobj{'streams'}")), 1
369             or
370             $om->elem('node', $_), 1
371             ;
372 0 0         $curobj{'ppath'} eq $ppath and
373             print "error: corrupted pairtree at pairpath ",
374             "$ppath/: split end $homily\n";
375             # xxx use om?
376             }
377             # xxx strange
378 0 0 0       die "newobj: all args must be defined"
      0        
      0        
379             unless (defined($ppath) && defined($encaperr)
380             && defined($octets) && defined($streams));
381 0           $curobj{'ppath'} = $ppath;
382 0           $curobj{'encaperr'} = $encaperr;
383 0           $curobj{'octets'} = $octets;
384 0           $curobj{'streams'} = $streams;
385 0           };
386              
387             my $visitor = sub { # receives no args from File::Find
388              
389 0     0     $pdname = $File::Find::dir; # current parent directory name
390 0           $tpname = $_; # current filename in that dir
391 0           $_ = $wpname = $File::Find::name; # whole pathname to file
392              
393             # We always need lstat() info on the current node XXX why?
394             # xxx tells us all, but if following symlinks the lstat is done
395             # ... by find: use (-X _), but of the nifty facts below we
396             # still need to harvest the size ($sze) by hand.
397             #
398 0 0 0       ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $sze) = lstat($tpname)
399             unless ($symlinks_followed and ($sze = -s _));
400              
401             #print "NEXT: $pdname $_ $wpname\n";
402              
403             # If we follow symlinks (usual), we have to expect the -l type,
404             # which hides the type of the link target (what we really want).
405             #
406 0 0 0       if (! $Win and -l _) {
407 0           $symlinkcount++;
408             #print "XXXX SYMLINK $_\n";
409             # yyy presumably this branch never happens when
410             # _not_ following links?
411 0           ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $sze)
412             = stat($tpname); # get the real thing
413             }
414             # After this, tests of the form (-X _) give almost everything.
415              
416 0 0         if (-f $tpname) {
    0          
417 0           $filecount++;
418 0 0         if (m@^.*$R/(.*/)?pairtree.*$@o) {
    0          
419             ### print "$pdname PTREEFILE $tpname\n";
420             # xxx if $verbose;
421             # else -prune ??
422             }
423             elsif (m@^.*$R/$P/[^/]+$@o) {
424             #print "m@.*$R/$P/[^/]+@: $_\n";
425             #print "$pdname UF $tpname\n";
426 0           print "error: corrupted pairtree at pairpath ",
427             "$pdname/: found unencapsulated file ",
428             "'$tpname' $homily\n";
429             }
430             else {
431             # xxx sanity check that $curobj is defined
432 0           $curobj{'octets'} += $sze;
433             ### print "sssss $curobj{'octets'}\n";
434 0           $curobj{'streams'}++;
435             # -fprintf $altout 'IN %p %s\n'
436             # $noprune
437             }
438             }
439             elsif (-d $tpname) {
440 0           $dircount++;
441 0 0         if (m@^.*$R/(.*/)?pairtree.*$@o) {
    0          
    0          
    0          
442             #print "$pdname PTREEDIR $tpname\n";
443             # xxx if $verbose;
444             # -prune
445             }
446             # At last, we're entering a "regular" object.
447             elsif (m@^.*$R/($P/)?[^/]{$pairp1,}$@o) {
448             # start new object; but end previous object first
449             # form: ppath, EncapErr, octets, streams
450 0           $objectcount++;
451 0           &$newobj($pdname, 0, 0, 0);
452             # print "$pdname NS $tpname\n";
453             # -fprintf $altout 'START %h 0\n'
454             # $noprune
455             }
456             elsif (m@^.*$R/$P$@o) {
457             # -empty
458             # xxx if $verbose... -printf '%p EP -\n'
459             }
460             # $pair, $pairm1, $pairp1
461             # We have a post-morty encapsulation error
462             elsif (m@^.*$R/([^/]{$pair}/)*[^/]{1,$pairm1}/[^/]{1,$pair}$@o) {
463             #print "$pdname PM $tpname\n";
464 0           print "error: corrupted pairtree at pairpath ",
465             "$pdname/: found '$tpname' after forced ",
466             "path ending $homily\n";
467            
468             # -fprintf $altout 'START %h 0\n'
469             # $noprune
470             }
471             }
472             else {
473 0           $irregularcount++;
474             }
475 0           };
476              
477 0     0     my $visit_over = sub { my( $ret, $tree )=@_;
478              
479 0   0       $ret ||= 0;
480              
481             # Dummy call to pt_newobj() to cough up the last buffered object.
482             # xxx not multi-threadable! how to give newobj its own context?
483 0           &$newobj("", 0, 0, 0); # shake out the last one
484              
485             # XXX what does find return?
486 0 0         $om->elem("lstree", "find returned '$ret' for $tree") if $ret;
487 0 0         $om->elem("objectcount", "$objectcount object" .
488             ($objectcount == 1 ? "" : "s"));
489              
490 0           return ($objectcount);
491 0           };
492              
493 0           return ({ 'wanted' => $visitor, 'follow' => $r_opt->{follow_fast} },
494             $visit_over);
495             }
496              
497 0     0 0   sub pt_lstree { my( $tree, $r_opt, $r_visit_node, $r_wrapup )=@_;
498              
499 0 0         $tree or croak "no tree dir or empty tree dir";
500 0 0         ref($r_opt) eq "HASH" or
501             croak "r_opt must reference a hash (for input/output)";
502 0 0 0       ref( $r_visit_node ||= \&pt_visit_node ) eq "CODE" or
503             croak "r_visit_node must reference a node-visiting function";
504             #ref( $r_wrapup ||= \&pt_lstree_wrapup ) eq "CODE" or
505             # croak "r_wrapup must reference a node-visiting function";
506              
507 0           $tree = fiso_dname($tree, $R); # make sure we have descender
508 0   0       $$r_opt{parent_dir} ||= fiso_uname($tree);
509 0   0       $$r_opt{prefix} ||= get_prefix($$r_opt{prefix});
510              
511 0           my ($find_opt, $visit_over) = make_visitor($r_opt);
512 0 0         $find_opt or
513             croak "make_visitor() failed";
514 0           my $ret = find($find_opt, $tree);
515 0 0         $visit_over and
516             &$visit_over($ret, $tree);
517 0           return $ret;
518             }
519              
520             # Create the bud string that will encapsulate the leaf node
521             #
522 0     0 0   sub pt_budstr { my( $id, $bud_style )=@_;
523              
524             # Xxx add chars if less than $pair chars in $_
525 0   0       $id ||= "";
526 0 0         ! $id and # valid but empty id/ppath
527             return "supernode";
528 0   0       $bud_style ||= 0; # xxx
529              
530 0           my $n = length($id) - 1;
531 0 0         $n < $pair and # pad on left with zeroes
532             $id = ("0" x ($pair - $n)) . $id;
533              
534             # xxx optional variation on endings
535 0 0         $bud_style == 0 and # "full"
536             return s2ppchars($id);
537             #or # XXX no other possibility right now
538             ;
539 0           return s2ppchars($id);
540             }
541              
542 0     0 0   sub pt_mkid { my( $dir, $id, $r_opt )=@_;
543              
544 0 0         $dir or croak "no dir or empty dir";
545 0 0         $id or croak "no id or empty id";
546 0 0         ref($r_opt) eq "HASH" or
547             croak "r_opt must reference a hash (for input/output)";
548              
549 0           $dir = fiso_dname($dir, $R); # make sure we have descender
550 0   0       my $parent_dir = $$r_opt{parent_dir}
551             || fiso_uname($dir);
552 0   0       my $prefix = $$r_opt{prefix}
553             || get_prefix($parent_dir);
554              
555             # prefix substitution is optional unless -f
556 0 0 0       $prefix and ! ($id =~ s/^$prefix//) and $$r_opt{force} and
      0        
557             ($$r_opt{msg} = "no prefix present in: $id"),
558             return 0;
559 0 0         $id !~ m/^\s*$/ or # double check that we won't xxx?
560             croak "bad node"; # create a malformed pairtree
561              
562 0 0 0       -d $dir or # need to create base directory
563             pt_mktree($dir, "", $r_opt) and # if error
564             ($$r_opt{msg} = "pt_mkid: $$r_opt{msg}"),
565             return 1; # return after adding our stamp
566 0           my $ppath = $parent_dir . id2ppath($id);
567 0           my $bud = $ppath . pt_budstr($id, $$r_opt{bud_style});
568              
569 0           my $ret;
570 0           eval { $ret = mkpath($bud) };
  0            
571 0 0         $@ and croak "Couldn't create $bud: $@";
572 0 0         if ($ret == 0) {
573 0 0         croak "pt_mkid: mkpath returned '0' for $bud"
574             unless -e $bud;
575 0           $$r_opt{msg} = "error: $bud ($id) already exists\n";
576 0           return 0;
577             }
578 0           $$r_opt{ppath} = $ppath;
579 0           $$r_opt{bud} = $bud;
580              
581 0           return 1;
582             }
583              
584 0     0 0   sub pt_mktree { my( $dir, $prefix, $r_opt )=@_;
585              
586             # XXXX make up my mind about when to croak and when to
587             # use $$r_opt{msg}
588 0 0         $dir or croak "no tree dir or empty tree dir";
589 0   0       $prefix ||= "";
590 0 0         ref($r_opt) eq "HASH" or
591             croak "r_opt must reference a hash (for input/output)";
592             # except that we ignore any r_opt inputs here
593              
594 0           $dir = fiso_dname($dir, $R); # make sure we have descender
595 0           my $parent_dir = fiso_uname($dir);
596 0           my $ret;
597 0           eval { $ret = mkpath($dir) };
  0            
598 0 0         if ($@) {
599 0           $$r_opt{msg} = "Couldn't create $dir tree: $@";
600 0           return 1;
601             }
602 0 0         if ($ret == 0) {
603 0 0         $$r_opt{msg} = -e $dir ? "$dir already exists"
604             : "pt_mktree: mkpath returned '0' for $dir";
605 0           return 1;
606             }
607              
608 0           my $pxfile = File::Spec->catfile($parent_dir, $pfixtail);
609 0 0         my $msg = file_value("> $pxfile", $prefix)
610             if ($prefix);
611 0 0         if ($msg) {
612 0           $$r_opt{msg} = "$pxfile: $msg";
613 0           return 1;
614             }
615 0 0         $msg and croak "Couldn't create namaste tag in $dir: $msg";
616 0           $msg = nam_add($parent_dir, undef, '0', "pairtree_$VERSION", 0);
617             # yyy better to use 0 to mean "don't truncate"
618             #length("pairtree_$VERSION"));
619             # xxxx croak or return via r_opt{msg}
620 0 0         $msg and croak "Couldn't create namaste tag in $parent_dir: $msg";
621              
622 0           return 0;
623             }
624              
625 0     0 0   sub pt_rmid { my( $dir, $id, $r_opt )=@_;
626              
627 0 0         $dir or croak "no dir or empty dir";
628 0 0         $id or croak "no id or empty id";
629 0 0         ref($r_opt) eq "HASH" or
630             croak "r_opt must reference a hash (for input/output)";
631              
632 0           $dir = fiso_dname($dir, $R); # make sure we have descender
633 0   0       my $parent_dir = $$r_opt{parent_dir}
634             || fiso_uname($dir);
635 0   0       my $prefix = $$r_opt{prefix}
636             || get_prefix($parent_dir);
637              
638             # prefix substitution is optional unless -f
639 0 0 0       $prefix and ! ($id =~ s/^$prefix//) and $$r_opt{force} and
      0        
640             ($$r_opt{msg} = "no prefix present in: $id"),
641             return 0;
642 0 0         $id !~ m/^\s*$/ or # double check that we won't xxx?
643             croak "bad node"; # create a malformed pairtree
644             #xxxxx this double check above should be against whole path
645             ## double check that we won't delete whole pairtree
646             #die "bad node: $_"
647             # if m,$R/*$,;
648              
649 0           my $ppath = $parent_dir . id2ppath($id);
650 0 0         -e $ppath or # if it doesn't exist
651             ($$r_opt{msg} = "non-existent ppath ($ppath)"),
652             ($$r_opt{ppath} = ""),
653             return 1; # softer failure than return 2
654              
655 0           $$r_opt{ppath} = $ppath;
656 0           my $ret;
657 0           eval { $ret = rmtree($ppath) };
  0            
658 0 0         if ($@) {
659 0           $$r_opt{msg} = "Couldn't remove $ppath tree: $@";
660 0           return 1;
661             }
662 0 0         if ($ret == 0) {
663 0 0         $$r_opt{msg} = "warning: $id ($ppath) " .
664             (-e $ppath ? "not removed" : "doesn't exist");
665 0           return 1; # soft failure
666             }
667 0           return 0; # success
668             }
669              
670             1;
671              
672             __END__