File Coverage

blib/lib/Banal/Util/Mini.pm
Criterion Covered Total %
statement 50 183 27.3
branch 0 72 0.0
condition 0 33 0.0
subroutine 15 36 41.6
pod 0 19 0.0
total 65 343 18.9


line stmt bran cond sub pod time code
1 1     1   4933 use 5.010;
  1         31  
2 1     1   900 use utf8;
  1         18  
  1         28  
3 1     1   59 use strict;
  1         3  
  1         50  
4 1     1   13 use warnings;
  1         16  
  1         144  
5              
6             package Banal::Util::Mini; # git description: d948342
7             # vim: set ts=2 sts=2 sw=2 tw=115 et :
8             # ABSTRACT: Provide several utility functions with minimal dependencies.
9             # KEYWORDS: Util utility light-weight
10              
11             our $VERSION = '0.001';
12             # AUTHORITY
13              
14 1     1   37 use Carp qw(croak);
  1         3  
  1         250  
15 1     1   8 use Scalar::Util qw(blessed refaddr reftype);
  1         2  
  1         159  
16 1     1   17 use List::Util 1.45 qw(any first none pairs uniq);
  1         41  
  1         302  
17 1     1   607 use List::MoreUtils qw(arrayify firstres listcmp);
  1         14357  
  1         9  
18 1     1   1251 use overload; # TAU : Required by flatten() and hence arrayify() routines copied from List::MoreUtils;
  1         2  
  1         30  
19              
20              
21              
22 1     1   762 use Data::Printer qw(p np); # During development only. TODO: comment this line out later.
  1         41255  
  1         7  
23              
24 1     1   7871 use namespace::autoclean;
  1         9362  
  1         4  
25              
26              
27 1     1   497 use Exporter::Shiny;
  1         449  
  1         8  
28 1     1   79 use vars qw(@EXPORT_OK);
  1         8  
  1         97  
