File Coverage

blib/lib/Sys/Export/VFAT/Directory.pm
Criterion Covered Total %
statement 113 115 98.2
branch 23 32 71.8
condition 4 8 50.0
subroutine 23 23 100.0
pod 14 14 100.0
total 177 192 92.1


line stmt bran cond sub pod time code
1             package Sys::Export::VFAT::Directory;
2              
3             our $VERSION = '0.005_002'; # TRIAL VERSION
4             # ABSTRACT: Represents a case-folded directory in VFAT
5              
6              
7 3     3   829 use v5.26;
  3         8  
8 3     3   9 use warnings;
  3         3  
  3         131  
9 3     3   9 use experimental qw( signatures );
  3         3  
  3         14  
10 3     3   337 use Sys::Export::LogAny '$log';
  3         3  
  3         32  
11 3     3   660 use Encode ();
  3         5  
  3         36  
12 3     3   1014 use Sys::Export::VFAT;
  3         6  
  3         126  
13 3     3   13 use Scalar::Util qw( weaken );
  3         3  
  3         103  
14 3     3   11 use List::Util qw( min max );
  3         27  
  3         140  
15 3     3   32 use Carp;
  3         93  
  3         3763  
16             our @CARP_NOT= qw( Sys::Export::VFAT );
17              
18              
19 70     70 1 250 sub new($class, %attrs) {
  70         76  
  70         173  
  70         68  
20             my $self= bless {
21             name => delete $attrs{name},
22             parent => delete $attrs{parent},
23             file => delete $attrs{file},
24 70   50     429 entries => delete $attrs{entries} // [],
25             ent_by_name => {},
26             }, $class;
27 70 50       138 croak "Unknown constructor option ".join(', ', keys %attrs) if keys %attrs;
28 70 100       191 weaken($self->{parent}) if $self->{parent};
29 70         133 for ($self->entries->@*) {
30 0         0 $self->ent_by_name->{$_->{name}}= $_->{name};
31 0 0       0 $self->ent_by_name->{$_->{shortname}}= $_->{shortname} if defined $_->{shortname};
32             }
33 70         132 $self;
34             }
35              
36              
37 104     104 1 346 sub name { $_[0]{name} }
38 39     39 1 100 sub parent { $_[0]{parent} }
39 138     138 1 464 sub is_root { !defined $_[0]{parent} }
40 11026     11026 1 23467 sub file { $_[0]{file} }
41 10932     10932 1 22574 sub entries { $_[0]{entries} }
42 10661     10661 1 14472 sub ent_by_name { $_[0]{ent_by_name} }
43              
44              
45             sub entry {
46 98363     98363 1 149999 $_[0]{ent_by_name}{lc $_[1]}
47             }
48              
49              
50 10552     10552 1 11129 sub add($self, $name, $file, %attrs) {
  10552         10660  
  10552         10727  
  10552         10171  
  10552         20547  
  10552         11650  
51 10552 50       16904 croak "Invalid long name" unless $self->is_valid_name($name);
52 10552         16075 $attrs{name}= $name;
53 10552         14709 $attrs{file}= $file;
54 10552 100 33     16747 $attrs{shortname} //= $name if $self->is_valid_shortname($name);
55             # any conflict?
56 10552         17167 my $by_name= $self->ent_by_name;
57             croak "Path ".$self->name."/$name already exists"
58 10552 50       23217 if defined $by_name->{lc $name};
59 10552 100       17698 if (defined $attrs{shortname}) {
60 10516         20740 utf8::downgrade($attrs{shortname}); # must be bytes
61 10516         28938 my $slot= \$by_name->{lc $attrs{shortname}};
62             croak "Path ".$self->name."/$name short name '$attrs{shortname}' conflicts with "
63             . $self->name."/".$$slot->{name}
64 10516 50       14768 if $$slot;
65 10516         13188 $$slot= \%attrs;
66             }
67 10552         17283 $by_name->{lc $name}= \%attrs;
68 10552         15467 push $self->entries->@*, \%attrs;
69 10552         18709 \%attrs;
70             }
71              
72              
73             # These 3 can be overridden for ISO9660 subclass
74 10550     10550 1 11068 sub is_valid_name($self, $name) {
  10550         10209  
  10550         11903  
  10550         9448  
75 10550         18530 Sys::Export::VFAT::is_valid_longname($name)
76             }
77 10586     10586 1 10612 sub is_valid_shortname($self, $name) {
  10586         10168  
  10586         10393  
  10586         9481  
78 10586         15638 Sys::Export::VFAT::is_valid_shortname($name)
79             }
80 72     72 1 67 sub remove_invalid_shortname_chars($self, $name, $repl) {
  72         69  
  72         75  
  72         73  
  72         59  
81 72         101 Sys::Export::VFAT::remove_invalid_shortname_chars($name, $repl)
82             }
83              
84              
85 36     36 1 39 sub find_unused_shortname($self, $name) {
  36         40  
  36         42  
  36         32  
86 36 50       49 length $name or croak "name cannot be empty";
87 36         49 my $by_name= $self->ent_by_name;
88 36         70 my $ext_pos= rindex($name, '.');
89 36 100       81 my $base= $ext_pos < 0? $name : substr($name, 0, $ext_pos);
90 36 100       57 my $ext= $ext_pos < 0? '' : substr($name, $ext_pos+1);
91 36         47 for ($base, $ext) {
92 72         98 $_= $self->remove_invalid_shortname_chars($_, '_');
93             # Now that all high characters have been removed, optimize these as bytes
94 72         146 utf8::downgrade($_);
95             }
96 36 100       55 $ext= '.'.substr($ext,0,3) if length $ext;
97 36         55 my ($iter, $iter_len, $base_len)= (0,0, length $base);
98 36 100 66     116 if (!$base_len || $base_len > 8) {
99 1         3 substr($base, min($base_len,6), $base_len, '~1');
100 1         3 ($iter, $iter_len)= (1, 2);
101             }
102 36         88 while ($by_name->{lc $base.$ext}) {
103 36         53 my $next_iter_len= 1 + length ++$iter;
104 36         62 my $iter_pos= min($base_len, 8 - $next_iter_len);
105 36 50       59 croak "Can't find available ~N suffix for '$name'"
106             if $iter_pos < 0;
107 36         62 substr($base, $iter_pos, $next_iter_len, '~'.$iter);
108 36         59 $iter_len= $next_iter_len;
109             }
110 36 50       71 $self->is_valid_shortname($base.$ext) or die "BUG: '$base$ext' is not a valid shortname";
111 36         80 return $base.$ext;
112             }
113              
114              
115 73     73 1 81 sub build_shortnames($self) {
  73         83  
  73         71  
116 73         153 my $by_name= $self->ent_by_name;
117 73         121 for (sort { lc $a->{name} cmp lc $b->{name} } $self->entries->@*) {
  22567         38667  
118 10553 100       16489 unless (defined $_->{shortname}) {
119 36         77 $_->{shortname}= $self->find_unused_shortname($_->{name});
120 36         84 $by_name->{lc $_->{shortname}}= $_;
121             }
122             }
123 73         138 return $self;
124             }
125              
126             # Avoiding dependency on namespace::clean
127             delete @{Sys::Export::VFAT::Directory::}{qw( carp confess croak min max weaken )};
128             1;
129              
130             __END__