File Coverage

lib/File/DataClass/Functions.pm
Criterion Covered Total %
statement 119 119 100.0
branch 49 54 96.3
condition 34 37 91.8
subroutine 34 34 100.0
pod 18 18 100.0
total 254 262 98.0


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__