29             BEGIN {
30 1     1   6 @EXPORT_OK = qw(
31             msg
32             polyvalent
33              
34             hash_access
35             hash_lookup
36             hash_lookup_staged
37              
38             inverse_dict
39             inverse_mapping
40              
41             maybe
42             maybe_kv
43             peek
44              
45             tidy_arrayify
46             first_viable
47             invoke_first_existing_method
48              
49             affixed
50             prefixed
51             suffixed
52              
53             sanitize_env_var_name
54             sanitize_subroutine_name
55             sanitize_identifier_name
56             );
57              
58             # Add function aliases with underscore prefixes (single & double)
59 1         4 my @ok = @EXPORT_OK;
60 1         2 foreach my $pfx ('_', '__') {
61 1     1   6 { no strict 'refs';
  1         2  
  1         105  
  2         3  
62 2         4 *{ __PACKAGE__ . '::' . $pfx . $_ } = \&{ __PACKAGE__ . '::' . $_ } for @ok ;
  38         154  
  38         141  
63             }
64 2         5 push @EXPORT_OK, ( map {; $pfx . $_ } (@ok) );
  38         2006  
65             }
66             }
67             #say STDERR 'EXPORT_OK : ' . np @EXPORT_OK;
68              
69              
70              
71             #$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
72             # UTILITY FUNCTIONS
73             #$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
74              
75             #----------------------------------------------------------
76             # CLASS / OBJECT related functions
77             #----------------------------------------------------------
78              
79             #######################################
80             sub polyvalent { # Helps with the parameter processing of polyvalent (object or class) methods
81             #######################################
82 0     0 0   my $proto = shift;
83 0 0         my $self = blessed $proto ? $proto : $proto->new();
84 0           my $class = blessed $self;
85 0 0         wantarray ? ($self, $class, $proto) : $self;
86             }
87              
88              
89             #######################################
90             sub msg(@) { # Message text builder to be used in error output (warn, die, ...)
91             #######################################
92 0 0   0 0   my $o = blessed ($_[0]) ? shift : caller();
93 0   0       state $pfx = eval { $o->_msg_pfx(@_) } // '';
  0            
94 0           join ('', $pfx, @_, "\n")
95             }
96              
97              
98             #..........................................................
99             # STRING/TEXT processing functions
100             #..........................................................
101              
102             sub prefixed ($@) {
103 0 0   0 0   my %opts = %{ ref ($_[0]) eq 'HASH' ? shift : +{ prefix => shift} };
  0            
104 0           affixed(\%opts, @_ )
105             }
106              
107             sub suffixed ($@) {
108 0 0   0 0   my %opts = %{ ref ($_[0]) eq 'HASH' ? shift : +{ suffix => shift} };
  0            
109 0           affixed(\%opts, @_ )
110             }
111              
112             sub affixed ($@) {
113 0 0   0 0   my %opts = %{ ref ($_[0]) eq 'HASH' ? shift : +{} };
  0            
114 0 0 0       my $pfx = exists $opts{prefix} ? ( $opts{prefix} // '') : '';
115 0 0 0       my $sfx = exists $opts{suffix} ? ( $opts{suffix} // '') : '';
116 0           map {; $pfx . $_ . $sfx } @_
  0            
117             }
118              
119             #&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
120 0     0 0   sub sanitize_env_var_name (;$) { &sanitize_identifier_name }
121 0     0 0   sub sanitize_subroutine_name (;$) { &sanitize_identifier_name }
122             sub sanitize_identifier_name (;$) {
123             #&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
124             # cleanse (sanitize) the name by replacing non-alphanumeric chars with underscores.
125 0 0   0 0   my $name = (@_) ? shift : $_; # If no argument is given, use the default SCALAR variable as our argument.
126              
127 0           $name =~ s/[^_A-Za-z0-9]/_/g;
128 0           return $name;
129             }
130              
131              
132             #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
133             # HASH related functions
134             #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
135              
136             #&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
137             sub peek {
138             #&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
139 0     0 0   my ($h, $keys) = @_;
140 0           my @keys = tidy_arrayify($keys);
141 0           my $v;
142              
143 0           foreach my $key (@keys) {
144 0 0         $v = exists $h->{$key} ? $h->{$key} : undef;
145 0 0         return $v if defined($v);
146             }
147              
148             # Allow falling back to a set of defaults
149             # In scalar context, the first one defined wins.
150             # In list context, we return a list that contains all of the defined results
151             # wantarray ? ( grep { defined } @_ ) : first { defined } @_;
152              
153             # [TAU] @ [ 2019-02-02 ] : Adopting a simpler behaviour, as below.
154 0     0     $v = first { defined } @_;
  0            
155 0 0 0       wantarray ? ( $v // () ) : $v;
156             }
157              
158              
159             # accumulate hash entries, given a set of ket value pairs.
160             # The result will only include those pairs where both the key
161             # and the value are 'defined'.
162 0     0 0   sub maybe { &maybe_kv }
163             sub maybe_kv {
164 0     0 0   my @r; # result is accumulated in an array (instead of a hash), so that we can use 'push'
165 0 0 0       push @r, ( shift // () ) if (@_ % 2); # This is how we deal with an odd number of args (including a single arg)
166              
167 0           foreach my $pair ( pairs @_ ) {
168 0           my ( $key, $value ) = @$pair;
169 0 0 0       push @r, ($key => $value) if defined($key) && defined ($value);
170             }
171 0 0         wantarray ? (@r) : +{@r}
172             }
173              
174              
175             #######################################
176             sub hash_access {
177             #######################################
178             # FUNCTION: deep hash access via multiple succesive keys that each signify a level deeper than the previous.
179             # hash_access ($h, key1, key2, key3, ...)
180 0     0 0   my $node = shift;
181 0           foreach my $k (@_) {
182 0 0 0       return unless defined $node && defined $k;
183 0 0         return unless eval { exists $node->{$k} };
  0            
184 0           $node = $node->{$k};
185             }
186             $node
187 0           }
188              
189             #######################################
190             sub inverse_mapping {
191             #######################################
192 0     0 0   my @k = tidy_arrayify (shift);
193 0           my @v = tidy_arrayify (@_);
194 0           my @res;
195              
196 0           foreach my $v (@v) {
197 0           do { push @res, ($v => $_) } for @k;
  0            
198             }
199 0 0         return wantarray ? (@res) : +{ @res };
200             }
201              
202             ########################################
203             sub inverse_dict {
204             ########################################
205 0     0 0   my %h;
206 0           %h = (%h, %{; shift } ) while ( ref($_[0]) eq 'HASH');
  0            
207 0           %h = (%h, @_);
208 0           my %res;
209              
210 0           while (my ($k, $v) = each %h) {
211 0           %res = (%res, inverse_mapping($k, $v));
212             }
213 0 0         wantarray ? (%res) : \%res
214             }
215              
216              
217             ########################################
218             sub hash_lookup { # lookup($key, sources =>[], depots => [])
219             ########################################
220 0 0   0 0   my $key = (@_ % 2) ? shift : undef;
221 0           my %opts = (@_);
222 0   0       $key //= $opts{key};
223 0           my $debug = $key =~ /dist/;
224 0           local $_; # allows us to be called in the likes of map / grep; as well as our little recursion below.
225              
226             #say STDERR " Looking up '$key' ... OPTIONS are : " . np %opts if $debug;
227              
228             # DEPOTS are hash refs that will be used for looking up SOURCES themselves, when those are strings (instead of a hash refs)
229 0           my @depots = ( grep { defined $_ } arrayify( @opts{qw(depot depots)}) );
  0            
230              
231             # SOURCES are hash refs that will be tried in order for key lookup.
232             # Alternatively, these may be denoted by strings, in which case they will themsleves be looked up in the 'depots'
233 0           my @sources = ( grep { defined $_ } arrayify( @opts{qw(source sources)}) );
  0            
234 0 0 0       @sources = map { ref($_) ? $_ : ( eval { hash_lookup("$_", sources=>[ @depots] ) } // () ) } @sources;
  0            
  0            
235              
236             SOURCE:
237 0           foreach my $h ( @sources ) {
238 0 0 0       next SOURCE unless defined($h) && ref($h); # Don't bother checking reftype. This allows for eventual fancy overloading to work.
239 0 0         next SOURCE unless defined $h;
240 0 0         next SOURCE unless exists $h->{$key};
241 0           my $v = $h->{$key};
242              
243 0 0         return wantarray ? ( $v ) : $v;
244             } # sources
245              
246 0           die "Can't find the '$key' in any of the hash sources."
247             }
248              
249              
250              
251             #######################################
252             sub hash_lookup_staged {
253             #######################################
254             # Returns the first found item (corresponding to any of the given keys) in any of the hash sources.
255 0     0 0   local $_;
256 0           my %opt = @_;
257 0           my @keys = tidy_arrayify($opt{keys});
258             # my $sources = $opt{sources} // [ ];
259             # $sources = [ $sources ] if ref $sources eq 'HASH';
260 0           my @sources = tidy_arrayify($opt{source}, $opt{sources});
261 0           my $debug = $opt{debug};
262 0           my $res;
263              
264             SOURCE :
265 0           foreach my $h (@sources) {
266 0 0 0       next SOURCE unless defined($h) && ( reftype($h) eq 'HASH');
267 0           my $map_keys = $opt{source_opts}{refaddr $h}{map_keys};
268 0 0         my @mkeys = defined($map_keys) ? ( $map_keys->(@keys) ) : (@keys);
269             KEY :
270 0           foreach my $key (@mkeys) {
271 0 0         next KEY unless defined $key;
272             # say STDERR " Hash lookup for key '$key' in hash '$h' ..." if $debug;
273 0 0         next KEY unless exists $h->{$key};
274 0           $res = $h->{$key};
275             # say STDERR " Value found for key '$key' => : '$res'\n" if $debug;
276 0 0         last SOURCE if defined $res;
277             }
278             }
279              
280 0 0         die "Can't find (in any of the given sources) the given keys [@keys] !" unless defined $res;
281              
282 0           return $res;
283             }
284              
285              
286              
287              
288             #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
289             # ARRAY & LIST related functions
290             #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
291              
292             ######################################
293 0     0 0   sub tidy_arrayify(;@) { local $_; my @res = ( grep { defined $_ } ( uniq( arrayify( @_) ))) }
  0            
  0            
294             #######################################
295              
296             #=begin STOLEN_FROM_List_MoreUtils
297             # ------------------------------------------------------
298             # TAU: The two routines, as well as the comment about 'leaks' were stolen from C
299             # The only thing I did was privatizing names and turning 'flatten' into a proper subroutine (instead of a scalar CODE closure)
300             # That allowed me to get rid of a warning.
301             # ------------------------------------------------------
302             # "leaks" when lexically hidden in arrayify.
303             # sub flatten { map { (ref $_ and ("ARRAY" eq ref $_ or overload::Method($_, '@{}'))) ? (flatten(@{$_})) : ($_) } @_; }
304             # sub arrayify { map { flatten($_) } @_; }
305             # #=cut
306              
307              
308             #&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
309             sub first_viable (&@) {
310             #&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
311 0     0 0   my $f = shift; # CODE BLOCK or subroutine ref. A closure is OK, too.
312 0           my @e = ();
313 0           local $_;
314              
315             #local $@; # so that we don't mess up caller's eval/error handling.
316 0           eval { 1 }; # resets $@ to whatever perl considers to be 'success';
  0            
317              
318             # This part, as well as the general flow, is copied shamelessly from the 'first()' function in C.
319 0 0 0       unless ( length ref $f && eval { $f = \&$f; 1 } ) {
  0            
  0            
320 0           require Carp;
321 0           Carp::croak("Not a subroutine reference");
322             }
323              
324             # Return the result of the first viable evaluation (i.e. first one that doesn't die on us, for whatever reason )
325 0           foreach ( @_) {
326 0           my ($item) = ($_);
327              
328 0 0         if (wantarray) { my @v = ( eval { $f->() } ); return @v unless $@; }
  0 0          
  0            
  0            
329 0 0         else { my $v = eval { $f->() } ; return $v unless $@; }
  0            
  0            
330              
331             # No luck. Save the error, for an eventual error stack output if we die.
332 0           push @e, {
333             item => $item, err => $@,
334             msg=> "Failed to invoke CODE BLOCK on item '$item', with the error : '$@'\n",
335             };
336             }
337              
338             # NO LUCK with any invocation.
339             # At this point, '$@' would normally be set to a true value by the last failed eval.
340 0 0         if (@e) {
341 0           my @emsg = map { $_->{msg} } @e;
  0            
342 0           my $name = (caller(0))[3]; # The name of this particular subroutine.
343 0           croak "$name : Failed to sucessfully invoke any of the given code blocks!\n"
344             . "Here's the list of all errors:\n\n @emsg"
345             }
346 0           return;
347             }
348              
349             #&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
350             sub invoke_first_existing_method {
351             #&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
352 0     0 0   my $o = shift;
353 0           my @methods = arrayify(@_);
354 0           my @args = ();
355              
356 0     0     first_viable { $o->$_(@args) } @methods;
  0            
357             }
358              
359              
360              
361              
362             1;
363              
364             __END__