File Coverage

blib/lib/Set/Files.pm
Criterion Covered Total %
statement 392 621 63.1
branch 176 310 56.7
condition 64 111 57.6
subroutine 20 24 83.3
pod 14 14 100.0
total 666 1080 61.6


line stmt bran cond sub pod time code
1             package Set::Files;
2             # Copyright (c) 2001-2010 Sullivan Beck. All rights reserved.
3             # This program is free software; you can redistribute it and/or modify it
4             # under the same terms as Perl itself.
5              
6             # TODO
7             # file locking (on a per-set basis)
8             # create a set (set owner if root, otherwise use current user)
9              
10             ########################################################################
11              
12             require 5.000;
13 9     9   278412 use strict;
  9         21  
  9         366  
14 9     9   53 use warnings;
  9         20  
  9         329  
15 9     9   47 use Carp;
  9         22  
  9         1224  
16 9     9   52 use IO::File;
  9         17  
  9         1447  
17              
18 9     9   47 use vars qw($VERSION);
  9         15  
  9         102501  
19             $VERSION = "1.06";
20              
21             ########################################################################
22             # METHODS
23             ########################################################################
24              
25             my @Cache = qw(type owner dir opts ele);
26              
27             # The Set::Files object:
28             #
29             # { SET => { type => { TYPE => 1, ... },
30             # owner => USER,
31             # dir => DIR,
32             # ele => { ELE => TRUE, ... },
33             # opts => { VAR => VAL, ... },
34             #
35             # incl => { SET => 1, ... },
36             # excl => { SET => 1, ... },
37             # omit => { ELE => 1, ... }
38             # }
39             # }
40             #
41             # The ELE => TRUE value is either 1 (if the element is explicitely
42             # included in the file) or 2 (if the element comes from an included
43             # file).
44              
45             sub new {
46 26     26 1 5935 my($class,%opts) = @_;
47              
48 26         109 my $self = _Init(%opts);
49 26         85 bless $self, $class;
50              
51 26         105 return $self;
52             }
53              
54             sub list_sets {
55 17     17 1 67 my($self,$type) = @_;
56 17 100       38 if ($type) {
57 4         7 my(@ret);
58 4         11 foreach my $set (keys %{ $$self{"set"} }) {
  4         47  
59 10 100       42 push(@ret,$set) if ($$self{"set"}{$set}{"type"}{$type});
60             }
61 4         35 return sort @ret;
62             } else {
63 13         22 return sort keys %{ $$self{"set"} };
  13         159  
64             }
65             }
66              
67             sub owner {
68 0     0 1 0 my($self,$set) = @_;
69 0 0       0 if ($set) {
70 0 0       0 if (exists $$self{"set"}{$set}) {
71 0         0 return $$self{"set"}{$set}{"owner"};
72             } else {
73 0         0 carp "ERROR: Invalid set: $set\n";
74 0         0 return undef;
75             }
76              
77             } else {
78 0         0 my %tmp;
79 0         0 foreach my $set (keys %{ $$self{"set"} }) {
  0         0  
80 0         0 $tmp{ $$self{"set"}{$set}{"owner"} } = 1;
81             }
82 0         0 return sort keys %tmp;
83             }
84             }
85              
86             sub owned_by {
87 0     0 1 0 my($self,$uid,$type) = @_;
88 0 0       0 if (! defined $uid) {
89 0         0 carp "ERROR: Must specify a UID for 'owned_by' info.\n";
90 0         0 return undef;
91             }
92              
93 0         0 my(@ret);
94 0         0 foreach my $set (keys %{ $$self{"set"} }) {
  0         0  
95 0 0 0     0 push(@ret,$set) if ($$self{"set"}{$set}{"owner"} == $uid &&
      0        
96             (! $type ||
97             exists $$self{"set"}{$set}{"type"}{$type}));
98             }
99 0         0 return sort @ret;
100             }
101              
102             sub members {
103 19     19 1 53 my($self,$set) = @_;
104 19 50       46 if (! $set) {
105 0         0 carp "ERROR: Must specify a set for 'members' info.\n";
106 0         0 return undef;
107             }
108 19 50       68 if (! exists $$self{"set"}{$set}) {
109 0         0 carp "ERROR: Invalid set: $set\n";
110 0         0 return undef;
111             }
112 19         37 return sort keys %{ $$self{"set"}{$set}{"ele"} };
  19         234  
113             }
114              
115             sub is_member {
116 8     8 1 17 my($self,$set,$ele) = @_;
117 8 50       28 if (! $set) {
118 0         0 carp "ERROR: Must specify a set for 'is_member' info.\n";
119 0         0 return undef;
120             }
121 8 50       28 if (! exists $$self{"set"}{$set}) {
122 0         0 carp "ERROR: Invalid set: $set\n";
123 0         0 return undef;
124             }
125 8 50       20 if (! defined $ele) {
126 0         0 carp "ERROR: Must specify an element for 'is_member' info.\n";
127 0         0 return undef;
128             }
129 8 100       40 return 1 if (exists $$self{"set"}{$set}{"ele"}{$ele});
130 4         34 return 0;
131             }
132              
133             sub list_types {
134 11     11 1 22 my($self,$set) = @_;
135 11 100       25 if ($set) {
136 7 50       20 if (exists $$self{"set"}{$set}) {
137 7         9 return sort keys %{ $$self{"set"}{$set}{"type"} };
  7         49  
138             } else {
139 0         0 carp "ERROR: Invalid set: $set\n";
140 0         0 return undef;
141             }
142              
143             } else {
144 4         21 my %tmp;
145 4         8 foreach my $set (keys %{ $$self{"set"} }) {
  4         14  
146 10         11 foreach my $type (keys %{ $$self{"set"}{$set}{"type"} }) {
  10         31  
147 14         34 $tmp{$type} = 1;
148             }
149             }
150 4         33 return sort keys %tmp;
151             }
152             }
153              
154             sub dir {
155 7     7 1 13 my($self,$set) = @_;
156 7 50       23 if ($set) {
157 7 50       21 if (exists $$self{"set"}{$set}) {
158 7         74 return $$self{"set"}{$set}{"dir"};
159             } else {
160 0         0 carp "ERROR: Invalid set: $set\n";
161 0         0 return undef;
162             }
163              
164             } else {
165 0         0 my %tmp;
166 0         0 foreach my $set (keys %{ $$self{"set"} }) {
  0         0  
167 0         0 $tmp{ $$self{"set"}{$set}{"dir"} } = 1;
168             }
169 0         0 return sort keys %tmp;
170             }
171             }
172              
173             sub opts {
174 8     8 1 17 my($self,$set,$opt) = @_;
175 8 50       22 if (! $set) {
176 0         0 carp "ERROR: Must specify a set for 'opts' info.\n";
177 0         0 return undef;
178             }
179 8 50       54 if (! exists $$self{"set"}{$set}) {
180 0         0 carp "ERROR: Invalid set: $set\n";
181 0         0 return undef;
182             }
183              
184 8 50       16 if ($opt) {
185 8 50       41 if (exists $$self{"set"}{$set}{"opts"}{$opt}) {
186 8         54 return $$self{"set"}{$set}{"opts"}{$opt};
187             } else {
188 0         0 return 0;
189             }
190             } else {
191 0         0 return %{ $$self{"set"}{$set}{"opts"} };
  0         0  
192             }
193             }
194              
195             sub delete {
196 0     0 1 0 my($self,$set,$nobackup) = @_;
197 0 0       0 if (! $set) {
198 0         0 carp "ERROR: Set must be specified.\n";
199 0         0 return;
200             }
201 0 0       0 if (! exists $$self{"set"}{$set}) {
202 0         0 carp "ERROR: Invalid set: $set.\n";
203 0         0 return;
204             }
205              
206 0         0 my $dir = $$self{"set"}{$set}{"dir"};
207              
208 0 0       0 if (! -w $dir) {
209 0         0 carp "ERROR: the delete method requires write access\n";
210 0         0 return;
211             }
212              
213 0 0       0 if (! -f "$dir/$set") {
214 0         0 carp "ERROR: Set file nonexistant: $dir/$set\n";
215 0         0 return;
216             }
217              
218 0 0       0 if ($nobackup) {
219 0   0     0 unlink "$dir/$set" ||
220             carp "ERROR: Unable to remove set file: $dir/$set\n";
221             } else {
222 0   0     0 rename "$dir/$set","$dir/.set_files.$set" ||
223             carp "ERROR: Unable to backup set file: $dir/$set\n";
224             }
225             }
226              
227             sub cache {
228 1     1 1 7 my($self) = @_;
229 1 50       6 if ($$self{"read"} ne "files") {
230 0         0 carp "ERROR: unable to cache information: read from cache or file\n";
231 0         0 return;
232             }
233              
234 1         3 my($file) = $$self{"cache"} . "/.set_files.cache";
235 1         5 my($out) = new IO::File;
236              
237 1 50       25 if (! $out->open("$file.new",O_CREAT|O_WRONLY,0644)) {
238 0         0 croak "ERROR: unable to create cache: $file.new: $!\n";
239             }
240              
241 1         135 foreach my $set (sort keys %{ $$self{"set"} }) {
  1         6  
242 3         16 print $out $set,"\n";
243 3         4 foreach my $key (@Cache) {
244 15 50       35 next if (! exists $$self{"set"}{$set}{$key});
245              
246 15 100       34 if (ref $$self{"set"}{$set}{$key} eq "HASH") {
247 9         11 print $out ".sf.hash\n";
248 9         6 print $out $key,"\n";
249 9         10 foreach my $k (sort keys %{ $$self{"set"}{$set}{$key} }) {
  9         29  
250 23         26 print $out $k,"\n";
251 23         70 print $out $$self{"set"}{$set}{$key}{$k},"\n";
252             }
253 9         12 print $out ".sf.end\n";
254 9         13 next;
255             }
256              
257 6 50       18 if (ref $$self{"set"}{$set}{$key} eq "ARRAY") {
258 0         0 print $out ".sf.array\n";
259 0         0 print $out $key,"\n";
260 0         0 foreach my $k (@{ $$self{"set"}{$set}{$key} }) {
  0         0  
261 0         0 print $out $k,"\n";
262             }
263 0         0 print $out ".sf.end\n";
264 0         0 next;
265             }
266              
267 6         7 print $out ".sf.scalar\n";
268 6         7 print $out $key,"\n";
269 6         13 print $out $$self{"set"}{$set}{$key},"\n";
270             }
271 3         6 print $out "\n";
272             }
273 1         5 $out->close;
274              
275 1   33     133 rename "$file.new",$file ||
276             croak "ERROR: unable to commit cache: $file: $!\n";
277             }
278              
279             sub add {
280 8     8 1 138 my($self,$set,$force,$commit,@ele) = @_;
281 8 50       25 if ($$self{"read"} ne "files") {
282 0         0 carp "ERROR: unable to add elements: read from cache\n";
283 0         0 return;
284             }
285              
286 8 50       15 if (! $set) {
287 0         0 carp "ERROR: Must specify a set for adding elements.\n";
288 0         0 return undef;
289             }
290 8 50       19 if (! exists $$self{"set"}{$set}) {
291 0         0 carp "ERROR: Invalid set: $set\n";
292 0         0 return undef;
293             }
294 8 50       14 if (! @ele) {
295 0         0 carp "ERROR: No elements present for adding.\n";
296 0         0 return undef;
297             }
298              
299 8         10 my(@add);
300 8         13 foreach my $ele (@ele) {
301 22 100 66     90 if (! exists $$self{"set"}{$set}{"ele"}{$ele} ||
      66        
302             ($$self{"set"}{$set}{"ele"}{$ele} == 2 && $force)) {
303 18         34 $$self{"set"}{$set}{"ele"}{$ele} = 1;
304 18         27 delete $$self{"set"}{$set}{"omit0"}{$ele};
305 18         39 push(@add,$ele);
306             }
307             }
308 8 50       17 return 0 if (! @add);
309              
310 8 50       16 commit($self,$set) if ($commit);
311 8         41 return $#add+1;
312             }
313              
314             sub remove {
315 2     2 1 7 my($self,$set,$force,$commit,@ele) = @_;
316 2 50       7 if ($$self{"read"} ne "files") {
317 0         0 carp "ERROR: unable to remove elements: read from cache\n";
318 0         0 return;
319             }
320              
321 2 50       5 if (! $set) {
322 0         0 carp "ERROR: Must specify a set for removing elements.\n";
323 0         0 return undef;
324             }
325 2 50       6 if (! exists $$self{"set"}{$set}) {
326 0         0 carp "ERROR: Invalid set: $set\n";
327 0         0 return undef;
328             }
329 2 50       6 if (! @ele) {
330 0         0 carp "ERROR: No elements present for removing.\n";
331 0         0 return undef;
332             }
333              
334 2         4 my(@rem);
335 2         3 foreach my $ele (@ele) {
336 6 100 33     42 if (exists $$self{"set"}{$set}{"ele"}{$ele} ||
      66        
      66        
337             ( (! exists $$self{"set"}{$set}{"omit0"} ||
338             ! exists $$self{"set"}{$set}{"omit0"}{$ele}) && $force )) {
339 5         11 delete $$self{"set"}{$set}{"ele"}{$ele};
340 5         10 $$self{"set"}{$set}{"omit0"}{$ele} = 1;
341 5         11 push(@rem,$ele);
342             }
343             }
344 2 50       8 return 0 if (! @rem);
345              
346 2 50       6 commit($self,$set) if ($commit);
347 2         11 return $#rem+1;
348             }
349              
350             sub commit {
351 0     0 1 0 my($self,@set) = @_;
352 0 0       0 if (! @set) {
353 0         0 carp "ERROR: Set must be specified.\n";
354 0         0 return;
355             }
356 0 0 0     0 if ($$self{"read"} ne "file" &&
357             $$self{"read"} ne "files") {
358 0         0 carp "ERROR: unable to commit changes: read from cache\n";
359 0         0 return;
360             }
361              
362 0         0 foreach my $set (@set) {
363 0 0       0 if (! exists $$self{"set"}{$set}) {
364 0         0 carp "ERROR: Invalid set: $set.\n";
365 0         0 next;
366             }
367              
368             # get dir and find out where to write new stuff
369              
370 0         0 my $dir = $$self{"set"}{$set}{"dir"};
371 0         0 my $scr;
372             my $wri;
373 0 0       0 if (-w $dir) {
374 0         0 $wri = 1;
375 0         0 $scr = $dir;
376             } else {
377 0         0 $wri = 0;
378 0         0 $scr = $$self{"scratch"};
379             }
380              
381             # write the new file
382              
383 0         0 my $template = $$self{"cache"} . "/.set_files.template";
384 0         0 my $file = "$scr/.set_files.$set.new";
385 0         0 my $out = new IO::File;
386 0         0 my $in = new IO::File;
387              
388 0         0 my @temp;
389 0 0       0 if (-f $template) {
390 0 0       0 if (! $in->open($template)) {
391 0         0 carp "ERROR: Unable to open template: $file: $!\n";
392             } else {
393 0         0 @temp = <$in>;
394 0         0 $in->close;
395             }
396             }
397              
398 0 0       0 if (! $out->open($file,O_CREAT|O_WRONLY,0644)) {
399 0         0 carp "ERROR: Unable to write file: $file: $!\n";
400 0         0 next;
401             }
402 0         0 foreach my $line (@temp) {
403 0         0 print $out $line;
404             }
405              
406 0         0 my $t = $$self{"tagchars"};
407              
408 0         0 foreach my $inc (sort keys %{ $$self{"set"}{$set}{"incl0"} }) {
  0         0  
409 0         0 print $out $t,"INCLUDE $inc\n";
410             }
411 0         0 foreach my $exc (sort keys %{ $$self{"set"}{$set}{"excl0"} }) {
  0         0  
412 0         0 print $out $t,"EXCLUDE $exc\n";
413             }
414 0         0 foreach my $omit (sort keys %{ $$self{"set"}{$set}{"omit0"} }) {
  0         0  
415 0         0 print $out $t,"OMIT $omit\n";
416             }
417 0         0 foreach my $type (sort keys %{ $$self{"set"}{$set}{"type0"} }) {
  0         0  
418 0         0 print $out $t,"TYPE $type\n";
419             }
420 0         0 foreach my $type (sort keys %{ $$self{"set"}{$set}{"notype0"} }) {
  0         0  
421 0         0 print $out $t,"NOTYPE $type\n";
422             }
423 0         0 foreach my $opt (sort keys %{ $$self{"set"}{$set}{"opts"} }) {
  0         0  
424 0         0 my $val = $$self{"set"}{$set}{"opts"}{$opt};
425 0         0 print $out $t,"OPTION $opt = $val\n";
426             }
427 0         0 foreach my $ele (sort keys %{ $$self{"set"}{$set}{"ele"} }) {
  0         0  
428 0 0       0 next if ($$self{"set"}{$set}{"ele"}{$ele} == 2);
429 0         0 print $out "$ele\n";
430             }
431              
432 0         0 $out->close;
433              
434             # back up the old one
435              
436 0 0       0 if ($wri) {
437 0   0     0 rename "$dir/$set","$dir/.set_files.$set" || do {
438             carp "ERROR: Unable to back up file: $dir/$set: $!\n";
439             next;
440             };
441             } else {
442 0         0 my @in;
443 0 0       0 if (! $in->open("$dir/$set")) {
444 0         0 carp "ERROR: Unable to read file: $dir/$set: $!\n";
445 0         0 next;
446             }
447 0         0 @in = <$in>;
448 0         0 $in->close;
449 0 0       0 if (! $out->open("$scr/.set_files.$set",O_CREAT|O_WRONLY,0644)) {
450 0         0 carp "ERROR: Unable to write file: $scr/.set_files.$set: $!\n";
451 0         0 next;
452             }
453 0         0 foreach my $line (@in) {
454 0         0 print $out $line;
455             }
456 0         0 $out->close;
457             }
458              
459             # move the new one into place
460              
461 0 0       0 if ($wri) {
462 0   0     0 rename "$dir/.set_files.$set.new","$dir/$set" || do {
463             carp "ERROR: Unable to commit file: $dir/$set: $!\n";
464             next;
465             };
466             } else {
467 0         0 my @in;
468 0 0       0 if (! $in->open("$scr/.set_files.$set.new")) {
469 0         0 carp "ERROR: Unable to read file: $scr/.set_files.$set.new: $!\n";
470 0         0 next;
471             }
472 0         0 @in = <$in>;
473 0         0 $in->close;
474 0 0       0 if (! $out->open("$dir/$set",O_CREAT|O_WRONLY,0644)) {
475 0         0 carp "ERROR: Unable to write file: $dir/$set: $!\n";
476 0         0 next;
477             }
478 0         0 foreach my $line (@in) {
479 0         0 print $out $line;
480             }
481 0         0 $out->close;
482             }
483             }
484             }
485              
486             ########################################################################
487              
488             sub _Init {
489 26     26   77 my(%opts)=@_;
490 26         151 my(%self) = ();
491              
492             ###########################
493             # Initialization
494              
495             # path
496              
497 26         34 my(@dir,@tmp);
498 26 50       77 if (exists $opts{"path"}) {
499 26         46 my $dir = $opts{"path"};
500 26 100       83 if (ref($dir) eq "ARRAY") {
    50          
501 14         98 @tmp = @$dir;
502             } elsif (ref($dir)) {
503 0         0 croak "ERROR: Invalid path value\n";
504             } else {
505 12         49 @tmp = split(":",$dir);
506             }
507             } else {
508 0         0 @tmp = (".");
509             }
510              
511 26         58 foreach my $dir (@tmp) {
512 39 50       644 if (-d $dir) {
513 39         96 push(@dir,$dir);
514             } else {
515 0         0 carp "WARNING: invalid directory: $dir\n";
516             }
517             }
518              
519 26 50       99 if (! @dir) {
520 0         0 croak "ERROR: no valid path elements\n";
521             }
522              
523             # cache
524              
525 26         59 my($cache,$cache_opt);
526 26 100       79 if (exists $opts{"cache"}) {
527 2         4 $cache = $opts{"cache"};
528 2         4 $cache_opt = 1;
529             } else {
530 24         37 $cache = $dir[0];
531 24         36 $cache_opt = 0;
532             }
533 26         187 $self{"cache"} = $cache;
534              
535 26 50       306 if (! -d $cache) {
536 0         0 croak "ERROR: invalid cache directory: $cache\n";
537             }
538              
539             # scratch
540              
541 26         39 my($scratch);
542 26 50       68 if (exists $opts{"scratch"}) {
543 0         0 $scratch = $opts{"scratch"};
544             } else {
545 26 50       517 $scratch = (-d '/tmp' ? '/tmp' : '.');
546             }
547 26         79 $self{"scratch"} = $scratch;
548              
549 26 50 33     864 if (! -d $scratch ||
550             ! -w $scratch) {
551 0         0 croak "ERROR: invalid scratch directory: $scratch\n";
552             }
553              
554             # invalid_quiet
555              
556 26         48 my($invalid_quiet);
557 26 100       88 if (exists $opts{"invalid_quiet"}) {
558 17         25 $invalid_quiet = 1;
559             } else {
560 9         15 $invalid_quiet = 0;
561             }
562              
563             # read
564              
565 26         35 my($read);
566 26 100       96 if (exists $opts{"read"}) {
567 5         7 $read = $opts{"read"};
568 5 50 100     36 if ($read ne "cache" &&
      66        
569             $read ne "files" &&
570             $read ne "file") {
571 0         0 croak "ERROR: Invalid read option: $read\n";
572             }
573             } else {
574 21 50       47 if ($cache_opt) {
575 0         0 $read="cache";
576             } else {
577 21         63 $read="files";
578             }
579             }
580 26         132 $self{"read"} = $read;
581              
582             # set
583              
584 26         37 my($set);
585 26 100       57 if (exists $opts{"set"}) {
586 3         6 $set = $opts{"set"};
587             } else {
588 23         73 $set = "";
589             }
590              
591 26 50 66     98 if ($read eq "file" && ! $set) {
592 0         0 croak "ERROR: Read file requires a set\n";
593             }
594 26 50 66     108 if ($set && $read ne "file") {
595 0         0 carp "WARNING: Set option ignored when not reading a single file\n";
596 0         0 return;
597             }
598              
599             # LOCK
600              
601 26         41 my($lock);
602 26 50       67 if (exists $opts{"lock"}) {
603 0 0       0 $lock = ($opts{"lock"} ? 1 : 0);
604             } else {
605 26         183 $lock = 0;
606             }
607              
608 26 50       77 if ($lock) {
609             }
610              
611             ###########################
612             # Read Cache
613              
614 26 100       74 if ($read eq "cache") {
615 1         2 my $file = "$cache/.set_files.cache";
616 1 50       11 if (-f $file) {
617 1         5 my $in = new IO::File;
618 1 50       24 $in->open($file) ||
619             croak "ERROR: unable to read cache: $file: $!\n";
620 1         100 my @in = <$in>;
621 1         7 $in->close;
622 1         15 chomp(@in);
623 1         4 while (@in) {
624 3         4 my $set = shift(@in);
625 3         6 while ($in[0]) {
626 15         15 my $tmp = shift(@in);
627 15         16 my $key = shift(@in);
628 15 100       27 if ($tmp eq ".sf.hash") {
    50          
    50          
629 9         15 while ($in[0] ne ".sf.end") {
630 23         20 my $k = shift(@in);
631 23         62 $self{"set"}{$set}{$key}{$k} = shift(@in);
632             }
633 9         13 shift(@in);
634              
635             } elsif ($tmp eq ".sf.array") {
636 0         0 my @tmp;
637 0         0 while ($in[0] ne ".sf.end") {
638 0         0 push(@tmp,shift(@in));
639             }
640 0         0 $self{"set"}{$set}{$key} = [ @tmp ];
641 0         0 shift(@in);
642              
643             } elsif ($tmp eq ".sf.scalar") {
644 6         15 $self{"set"}{$set}{$key} = shift(@in);
645             }
646             }
647 3         7 shift(@in);
648             }
649              
650             } else {
651 0         0 $read = "files";
652             }
653             }
654              
655             ###########################
656             # Read Files
657              
658 26 100 100     114 if ($read eq "files" ||
659             $read eq "file") {
660              
661             # valid_file
662              
663 25         31 my($valid_file,$valid_file_re,$valid_file_nre);
664 25 100       80 if (exists $opts{"valid_file"}) {
665 5         7 my $tmp = $opts{"valid_file"};
666 5 100       599 if (ref($tmp) eq "CODE") {
    50          
    100          
667 1         2 $valid_file = $tmp;
668 1         2 $valid_file_re = "";
669 1         2 $valid_file_nre = "";
670             } elsif (ref($tmp)) {
671 0         0 croak "ERROR: Invalid valid_file value\n";
672             } elsif ($tmp =~ s,^!,,) {
673 2         3 $valid_file = "";
674 2         3 $valid_file_re = "";
675 2         5 $valid_file_nre = $tmp;
676             } else {
677 2         3 $valid_file = "";
678 2         2 $valid_file_re = $tmp;
679 2         3 $valid_file_nre = "";
680             }
681             } else {
682 20         32 $valid_file = "";
683 20         22 $valid_file_re = "";
684 20         28 $valid_file_nre = "";
685             }
686              
687 25         32 my %dir;
688 25         48 foreach my $dir (@dir) {
689 37 50       25427 if (! opendir(DIR,$dir)) {
690 0         0 carp "ERROR: Can't read directory: $dir: $!\n";
691 0         0 next;
692             }
693 37         1230 my(@f) = readdir(DIR);
694 37         424 closedir(DIR);
695 37         72 foreach my $f (@f) {
696 155 50 100     2389 next if ($f eq "." ||
      66        
      66        
697             $f eq ".." ||
698             $f =~ /^.set_files/ ||
699             ! -f "$dir/$f");
700 81 100 100     1873 if (($valid_file_nre && $f =~ /$valid_file_nre/) ||
      100        
      66        
      100        
      66        
701             ($valid_file_re && $f !~ /$valid_file_re/) ||
702             ($valid_file && ! &$valid_file($dir,$f))) {
703 10 50       32 warn "WARNING: File fails validity test: $f\n"
704             if (! $invalid_quiet);
705 10         18 next;
706             }
707 71 50       160 if (exists $dir{$f}) {
708 0         0 carp "WARNING: File redefined: $f\n";
709             } else {
710 71         175 $dir{$f} = $dir;
711             }
712             }
713             }
714              
715             # types
716              
717 25         40 my(@types);
718 25 100       69 if (exists $opts{"types"}) {
719 13         29 my $type = $opts{"types"};
720 13 50       44 if (ref($type) eq "ARRAY") {
    0          
721 13         38 @types = @$type;
722             } elsif (ref($type)) {
723 0         0 croak "ERROR: Invalid types value\n";
724             } else {
725 0         0 @types = ($type);
726             }
727             } else {
728 12         23 @types = ("_");
729             }
730              
731             # default_types
732              
733 25         36 my(@def_types);
734 25 100       68 if (exists $opts{"default_types"}) {
735 13         32 my $type = $opts{"default_types"};
736 13 50       874 if (ref($type) eq "ARRAY") {
    50          
    50          
    50          
737 0         0 @def_types = @$type;
738             } elsif (ref($type)) {
739 0         0 croak "ERROR: Invalid default_types value\n";
740             } elsif ($type eq "all") {
741 0         0 @def_types = (@types);
742             } elsif ($type eq "none") {
743 13         26 @def_types = ();
744             } else {
745 0         0 @def_types = ($type);
746             }
747             } else {
748 12         22 @def_types = @types;
749             }
750              
751 25         46 my %tmp = map { $_,1 } @types;
  38         120  
752 25         38 my @tmp;
753 25         44 foreach my $type (@def_types) {
754 12 50       24 if (! exists $tmp{$type}) {
755 0         0 carp "WARNING: Invalid default_types value: $type\n";
756             } else {
757 12         32 push(@tmp,$type);
758             }
759             }
760 25         38 @def_types = @tmp;
761              
762             # comment
763              
764 25         30 my($comment);
765 25 100       60 if (exists $opts{"comment"}) {
766 1         3 $comment = $opts{"comment"};
767             } else {
768 24         40 $comment = "#.*";
769             }
770 25         42 $self{"comment"} = $comment;
771              
772             # tagchars
773              
774 25         32 my($tagchars);
775 25 100       48 if (exists $opts{"tagchars"}) {
776 1         2 $tagchars = $opts{"tagchars"};
777             } else {
778 24         46 $tagchars = '@';
779             }
780 25         45 $self{"tagchars"} = $tagchars;
781              
782             # valid_ele
783              
784 25         29 my($valid_ele,$valid_ele_re,$valid_ele_nre);
785 25 100       56 if (exists $opts{"valid_ele"}) {
786 3         6 my $tmp = $opts{"valid_ele"};
787 3 100       17 if (ref($tmp) eq "CODE") {
    50          
    100          
788 1         2 $valid_ele = $tmp;
789 1         68 $valid_ele_re = "";
790 1         2 $valid_ele_nre = "";
791             } elsif (ref($tmp)) {
792 0         0 croak "ERROR: Invalid valid_ele value\n";
793             } elsif ($tmp =~ s,^!,,) {
794 1         3 $valid_ele = "";
795 1         2 $valid_ele_re = "";
796 1         2 $valid_ele_nre = $tmp;
797             } else {
798 1         2 $valid_ele = "";
799 1         2 $valid_ele_re = $tmp;
800 1         2 $valid_ele_nre = "";
801             }
802             } else {
803 22         30 $valid_ele = "";
804 22         22 $valid_ele_re = "";
805 22         31 $valid_ele_nre = "";
806             }
807              
808             # Read File
809              
810 25 100       151 if ($read eq "file") {
811 3         7 my(@set) = ($set);;
812 3         7 while (@set) {
813 7         13 $set = shift(@set);
814 7 100       19 next if (exists $self{"set"}{$set});
815              
816 6 50       14 if (! exists $dir{$set}) {
817 0         0 croak "ERROR: invalid set to read: $set\n";
818             }
819              
820 6         17 $self{"set"}{$set} = _ReadSet($set,$dir{$set},\@types,\@def_types,
821             $comment,$tagchars,
822             $valid_ele,$valid_ele_re,$valid_ele_nre,
823             $invalid_quiet);
824 6 100       20 push (@set,keys %{ $self{"set"}{$set}{"incl"} })
  3         27  
825             if (exists $self{"set"}{$set}{"incl"});
826 6 100       27 push (@set,keys %{ $self{"set"}{$set}{"excl"} })
  1         5  
827             if (exists $self{"set"}{$set}{"excl"});
828             }
829             }
830              
831             # Read Files
832              
833 25 100       61 if ($read eq "files") {
834 22         62 foreach my $set (keys %dir) {
835 62         196 $self{"set"}{$set} = _ReadSet($set,$dir{$set},\@types,\@def_types,
836             $comment,$tagchars,
837             $valid_ele,$valid_ele_re,$valid_ele_nre,
838             $invalid_quiet);
839             }
840             }
841              
842             # Includes and Excludes
843              
844 25         46 foreach my $set (keys %{ $self{"set"} }) {
  25         94  
845 68 100       176 if (exists $self{"set"}{$set}{"incl"}) {
846 17         21 foreach my $inc (keys %{ $self{"set"}{$set}{"incl"} }) {
  17         48  
847 17 50       67 if (! exists $self{"set"}{$inc}) {
848 0         0 carp "WARNING: Invalid include [ $inc ] in set: $set\n";
849 0         0 delete $self{"set"}{$set}{"incl"}{$inc};
850 0         0 delete $self{"set"}{$set}{"incl"}
851 0 0       0 if (! keys %{ $self{"set"}{$set}{"incl"} });
852             }
853             }
854             }
855              
856 68 100       217 if (exists $self{"set"}{$set}{"excl"}) {
857 14         19 foreach my $exc (keys %{ $self{"set"}{$set}{"excl"} }) {
  14         43  
858 14 50       59 if (! exists $self{"set"}{$exc}) {
859 0         0 carp "WARNING: Invalid exclude [ $exc ] in set: $set\n";
860 0         0 delete $self{"set"}{$set}{"excl"}{$exc};
861 0         0 delete $self{"set"}{$set}{"excl"}
862 0 0       0 if (! keys %{ $self{"set"}{$set}{"excl"} });
863             }
864             }
865             }
866             }
867              
868 25         104 while (1) {
869 40         365 my $flag1 = _ExpandInclude($self{"set"});
870 40         104 my $flag2 = _ExpandExclude($self{"set"});
871 40 50 66     401 last if (! $flag1 && ! $flag2);
872             }
873              
874 25         44 foreach my $set (keys %{ $self{"set"} }) {
  25         63  
875 68 50 33     441 if (exists $self{"set"}{$set}{"excl"} ||
    100          
876             exists $self{"set"}{$set}{"incl"}) {
877 0         0 carp "ERROR: Unresolved (circular) dependancy: $set\n";
878             } elsif (exists $self{"set"}{$set}{"omit"}) {
879 10         12 foreach my $ele (keys %{ $self{"set"}{$set}{"omit"} }) {
  10         31  
880 30         56 delete $self{"set"}{$set}{"ele"}{$ele};
881             }
882 10         34 delete $self{"set"}{$set}{"omit"};
883             }
884             }
885              
886 25 50       48 if (! keys %{ $self{"set"} }) {
  25         139  
887 0         0 croak "ERROR: No set data read.\n";
888             }
889             }
890              
891 26         126 return \%self;
892             }
893              
894             sub _ReadSet {
895 68     68   151 my($set,$dir,$types,$def_types,$comment,$tagchars,
896             $valid_ele,$valid_ele_re,$valid_ele_nre,$invalid_quiet) = @_;
897 68         69 my %set;
898              
899 68         173 $set{"dir"} = $dir;
900              
901 68         454 my $in = new IO::File;
902 68 50       2143 if (! $in->open("$dir/$set")) {
903 0         0 croak "ERROR: Unable to open file: $dir/$set: $!\n";
904             }
905 68         3898 my $uid = ( stat("$dir/$set") )[4];
906 68         145 $set{"owner"} = $uid;
907 68         186 _ReadSetFile($set,$in,\%set,$types,$def_types,$comment,
908             $tagchars,$valid_ele,$valid_ele_re,$valid_ele_nre,
909             $invalid_quiet);
910 68         284 $in->close;
911 68         1398 return \%set;
912             }
913              
914             sub _ReadSetFile {
915 68     68   143 my($set,$in,$self,$types,$def_types,$comment,$tagchars,
916             $valid_ele,$valid_ele_re,$valid_ele_nre,$invalid_quiet)=@_;
917 68         131 my %types = map { $_,1 } @$types;
  105         293  
918 68         125 my %def_types = map { $_,1 } @$def_types;
  31         81  
919 68         186 $$self{"type"} = { %def_types };
920 68         1604 my(@in) = <$in>;
921 68         174 chomp(@in);
922 68         110 foreach my $line (@in) {
923 477         1153 $line =~ s,$comment,,;
924 477         780 $line =~ s,^\s+,,;
925 477         792 $line =~ s,\s+$,,;
926 477 100       981 next if (! $line);
927              
928 430 100       1586 if ($line =~ s,^$tagchars,,) {
929 185         300 $line =~ s,^\s+,,;
930 185 100       1209 if ($line =~ /^include\s+(.+)/i) {
    100          
    100          
    50          
    100          
    100          
    50          
931 17         37 my $tmp = $1;
932 17         54 my @tmp = split(/,/,$tmp);
933 17         28 foreach my $tmp (@tmp) {
934 17         56 $$self{"incl"}{$tmp} = 1;
935 17         86 $$self{"incl0"}{$tmp} = 1;
936             }
937              
938             } elsif ($line =~ /^exclude\s+(.+)/i) {
939 14         28 my $tmp = $1;
940 14         38 my @tmp = split(/,/,$tmp);
941 14         23 foreach my $tmp (@tmp) {
942 14         47 $$self{"excl"}{$tmp} = 1;
943 14         66 $$self{"excl0"}{$tmp} = 1;
944             }
945              
946             } elsif ($line =~ /^type\s+(.+)/i) {
947 50         100 my $tmp = $1;
948 50         121 my @tmp = split(/,/,$tmp);
949 50         70 foreach my $tmp (@tmp) {
950 50 50       169 if (exists $types{$tmp}) {
951 50         100 $$self{"type"}{$tmp} = 1;
952 50         244 $$self{"type0"}{$tmp} = 1;
953             } else {
954 0         0 carp "ERROR: Invalid set type: $set [ $tmp ]\n";
955             }
956             }
957              
958             } elsif ($line =~ /^notype\s+(.+)/i) {
959 0         0 my $tmp = $1;
960 0         0 my @tmp = split(/,/,$tmp);
961 0         0 foreach my $tmp (@tmp) {
962 0 0       0 if (exists $types{$tmp}) {
963 0         0 delete $$self{"type"}{$tmp};
964 0         0 $$self{"notype0"}{$tmp} = 1;
965             } else {
966 0         0 carp "ERROR: Invalid set type: $set [ $tmp ]\n";
967             }
968             }
969              
970             } elsif ($line =~ /^omit\s+(.+)/i) {
971 30         85 $$self{"omit"}{$1} = 1;
972 30         76 $$self{"omit0"}{$1} = 1;
973              
974             } elsif ($line =~ /^option\s+(.+?)\s*=\s*(.*)/i) {
975 47         161 my($var,$val)=($1,$2);
976 47 50       91 $val=0 if (! $val);
977 47         299 $$self{"opts"}{$var} = $val;
978              
979             } elsif ($line =~ /^option\s+(.+)/i) {
980 27         118 $$self{"opts"}{$1} = 1;
981              
982             } else {
983 0         0 carp "ERROR: Invalid tag line: $set: $line\n";
984             }
985              
986             } else {
987 245 100 100     1622 if (($valid_ele_nre && $line =~ /$valid_ele_nre/) ||
      100        
      66        
      100        
      66        
988             ($valid_ele_re && $line !~ /$valid_ele_re/) ||
989             ($valid_ele && ! &$valid_ele($set,$line))) {
990 24 50       103 warn "WARNING: Element fails validity test: $line\n"
991             if (! $invalid_quiet);
992 24         107 next;
993             }
994 221         967 $$self{"ele"}{$line} = 1;
995             }
996             }
997             }
998              
999             sub _ExpandInclude {
1000 40     40   55 my($self)=@_;
1001 40         47 my $prog = 0; # overall progress
1002              
1003 40         48 my %inc;
1004             my %exc;
1005 40         86 foreach my $set (keys %$self) {
1006 112 100       266 $inc{$set} = 1 if (exists $$self{$set}{"incl"});
1007 112 100       282 $exc{$set} = 1 if (exists $$self{$set}{"excl"});
1008             }
1009              
1010 40         69 while (1) {
1011 55 100       132 last if (! keys %inc);
1012 15         18 my $progress = 0; # progress this iteration
1013              
1014 15         28 foreach my $set (keys %inc) {
1015 17         21 foreach my $inc (keys %{ $$self{$set}{"incl"} }) {
  17         44  
1016 17 50 33     92 next if (exists $inc{$inc} ||
1017             exists $exc{$inc});
1018 17         28 $prog = $progress = 1;
1019              
1020 17         23 foreach my $ele (keys %{ $$self{$inc}{"ele"} }) {
  17         56  
1021 53 100       185 $$self{$set}{"ele"}{$ele} = 2
1022             if (! exists $$self{$set}{"ele"}{$ele});
1023             }
1024              
1025 17         42 delete $inc{$set};
1026 17         90 delete $$self{$set}{"incl"}{$inc};
1027 17 50       21 delete $$self{$set}{"incl"} if (! keys %{ $$self{$set}{"incl"} });
  17         96  
1028             }
1029             }
1030 15 50       42 next if ($progress);
1031 0         0 last;
1032             }
1033 40         97 return $prog;
1034             }
1035              
1036             sub _ExpandExclude {
1037 40     40   53 my($self)=@_;
1038 40         43 my $prog = 0;
1039              
1040 40         47 my %inc;
1041             my %exc;
1042 40         82 foreach my $set (keys %$self) {
1043 112 50       259 $inc{$set} = 1 if (exists $$self{$set}{"incl"});
1044 112 100       308 $exc{$set} = 1 if (exists $$self{$set}{"excl"});
1045             }
1046              
1047 40         61 while (1) {
1048 54 100       127 last if (! keys %exc);
1049 14         18 my $progress = 0; # progress this iteration
1050              
1051 14         27 foreach my $set (keys %exc) {
1052 14 50       31 next if (exists $inc{$set}); # only exclude after all includes
1053 14         15 foreach my $exc (keys %{ $$self{$set}{"excl"} }) {
  14         43  
1054 14 50 33     85 next if (exists $inc{$exc} ||
1055             exists $exc{$exc});
1056 14         52 $prog = $progress = 1;
1057              
1058 14         18 foreach my $ele (keys %{ $$self{$exc}{"ele"} }) {
  14         45  
1059             # We don't want to exclude elements that are explicitly included
1060             # in the set file.
1061 52 100 100     318 delete $$self{$set}{"ele"}{$ele}
1062             if (exists $$self{$set}{"ele"}{$ele} &&
1063             $$self{$set}{"ele"}{$ele} == 2);
1064             }
1065              
1066 14         31 delete $exc{$set};
1067 14         31 delete $$self{$set}{"excl"}{$exc};
1068 14 50       20 delete $$self{$set}{"excl"} if (! keys %{ $$self{$set}{"excl"} });
  14         85  
1069             }
1070             }
1071 14 50       5850 next if ($progress);
1072 0         0 last;
1073             }
1074 40         90 return $prog;
1075             }
1076              
1077             ########################################################################
1078              
1079             1;
1080             # Local Variables:
1081             # mode: cperl
1082             # indent-tabs-mode: nil
1083             # cperl-indent-level: 3
1084             # cperl-continued-statement-offset: 2
1085             # cperl-continued-brace-offset: 0
1086             # cperl-brace-offset: 0
1087             # cperl-brace-imaginary-offset: 0
1088             # cperl-label-offset: -2
1089             # End: