line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package File::DataClass::Functions; |
2
|
|
|
|
|
|
|
|
3
|
7
|
|
|
7
|
|
120623
|
use 5.010001; |
|
7
|
|
|
|
|
20
|
|
4
|
7
|
|
|
7
|
|
32
|
use strict; |
|
7
|
|
|
|
|
9
|
|
|
7
|
|
|
|
|
129
|
|
5
|
7
|
|
|
7
|
|
25
|
use warnings; |
|
7
|
|
|
|
|
9
|
|
|
7
|
|
|
|
|
185
|
|
6
|
|
|
|
|
|
|
|
7
|
7
|
|
|
7
|
|
24
|
use English qw( -no_match_vars ); |
|
7
|
|
|
|
|
8
|
|
|
7
|
|
|
|
|
43
|
|
8
|
7
|
|
|
7
|
|
2264
|
use Exporter 5.57 qw( import ); |
|
7
|
|
|
|
|
93
|
|
|
7
|
|
|
|
|
196
|
|
9
|
7
|
|
|
|
|
399
|
use File::DataClass::Constants qw( CYGWIN EXCEPTION_CLASS MSOFT STORAGE_BASE |
10
|
7
|
|
|
7
|
|
329
|
STORAGE_EXCEPTIONS ); |
|
7
|
|
|
|
|
11
|
|
11
|
7
|
|
|
7
|
|
3256
|
use Hash::Merge qw( merge ); |
|
7
|
|
|
|
|
12822
|
|
|
7
|
|
|
|
|
402
|
|
12
|
7
|
|
|
7
|
|
39
|
use List::Util qw( first ); |
|
7
|
|
|
|
|
10
|
|
|
7
|
|
|
|
|
411
|
|
13
|
7
|
|
|
7
|
|
3265
|
use Module::Pluggable::Object; |
|
7
|
|
|
|
|
23857
|
|
|
7
|
|
|
|
|
220
|
|
14
|
7
|
|
|
7
|
|
100
|
use Module::Runtime qw( require_module ); |
|
7
|
|
|
|
|
9
|
|
|
7
|
|
|
|
|
35
|
|
15
|
7
|
|
|
7
|
|
275
|
use Scalar::Util qw( blessed ); |
|
7
|
|
|
|
|
10
|
|
|
7
|
|
|
|
|
328
|
|
16
|
7
|
|
|
7
|
|
33
|
use Try::Tiny; |
|
7
|
|
|
|
|
12
|
|
|
7
|
|
|
|
|
351
|
|
17
|
7
|
|
|
7
|
|
31
|
use Unexpected::Functions qw( is_class_loaded Unspecified ); |
|
7
|
|
|
|
|
8
|
|
|
7
|
|
|
|
|
62
|
|
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
our @EXPORT_OK = qw( ensure_class_loaded extension_map first_char |
20
|
|
|
|
|
|
|
is_arrayref is_coderef is_hashref is_member is_mswin |
21
|
|
|
|
|
|
|
is_ntfs is_stale qualify_storage_class |
22
|
|
|
|
|
|
|
map_extension2class merge_attributes merge_file_data |
23
|
|
|
|
|
|
|
merge_for_update supported_extensions thread_id throw ); |
24
|
|
|
|
|
|
|
our %EXPORT_TAGS = ( all => [ @EXPORT_OK ], ); |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
my $LC_OSNAME = lc $OSNAME; |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
# Private functions |
29
|
|
|
|
|
|
|
my $_merge_attr; |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
my $_merge_attr_arrays = sub { |
32
|
|
|
|
|
|
|
my ($to, $from) = @_; my $updated = 0; |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
for (0 .. $#{ $to }) { |
35
|
|
|
|
|
|
|
if (defined $from->[ $_ ]) { |
36
|
|
|
|
|
|
|
my $res = $_merge_attr->( \$to->[ $_ ], $from->[ $_ ] ); |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
$updated ||= $res; |
39
|
|
|
|
|
|
|
} |
40
|
|
|
|
|
|
|
elsif ($to->[ $_ ]) { splice @{ $to }, $_; $updated = 1; last } |
41
|
|
|
|
|
|
|
} |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
if (@{ $from } > @{ $to }) { |
44
|
|
|
|
|
|
|
push @{ $to }, (splice @{ $from }, $#{ $to } + 1); $updated = 1; |
45
|
|
|
|
|
|
|
} |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
return $updated; |
48
|
|
|
|
|
|
|
}; |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
my $_merge_attr_hashes = sub { |
51
|
|
|
|
|
|
|
my ($to, $from) = @_; my $updated = 0; |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
for (grep { exists $from->{ $_ } } keys %{ $to }) { |
54
|
|
|
|
|
|
|
if (defined $from->{ $_ }) { |
55
|
|
|
|
|
|
|
my $res = $_merge_attr->( \$to->{ $_ }, $from->{ $_ } ); |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
$updated ||= $res; |
58
|
|
|
|
|
|
|
} |
59
|
|
|
|
|
|
|
else { delete $to->{ $_ }; delete $from->{ $_ }; $updated = 1 } |
60
|
|
|
|
|
|
|
} |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
for (grep { not exists $to->{ $_ } } keys %{ $from }) { |
63
|
|
|
|
|
|
|
if (defined $from->{ $_ }) { |
64
|
|
|
|
|
|
|
$to->{ $_ } = $from->{ $_ }; $updated = 1; |
65
|
|
|
|
|
|
|
} |
66
|
|
|
|
|
|
|
} |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
return $updated; |
69
|
|
|
|
|
|
|
}; |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
$_merge_attr = sub { |
72
|
|
|
|
|
|
|
my ($to_ref, $from) = @_; my $to = ${ $to_ref }; my $updated = 0; |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
if ($to and ref $to eq 'HASH') { |
75
|
|
|
|
|
|
|
$updated = $_merge_attr_hashes->( $to, $from ); |
76
|
|
|
|
|
|
|
} |
77
|
|
|
|
|
|
|
elsif ($to and ref $to eq 'ARRAY') { |
78
|
|
|
|
|
|
|
$updated = $_merge_attr_arrays->( $to, $from ); |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
elsif (defined $to and $to ne $from) { |
81
|
|
|
|
|
|
|
$updated = 1; ${ $to_ref } = $from; |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
elsif (not defined $to) { |
84
|
|
|
|
|
|
|
if (ref $from eq 'HASH') { |
85
|
|
|
|
|
|
|
scalar keys %{ $from } > 0 and $updated = 1 |
86
|
|
|
|
|
|
|
and ${ $to_ref } = $from; |
87
|
|
|
|
|
|
|
} |
88
|
|
|
|
|
|
|
elsif (ref $from eq 'ARRAY') { |
89
|
|
|
|
|
|
|
scalar @{ $from } > 0 and $updated = 1 and ${ $to_ref } = $from; |
90
|
|
|
|
|
|
|
} |
91
|
|
|
|
|
|
|
else { $updated = 1; ${ $to_ref } = $from } |
92
|
|
|
|
|
|
|
} |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
return $updated; |
95
|
|
|
|
|
|
|
}; |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
# Public functions |
98
|
|
|
|
|
|
|
sub ensure_class_loaded ($;$) { |
99
|
42
|
|
100
|
42
|
1
|
11295
|
my ($class, $opts) = @_; $opts //= {}; |
|
42
|
|
|
|
|
171
|
|
100
|
|
|
|
|
|
|
|
101
|
42
|
100
|
100
|
|
|
242
|
not $opts->{ignore_loaded} and is_class_loaded( $class ) and return 1; |
102
|
|
|
|
|
|
|
|
103
|
9
|
|
|
6
|
|
411
|
try { require_module( $class ) } catch { throw( $_ ) }; |
|
9
|
|
|
|
|
408
|
|
|
1
|
|
|
|
|
440
|
|
104
|
|
|
|
|
|
|
|
105
|
8
|
100
|
|
|
|
3694
|
is_class_loaded( $class ) |
106
|
|
|
|
|
|
|
or throw( 'Class [_1] loaded but package undefined', [ $class ] ); |
107
|
|
|
|
|
|
|
|
108
|
7
|
|
|
|
|
203
|
return 1; |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
{ my $_extension_map = { '_map_loaded' => 0 }; |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
sub extension_map (;$$) { |
114
|
17
|
|
|
17
|
1
|
1942
|
my ($class, $extensions) = @_; |
115
|
|
|
|
|
|
|
|
116
|
17
|
100
|
|
|
|
51
|
if (defined $class) { |
117
|
5
|
50
|
|
|
|
17
|
if (defined $extensions) { # uncoverable branch false |
118
|
5
|
100
|
|
|
|
18
|
is_arrayref( $extensions ) or $extensions = [ $extensions ]; |
119
|
|
|
|
|
|
|
|
120
|
5
|
|
|
|
|
11
|
for my $extn (@{ $extensions }) { |
|
5
|
|
|
|
|
12
|
|
121
|
6
|
|
100
|
|
|
32
|
$_extension_map->{ $extn } //= []; |
122
|
|
|
|
|
|
|
is_member( $class, $_extension_map->{ $extn } ) |
123
|
6
|
100
|
|
|
|
20
|
or push @{ $_extension_map->{ $extn } }, $class; |
|
5
|
|
|
|
|
24
|
|
124
|
|
|
|
|
|
|
} |
125
|
|
|
|
|
|
|
} |
126
|
|
|
|
|
|
|
|
127
|
5
|
|
|
|
|
12
|
return; |
128
|
|
|
|
|
|
|
} |
129
|
|
|
|
|
|
|
|
130
|
12
|
100
|
|
|
|
59
|
$_extension_map->{ '_map_loaded' } and return $_extension_map; |
131
|
|
|
|
|
|
|
|
132
|
3
|
|
|
|
|
6
|
my $base = STORAGE_BASE; |
133
|
3
|
|
|
|
|
5
|
my $exceptions = STORAGE_EXCEPTIONS; |
134
|
3
|
|
|
|
|
36
|
my $finder = Module::Pluggable::Object->new |
135
|
|
|
|
|
|
|
( except => [ $exceptions ], search_path => [ $base ], require => 1, ); |
136
|
|
|
|
|
|
|
|
137
|
3
|
|
|
|
|
32
|
$finder->plugins; $_extension_map->{ '_map_loaded' } = 1; |
|
3
|
|
|
|
|
7772
|
|
138
|
|
|
|
|
|
|
|
139
|
3
|
|
|
|
|
60
|
return $_extension_map; |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
sub first_char ($) { |
144
|
1499
|
|
|
1499
|
1
|
4321
|
return substr $_[ 0 ], 0, 1; |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
sub is_arrayref (;$) { |
148
|
737
|
100
|
100
|
737
|
1
|
3125
|
return $_[ 0 ] && ref $_[ 0 ] eq 'ARRAY' ? 1 : 0; |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
sub is_coderef (;$) { |
152
|
647
|
100
|
100
|
647
|
1
|
3217
|
return $_[ 0 ] && ref $_[ 0 ] eq 'CODE' ? 1 : 0; |
153
|
|
|
|
|
|
|
} |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
sub is_hashref (;$) { |
156
|
1025
|
100
|
100
|
1025
|
1
|
5719
|
return $_[ 0 ] && ref $_[ 0 ] eq 'HASH' ? 1 : 0; |
157
|
|
|
|
|
|
|
} |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
sub is_member (;@) { |
160
|
34
|
100
|
|
34
|
1
|
59
|
my ($candidate, @args) = @_; $candidate or return; |
|
34
|
|
|
|
|
69
|
|
161
|
|
|
|
|
|
|
|
162
|
33
|
100
|
|
|
|
66
|
is_arrayref $args[ 0 ] and @args = @{ $args[ 0 ] }; |
|
31
|
|
|
|
|
97
|
|
163
|
|
|
|
|
|
|
|
164
|
33
|
100
|
|
24
|
|
190
|
return (first { $_ eq $candidate } @args) ? 1 : 0; |
|
24
|
|
|
|
|
129
|
|
165
|
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
sub is_mswin () { |
168
|
544
|
50
|
|
544
|
1
|
4228
|
return $LC_OSNAME eq MSOFT ? 1 : 0; |
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
sub is_ntfs () { |
172
|
505
|
50
|
33
|
505
|
1
|
2330
|
return is_mswin || $LC_OSNAME eq CYGWIN ? 1 : 0; |
173
|
|
|
|
|
|
|
} |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
sub is_stale (;$$$) { |
176
|
86
|
|
|
86
|
1
|
95
|
my ($data, $cache_mtime, $path_mtime) = @_; |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
# Assume NTFS does not support mtime |
179
|
86
|
50
|
|
|
|
165
|
is_ntfs() and return 1; # uncoverable branch true |
180
|
|
|
|
|
|
|
|
181
|
86
|
|
100
|
|
|
392
|
my $is_def = defined $data && defined $path_mtime && defined $cache_mtime; |
182
|
|
|
|
|
|
|
|
183
|
86
|
100
|
100
|
|
|
407
|
return (!$is_def || ($path_mtime > $cache_mtime)) ? 1 : 0; |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
sub map_extension2class ($) { |
187
|
8
|
|
|
8
|
1
|
292
|
my $map = extension_map(); |
188
|
|
|
|
|
|
|
|
189
|
8
|
100
|
|
|
|
74
|
return exists $map->{ $_[ 0 ] } ? $map->{ $_[ 0 ] } : undef; |
190
|
|
|
|
|
|
|
} |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
sub merge_attributes ($$;$) { |
193
|
7
|
|
|
7
|
1
|
368
|
my ($dest, $src, $attrs) = @_; my $class = blessed $src; |
|
7
|
|
|
|
|
25
|
|
194
|
|
|
|
|
|
|
|
195
|
7
|
|
66
|
|
|
8
|
for (grep { not exists $dest->{ $_ } or not defined $dest->{ $_ } } |
|
14
|
|
|
|
|
39
|
|
196
|
7
|
100
|
|
|
|
31
|
@{ $attrs || [] }) { |
197
|
13
|
100
|
|
|
|
78
|
my $v = $class ? ($src->can( $_ ) ? $src->$_() : undef) : $src->{ $_ }; |
|
|
100
|
|
|
|
|
|
198
|
|
|
|
|
|
|
|
199
|
13
|
100
|
|
|
|
128
|
defined $v and $dest->{ $_ } = $v; |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
|
202
|
7
|
|
|
|
|
13
|
return $dest; |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
sub merge_file_data ($$) { |
206
|
5
|
|
|
5
|
1
|
417
|
my ($existing, $new) = @_; |
207
|
|
|
|
|
|
|
|
208
|
5
|
|
|
|
|
7
|
for (keys %{ $new }) { |
|
5
|
|
|
|
|
16
|
|
209
|
|
|
|
|
|
|
$existing->{ $_ } = exists $existing->{ $_ } |
210
|
|
|
|
|
|
|
? merge( $existing->{ $_ }, $new->{ $_ } ) |
211
|
11
|
100
|
|
|
|
32
|
: $new->{ $_ }; |
212
|
|
|
|
|
|
|
} |
213
|
|
|
|
|
|
|
|
214
|
5
|
|
|
|
|
2955
|
return; |
215
|
|
|
|
|
|
|
} |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
sub merge_for_update (;$$$) { |
218
|
21
|
|
|
21
|
1
|
12112
|
my ($dest_ref, $src, $filter) = @_; my $updated = 0; |
|
21
|
|
|
|
|
34
|
|
219
|
|
|
|
|
|
|
|
220
|
21
|
100
|
|
|
|
54
|
$dest_ref or throw( Unspecified, [ 'destination reference' ] ); |
221
|
|
|
|
|
|
|
|
222
|
20
|
|
100
|
3
|
|
22
|
${ $dest_ref } //= {}; $src //= {}; $filter //= sub { keys %{ $_[ 0 ] } }; |
|
20
|
|
100
|
|
|
58
|
|
|
20
|
|
100
|
|
|
44
|
|
|
20
|
|
|
|
|
55
|
|
|
3
|
|
|
|
|
2
|
|
|
3
|
|
|
|
|
11
|
|
223
|
|
|
|
|
|
|
|
224
|
20
|
|
|
|
|
47
|
for my $k ($filter->( $src )) { |
225
|
24
|
100
|
|
|
|
52
|
if (defined $src->{ $k }) { |
|
|
100
|
|
|
|
|
|
226
|
22
|
|
|
|
|
25
|
my $res = $_merge_attr->( \${ $dest_ref }->{ $k }, $src->{ $k } ); |
|
22
|
|
|
|
|
64
|
|
227
|
|
|
|
|
|
|
|
228
|
22
|
|
100
|
|
|
219
|
$updated ||= $res; |
229
|
|
|
|
|
|
|
} |
230
|
2
|
|
|
|
|
6
|
elsif (exists ${ $dest_ref }->{ $k }) { |
231
|
1
|
|
|
|
|
1
|
delete ${ $dest_ref }->{ $k }; $updated = 1; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
2
|
|
232
|
|
|
|
|
|
|
} |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
|
235
|
20
|
|
|
|
|
76
|
return $updated; |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
sub qualify_storage_class ($) { |
239
|
21
|
|
|
21
|
1
|
63
|
return STORAGE_BASE.'::'.$_[ 0 ]; |
240
|
|
|
|
|
|
|
} |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
sub supported_extensions () { |
243
|
1
|
|
|
1
|
1
|
414
|
return grep { not m{ \A _ }mx } keys %{ extension_map() }; |
|
3
|
|
|
|
|
9
|
|
|
1
|
|
|
|
|
3
|
|
244
|
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
sub thread_id () { |
247
|
|
|
|
|
|
|
# uncoverable branch true |
248
|
111
|
50
|
|
111
|
1
|
291
|
return exists $INC{ 'threads.pm' } ? threads->tid() : 0; |
249
|
|
|
|
|
|
|
} |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
sub throw (;@) { |
252
|
47
|
|
|
47
|
1
|
360
|
EXCEPTION_CLASS->throw( @_ ); |
253
|
|
|
|
|
|
|
} |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
1; |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
__END__ |