File Coverage

blib/lib/Banal/Mini/Utils.pm
Criterion Covered Total %
statement 50 183 27.3
branch 0 72 0.0
condition 0 30 0.0
subroutine 15 36 41.6
pod 0 19 0.0
total 65 340 19.1


line stmt bran cond sub pod time code
1 2     2   4361 use 5.010;
  2         27  
2 2     2   1304 use utf8;
  2         39  
  2         20  
3 2     2   76 use strict;
  2         15  
  2         59  
4 2     2   12 use warnings;
  2         9  
  2         160  
5              
6             package Banal::Mini::Utils; # git description: a999b72
7             # vim: set ts=8 sts=4 sw=4 tw=115 et :
8             # ABSTRACT: Provide several MUNGER functions that may be use in conjunction with C.
9             # KEYWORDS: Munge Has has MungeHas MooseX::MungeHas Moose MooseX Moo MooX
10              
11             our $VERSION = '0.002';
12             # AUTHORITY
13              
14 2     2   19 use Carp qw(croak);
  2         5  
  2         219  
15 2     2   12 use Scalar::Util qw(blessed refaddr reftype);
  2         14  
  2         229  
16 2     2   15 use List::Util 1.45 qw(any first none pairs uniq);
  2         49  
  2         450  
17 2     2   1198 use List::MoreUtils qw(arrayify firstres listcmp);
  2         25186  
  2         27  
18 2     2   2382 use overload; # TAU : Required by flatten() and hence arrayify() routines copied from List::MoreUtils;
  2         7  
  2         61  
19              
20              
21              
22 2     2   857 use Data::Printer qw(p np); # During development only. TODO: comment this line out later.
  2         40840  
  2         14  
23              
24 2     2   13770 use namespace::autoclean;
  2         18322  
  2         7  
25              
26              
27 2     2   925 use Exporter::Shiny;
  2         866  
  2         13  
28 2     2   136 use vars qw(@EXPORT_OK);
  2         11  
  2         180  
