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.006'; # VERSION
4             # ABSTRACT: Represents a case-folded directory in VFAT
5              
6              
7 3     3   1011 use v5.26;
  3         9  
8 3     3   17 use warnings;
  3         5  
  3         137  
9 3     3   14 use experimental qw( signatures );
  3         4  
  3         15  
10 3     3   335 use Sys::Export::LogAny '$log';
  3         13  
  3         23  
11 3     3   501 use Encode ();
  3         12  
  3         53  
12 3     3   1155 use Sys::Export::VFAT;
  3         6  
  3         127  
13 3     3   13 use Scalar::Util qw( weaken );
  3         3  
  3         102  
14 3     3   11 use List::Util qw( min max );
  3         4  
  3         137  
15 3     3   14 use Carp;
  3         7  
  3         3998  
16             our @CARP_NOT= qw( Sys::Export::VFAT );
17              
18              
19 70     70 1 211 sub new($class, %attrs) {
  70         88  
  70         137  
  70         75  
20             my $self= bless {
21             name => delete $attrs{name},
22             parent => delete $attrs{parent},
23             file => delete $attrs{file},
24 70   50     366 entries => delete $attrs{entries} // [],
25             ent_by_name => {},
26             }, $class;
27 70 50       157 croak "Unknown constructor option ".join(', ', keys %attrs) if keys %attrs;
28 70 100       172 weaken($self->{parent}) if $self->{parent};
29 70         163 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         127 $self;
34             }
35              
36              
37 104     104 1 315 sub name { $_[0]{name} }
38 39     39 1 60 sub parent { $_[0]{parent} }
39 138     138 1 393 sub is_root { !defined $_[0]{parent} }
40 11026     11026 1 22294 sub file { $_[0]{file} }
41 10932     10932 1 19070 sub entries { $_[0]{entries} }
42 10661     10661 1 14156 sub ent_by_name { $_[0]{ent_by_name} }
43              
44              
45             sub entry {
46 98363     98363 1 147896 $_[0]{ent_by_name}{lc $_[1]}
47             }
48              
49              
50 10552     10552 1 10811 sub add($self, $name, $file, %attrs) {
  10552         10031  
  10552         10420  
  10552         9222  
  10552         21268  
  10552         10082  
51 10552 50       15957 croak "Invalid long name" unless $self->is_valid_name($name);
52 10552         15611 $attrs{name}= $name;
53 10552         14011 $attrs{file}= $file;
54 10552 100 33     15196 $attrs{shortname} //= $name if $self->is_valid_shortname($name);
55             # any conflict?
56 10552         15144 my $by_name= $self->ent_by_name;
57             croak "Path ".$self->name."/$name already exists"
58 10552 50       21698 if defined $by_name->{lc $name};
59 10552 100       14290 if (defined $attrs{shortname}) {
60 10516         18264 utf8::downgrade($attrs{shortname}); # must be bytes
61 10516         21383 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       15857 if $$slot;
65 10516         12104 $$slot= \%attrs;
66             }
67 10552         17537 $by_name->{lc $name}= \%attrs;
68 10552         14462 push $self->entries->@*, \%attrs;
69 10552         16023 \%attrs;
70             }
71              
72              
73             # These 3 can be overridden for ISO9660 subclass
74 10550     10550 1 10139 sub is_valid_name($self, $name) {
  10550         9256  
  10550         9870  
  10550         9236  
75 10550         15822 Sys::Export::VFAT::is_valid_longname($name)
76             }
77 10586     10586 1 10397 sub is_valid_shortname($self, $name) {
  10586         10466  
  10586         9800  
  10586         9272  
78 10586         14731 Sys::Export::VFAT::is_valid_shortname($name)
79             }
80 72     72 1 66 sub remove_invalid_shortname_chars($self, $name, $repl) {
  72         66  
  72         65  
  72         59  
  72         72  
81 72         109 Sys::Export::VFAT::remove_invalid_shortname_chars($name, $repl)
82             }
83              
84              
85 36     36 1 36 sub find_unused_shortname($self, $name) {
  36         38  
  36         55  
  36         33  
86 36 50       60 length $name or croak "name cannot be empty";
87 36         58 my $by_name= $self->ent_by_name;
88 36         67 my $ext_pos= rindex($name, '.');
89 36 100       62 my $base= $ext_pos < 0? $name : substr($name, 0, $ext_pos);
90 36 100       47 my $ext= $ext_pos < 0? '' : substr($name, $ext_pos+1);
91 36         40 for ($base, $ext) {
92 72         90 $_= $self->remove_invalid_shortname_chars($_, '_');
93             # Now that all high characters have been removed, optimize these as bytes
94 72         95 utf8::downgrade($_);
95             }
96 36 100       51 $ext= '.'.substr($ext,0,3) if length $ext;
97 36         59 my ($iter, $iter_len, $base_len)= (0,0, length $base);
98 36 100 66     96 if (!$base_len || $base_len > 8) {
99 1         4 substr($base, min($base_len,6), $base_len, '~1');
100 1         2 ($iter, $iter_len)= (1, 2);
101             }
102 36         91 while ($by_name->{lc $base.$ext}) {
103 36         46 my $next_iter_len= 1 + length ++$iter;
104 36         65 my $iter_pos= min($base_len, 8 - $next_iter_len);
105 36 50       52 croak "Can't find available ~N suffix for '$name'"
106             if $iter_pos < 0;
107 36         53 substr($base, $iter_pos, $next_iter_len, '~'.$iter);
108 36         67 $iter_len= $next_iter_len;
109             }
110 36 50       56 $self->is_valid_shortname($base.$ext) or die "BUG: '$base$ext' is not a valid shortname";
111 36         79 return $base.$ext;
112             }
113              
114              
115 73     73 1 74 sub build_shortnames($self) {
  73         79  
  73         65  
116 73         123 my $by_name= $self->ent_by_name;
117 73         107 for (sort { lc $a->{name} cmp lc $b->{name} } $self->entries->@*) {
  22567         37022  
118 10553 100       14265 unless (defined $_->{shortname}) {
119 36         61 $_->{shortname}= $self->find_unused_shortname($_->{name});
120 36         73 $by_name->{lc $_->{shortname}}= $_;
121             }
122             }
123 73         128 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__