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'; # VERSION
4             # ABSTRACT: Represents a case-folded directory in VFAT
5              
6              
7 3     3   874 use v5.26;
  3         9  
8 3     3   9 use warnings;
  3         5  
  3         113  
9 3     3   10 use experimental qw( signatures );
  3         5  
  3         13  
10 3     3   339 use Sys::Export::LogAny '$log';
  3         3  
  3         64  
11 3     3   616 use Encode ();
  3         5  
  3         45  
12 3     3   1054 use Sys::Export::VFAT;
  3         6  
  3         117  
13 3     3   11 use Scalar::Util qw( weaken );
  3         4  
  3         113  
14 3     3   11 use List::Util qw( min max );
  3         3  
  3         133  
15 3     3   11 use Carp;
  3         82  
  3         3604  
16             our @CARP_NOT= qw( Sys::Export::VFAT );
17              
18              
19 70     70 1 229 sub new($class, %attrs) {
  70         67  
  70         168  
  70         75  
20             my $self= bless {
21             name => delete $attrs{name},
22             parent => delete $attrs{parent},
23             file => delete $attrs{file},
24 70   50     472 entries => delete $attrs{entries} // [],
25             ent_by_name => {},
26             }, $class;
27 70 50       139 croak "Unknown constructor option ".join(', ', keys %attrs) if keys %attrs;
28 70 100       165 weaken($self->{parent}) if $self->{parent};
29 70         128 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 306 sub name { $_[0]{name} }
38 39     39 1 76 sub parent { $_[0]{parent} }
39 138     138 1 488 sub is_root { !defined $_[0]{parent} }
40 11026     11026 1 26846 sub file { $_[0]{file} }
41 10932     10932 1 21106 sub entries { $_[0]{entries} }
42 10661     10661 1 16661 sub ent_by_name { $_[0]{ent_by_name} }
43              
44              
45             sub entry {
46 98363     98363 1 171196 $_[0]{ent_by_name}{lc $_[1]}
47             }
48              
49              
50 10552     10552 1 10541 sub add($self, $name, $file, %attrs) {
  10552         10983  
  10552         10459  
  10552         9780  
  10552         21939  
  10552         11117  
51 10552 50       16055 croak "Invalid long name" unless $self->is_valid_name($name);
52 10552         17276 $attrs{name}= $name;
53 10552         14119 $attrs{file}= $file;
54 10552 100 33     15392 $attrs{shortname} //= $name if $self->is_valid_shortname($name);
55             # any conflict?
56 10552         15670 my $by_name= $self->ent_by_name;
57             croak "Path ".$self->name."/$name already exists"
58 10552 50       25059 if defined $by_name->{lc $name};
59 10552 100       17176 if (defined $attrs{shortname}) {
60 10516         22207 utf8::downgrade($attrs{shortname}); # must be bytes
61 10516         24254 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       15318 if $$slot;
65 10516         14112 $$slot= \%attrs;
66             }
67 10552         19229 $by_name->{lc $name}= \%attrs;
68 10552         15013 push $self->entries->@*, \%attrs;
69 10552         18898 \%attrs;
70             }
71              
72              
73             # These 3 can be overridden for ISO9660 subclass
74 10550     10550 1 11130 sub is_valid_name($self, $name) {
  10550         9973  
  10550         11899  
  10550         9419  
75 10550         16849 Sys::Export::VFAT::is_valid_longname($name)
76             }
77 10586     10586 1 10646 sub is_valid_shortname($self, $name) {
  10586         9988  
  10586         10334  
  10586         9972  
78 10586         14573 Sys::Export::VFAT::is_valid_shortname($name)
79             }
80 72     72 1 77 sub remove_invalid_shortname_chars($self, $name, $repl) {
  72         84  
  72         87  
  72         83  
  72         74  
81 72         141 Sys::Export::VFAT::remove_invalid_shortname_chars($name, $repl)
82             }
83              
84              
85 36     36 1 64 sub find_unused_shortname($self, $name) {
  36         43  
  36         47  
  36         43  
86 36 50       64 length $name or croak "name cannot be empty";
87 36         59 my $by_name= $self->ent_by_name;
88 36         87 my $ext_pos= rindex($name, '.');
89 36 100       74 my $base= $ext_pos < 0? $name : substr($name, 0, $ext_pos);
90 36 100       92 my $ext= $ext_pos < 0? '' : substr($name, $ext_pos+1);
91 36         57 for ($base, $ext) {
92 72         120 $_= $self->remove_invalid_shortname_chars($_, '_');
93             # Now that all high characters have been removed, optimize these as bytes
94 72         147 utf8::downgrade($_);
95             }
96 36 100       68 $ext= '.'.substr($ext,0,3) if length $ext;
97 36         68 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         4 ($iter, $iter_len)= (1, 2);
101             }
102 36         124 while ($by_name->{lc $base.$ext}) {
103 36         56 my $next_iter_len= 1 + length ++$iter;
104 36         92 my $iter_pos= min($base_len, 8 - $next_iter_len);
105 36 50       64 croak "Can't find available ~N suffix for '$name'"
106             if $iter_pos < 0;
107 36         76 substr($base, $iter_pos, $next_iter_len, '~'.$iter);
108 36         73 $iter_len= $next_iter_len;
109             }
110 36 50       78 $self->is_valid_shortname($base.$ext) or die "BUG: '$base$ext' is not a valid shortname";
111 36         116 return $base.$ext;
112             }
113              
114              
115 73     73 1 85 sub build_shortnames($self) {
  73         86  
  73         76  
116 73         123 my $by_name= $self->ent_by_name;
117 73         133 for (sort { lc $a->{name} cmp lc $b->{name} } $self->entries->@*) {
  22567         33696  
118 10553 100       16235 unless (defined $_->{shortname}) {
119 36         93 $_->{shortname}= $self->find_unused_shortname($_->{name});
120 36         99 $by_name->{lc $_->{shortname}}= $_;
121             }
122             }
123 73         152 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__