29             BEGIN {
30 2     2   12 @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 2         8 my @ok = @EXPORT_OK;
60 2         6 foreach my $pfx ('_', '__') {
61 2     2   12 { no strict 'refs';
  2         4  
  2         222  
  4         5  
62 4         10 *{ __PACKAGE__ . '::' . $pfx . $_ } = \&{ __PACKAGE__ . '::' . $_ } for @ok ;
  76         312  
  76         275  
63             }
64 4         11 push @EXPORT_OK, ( map {; $pfx . $_ } (@ok) );
  76         4029  
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 0 0   0     wantarray ? ( grep { defined } @_ ) : first { defined } @_;
  0            
  0            
152             }
153              
154              
155             # accumulate hash entries, given a set of ket value pairs.
156             # The result will only include those pairs where both the key
157             # and the value are 'defined'.
158 0     0 0   sub maybe { &maybe_kv }
159             sub maybe_kv {
160 0     0 0   my @r; # result is accumulated in an array (instead of a hash), so that we can use 'push'
161 0 0 0       push @r, ( shift // () ) if (@_ % 2); # This is how we deal with an odd number of args (including a single arg)
162              
163 0           foreach my $pair ( pairs @_ ) {
164 0           my ( $key, $value ) = @$pair;
165 0 0 0       push @r, ($key => $value) if defined($key) && defined ($value);
166             }
167 0 0         wantarray ? (@r) : +{@r}
168             }
169              
170              
171             #######################################
172             sub hash_access {
173             #######################################
174             # FUNCTION: deep hash access via multiple succesive keys that each signify a level deeper than the previous.
175             # hash_access ($h, key1, key2, key3, ...)
176 0     0 0   my $node = shift;
177 0           foreach my $k (@_) {
178 0 0 0       return unless defined $node && defined $k;
179 0 0         return unless eval { exists $node->{$k} };
  0            
180 0           $node = $node->{$k};
181             }
182             $node
183 0           }
184              
185             #######################################
186             sub inverse_mapping {
187             #######################################
188 0     0 0   my @k = tidy_arrayify (shift);
189 0           my @v = tidy_arrayify (@_);
190 0           my @res;
191              
192 0           foreach my $v (@v) {
193 0           do { push @res, ($v => $_) } for @k;
  0            
194             }
195 0 0         return wantarray ? (@res) : +{ @res };
196             }
197              
198             ########################################
199             sub inverse_dict {
200             ########################################
201 0     0 0   my %h;
202 0           %h = (%h, %{; shift } ) while ( ref($_[0]) eq 'HASH');
  0            
203 0           %h = (%h, @_);
204 0           my %res;
205              
206 0           while (my ($k, $v) = each %h) {
207 0           %res = (%res, inverse_mapping($k, $v));
208             }
209 0 0         wantarray ? (%res) : \%res
210             }
211              
212              
213             ########################################
214             sub hash_lookup { # lookup($key, sources =>[], depots => [])
215             ########################################
216 0 0   0 0   my $key = (@_ % 2) ? shift : undef;
217 0           my %opts = (@_);
218 0   0       $key //= $opts{key};
219 0           my $debug = $key =~ /dist/;
220 0           local $_; # allows us to be called in the likes of map / grep; as well as our little recursion below.
221              
222             #say STDERR " Looking up '$key' ... OPTIONS are : " . np %opts if $debug;
223              
224             # DEPOTS are hash refs that will be used for looking up SOURCES themselves, when those are strings (instead of a hash refs)
225 0           my @depots = ( grep { defined $_ } arrayify( @opts{qw(depot depots)}) );
  0            
226              
227             # SOURCES are hash refs that will be tried in order for key lookup.
228             # Alternatively, these may be denoted by strings, in which case they will themsleves be looked up in the 'depots'
229 0           my @sources = ( grep { defined $_ } arrayify( @opts{qw(source sources)}) );
  0            
230 0 0 0       @sources = map { ref($_) ? $_ : ( eval { hash_lookup("$_", sources=>[ @depots] ) } // () ) } @sources;
  0            
  0            
231              
232             SOURCE:
233 0           foreach my $h ( @sources ) {
234 0 0 0       next SOURCE unless defined($h) && ref($h); # Don't bother checking reftype. This allows for eventual fancy overloading to work.
235 0 0         next SOURCE unless defined $h;
236 0 0         next SOURCE unless exists $h->{$key};
237 0           my $v = $h->{$key};
238              
239 0 0         return wantarray ? ( $v ) : $v;
240             } # sources
241              
242 0           die "Can't find the '$key' in any of the hash sources."
243             }
244              
245              
246              
247             #######################################
248             sub hash_lookup_staged {
249             #######################################
250             # Returns the first found item (corresponding to any of the given keys) in any of the hash sources.
251 0     0 0   local $_;
252 0           my %opt = @_;
253 0           my @keys = tidy_arrayify($opt{keys});
254             # my $sources = $opt{sources} // [ ];
255             # $sources = [ $sources ] if ref $sources eq 'HASH';
256 0           my @sources = tidy_arrayify($opt{source}, $opt{sources});
257 0           my $debug = $opt{debug};
258 0           my $res;
259              
260             SOURCE :
261 0           foreach my $h (@sources) {
262 0 0 0       next SOURCE unless defined($h) && ( reftype($h) eq 'HASH');
263 0           my $map_keys = $opt{source_opts}{refaddr $h}{map_keys};
264 0 0         my @mkeys = defined($map_keys) ? ( $map_keys->(@keys) ) : (@keys);
265             KEY :
266 0           foreach my $key (@mkeys) {
267 0 0         next KEY unless defined $key;
268             # say STDERR " Hash lookup for key '$key' in hash '$h' ..." if $debug;
269 0 0         next KEY unless exists $h->{$key};
270 0           $res = $h->{$key};
271             # say STDERR " Value found for key '$key' => : '$res'\n" if $debug;
272 0 0         last SOURCE if defined $res;
273             }
274             }
275              
276 0 0         die "Can't find (in any of the given sources) the given keys [@keys] !" unless defined $res;
277              
278 0           return $res;
279             }
280              
281              
282              
283              
284             #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
285             # ARRAY & LIST related functions
286             #@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
287              
288             ######################################
289 0     0 0   sub tidy_arrayify(;@) { local $_; my @res = ( grep { defined $_ } ( uniq( arrayify( @_) ))) }
  0            
  0            
290             #######################################
291              
292             #=begin STOLEN_FROM_List_MoreUtils
293             # ------------------------------------------------------
294             # TAU: The two routines, as well as the comment about 'leaks' were stolen from C
295             # The only thing I did was privatizing names and turning 'flatten' into a proper subroutine (instead of a scalar CODE closure)
296             # That allowed me to get rid of a warning.
297             # ------------------------------------------------------
298             # "leaks" when lexically hidden in arrayify.
299             # sub flatten { map { (ref $_ and ("ARRAY" eq ref $_ or overload::Method($_, '@{}'))) ? (flatten(@{$_})) : ($_) } @_; }
300             # sub arrayify { map { flatten($_) } @_; }
301             # #=cut
302              
303              
304             #&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
305             sub first_viable (&@) {
306             #&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
307 0     0 0   my $f = shift; # CODE BLOCK or subroutine ref. A closure is OK, too.
308 0           my @e = ();
309 0           local $_;
310              
311             #local $@; # so that we don't mess up caller's eval/error handling.
312 0           eval { 1 }; # resets $@ to whatever perl considers to be 'success';
  0            
313              
314             # This part, as well as the general flow, is copied shamelessly from the 'first()' function in C.
315 0 0 0       unless ( length ref $f && eval { $f = \&$f; 1 } ) {
  0            
  0            
316 0           require Carp;
317 0           Carp::croak("Not a subroutine reference");
318             }
319              
320             # Return the result of the first viable evaluation (i.e. first one that doesn't die on us, for whatever reason )
321 0           foreach ( @_) {
322 0           my ($item) = ($_);
323              
324 0 0         if (wantarray) { my @v = ( eval { $f->() } ); return @v unless $@; }
  0 0          
  0            
  0            
325 0 0         else { my $v = eval { $f->() } ; return $v unless $@; }
  0            
  0            
326              
327             # No luck. Save the error, for an eventual error stack output if we die.
328 0           push @e, {
329             item => $item, err => $@,
330             msg=> "Failed to invoke CODE BLOCK on item '$item', with the error : '$@'\n",
331             };
332             }
333              
334             # NO LUCK with any invocation.
335             # At this point, '$@' would normally be set to a true value by the last failed eval.
336 0 0         if (@e) {
337 0           my @emsg = map { $_->{msg} } @e;
  0            
338 0           my $name = (caller(0))[3]; # The name of this particular subroutine.
339 0           croak "$name : Failed to sucessfully invoke any of the given code blocks!\n"
340             . "Here's the list of all errors:\n\n @emsg"
341             }
342 0           return;
343             }
344              
345             #&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
346             sub invoke_first_existing_method {
347             #&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
348 0     0 0   my $o = shift;
349 0           my @methods = arrayify(@_);
350 0           my @args = ();
351              
352 0     0     first_viable { $o->$_(@args) } @methods;
  0            
353             }
354              
355              
356              
357              
358             1;
359              
360             __END__