File Coverage

blib/lib/Banal/Role/Fallback/Tiny.pm
Criterion Covered Total %
statement 41 156 26.2
branch 0 82 0.0
condition 0 51 0.0
subroutine 14 26 53.8
pod n/a
total 55 315 17.4


line stmt bran cond sub pod time code
1 4     4   383497 use 5.014;
  4         43  
2 4     4   1357 use utf8;
  4         46  
  4         57  
3 4     4   143 use strict;
  4         12  
  4         119  
4 4     4   21 use warnings;
  4         8  
  4         328  
5              
6             package Banal::Role::Fallback::Tiny;
7             # vim: set ts=2 sts=2 sw=2 tw=115 et :
8             # ABSTRACT: A tiny role that provides a 'fallback' method which helps building default values for object attributes.
9             # KEYWORDS: author utility
10              
11             our $VERSION = '0.001';
12             # AUTHORITY
13              
14 4     4   1875 use Banal::Util::Mini qw(tidy_arrayify hash_lookup_staged sanitize_subroutine_name);
  4         257768  
  4         40  
15 4     4   6563 use Array::Utils qw(intersect);
  4         1709  
  4         442  
16 4     4   35 use Scalar::Util qw(blessed refaddr reftype);
  4         65  
  4         316  
17 4     4   92 use List::Util 1.45 qw(any none pairs uniq);
  4         92  
  4         518  
18 4     4   30 use List::MoreUtils qw(arrayify firstres listcmp);
  4         9  
  4         31  
19 4     4   6524 use Text::ParseWords qw(quotewords);
  4         5309  
  4         236  
20 4     4   62 use Data::Printer;
  4         12  
  4         25  
21              
22 4     4   254 use namespace::autoclean;
  4         10  
  4         22  
23 4     4   794 use Role::Tiny;
  4         4322  
  4         36  
24             requires qw( _fallback_settings );
25              
26              
27              
28             #######################################
29             sub _resolve { # Transitional. Helps support an older name & interface to _fallback
30             #######################################
31 0     0     my $o = shift;
32 0 0         my %opt = %{ ref $_[0] eq 'HASH' ? shift : +{} };
  0            
33 0           _fallback($o, { args_are_keys => 1, %opt}, @_)
34             }
35              
36             #######################################
37             sub _resolve_mv { # Explicitely asks for an ARRAY reference.
38             #######################################
39             # Also handles 'implicit additions' (extras that are appended systematically)
40 0     0     my $self = shift;
41 0 0         my $opt = ( ref ($_[0]) =~ /HASH/ ) ? shift : {};
42 0           $self->_resolve( { %$opt, want_reftype => 'ARRAY', multivalue => 1 }, @_);
43             }
44              
45             #######################################
46             sub _resolve_mv_list { # Always returns a list, instead of an ARRAY reference.
47             #######################################
48 0     0     @{ shift->_resolve_mv(@_) }
  0            
49             }
50              
51             #######################################
52             sub _resolve_href { # Explicitely asks for a hash reference
53             #######################################
54 0     0     my $self = shift;
55 0 0         my $opt = ( ref ($_[0]) =~ /HASH/ ) ? shift : {};
56 0           $self->_resolve( { %$opt, want_reftype => 'HASH' }, @_ );
57             }
58              
59              
60             # Practical method-helper for determining the effective value to be used for a given attribute for a given object.
61             # The object would need to satisfy several conditions, though
62             #######################################
63             sub _fallback {
64             #######################################
65 0     0     local $_;
66 0           my ($o, %opt) = &_normalize_fallback_opts;
67 0           my @keys = tidy_arrayify( $opt{keys} );
68 0           my @blankers = tidy_arrayify( $opt{blanker_token} );
69 0           my @mid = tidy_arrayify( @opt{qw( via )});
70 0 0         @mid = tidy_arrayify( @opt{qw( mid nrm normally )}, \&_smart_lookup) unless !!@mid;
71             my @attempts = tidy_arrayify(
72             @opt{qw( apriori primo )},
73             @mid,
74 0           @opt{qw( def last fin final finally )}
75             );
76 0           my @res;
77 0           my $debug = $opt{debug};
78              
79             # say STDERR "Looking up keys : " . np @keys if $debug;
80             ATTEMPT:
81 0           foreach my $item (@attempts) {
82 0 0         next unless defined $item;
83             my $v = (reftype($item) // '') eq 'CODE'
84 0 0 0       ? eval { $item->($o, \%opt) }
  0            
85             : $item;
86              
87             # say STDERR "Attempt died on us : $@" if $@ && $debug;
88              
89 0 0 0       if (defined($v) && !$@) {
90 0           push @res, $v;
91 0           last ATTEMPT;
92             }
93 0           eval {; 1 }; # clear the last error
  0            
94             }
95              
96 0           @res = tidy_arrayify(@res);
97              
98             # say STDERR " Got (raw) : " . np @res if $debug;
99              
100             {
101 4     4   2996 no warnings qw(uninitialized);
  4         12  
  4         6536  
  0            
102 0           my @greps = tidy_arrayify( $opt{grep}, $opt{greps});
103 0 0   0     push @greps, sub {; my @v=($_); !intersect(@blankers, @v) } if (@blankers);
  0            
  0            
104              
105 0           foreach my $f ( @greps ) {
106 0   0       my $rt = reftype($f) // '';
107             @res = grep {
108 0           my $gr = $_;
  0            
109 0 0         $rt eq 'CODE' and $gr = $f->($_);
110 0 0         $rt eq 'REGEXP' and $gr = m/$f/;
111 0 0         !$rt and $gr = looks_like_number($f) ? ($_ == $f) : ($_ eq "$f");
    0          
112 0           $gr
113             }@res;
114             }
115 0 0 0       @res = uniq(@res) unless $opt{no_uniq} && !$opt{uniq};
116 0 0         @res = sort @res if $opt{sort};
117             }
118              
119             # say STDERR " Keys : " . np @keys if grep { m/install/ } @keys;
120             # say STDERR " Got : " . np @res if $debug;
121              
122 0 0         return [ @res ] if $opt{want_reftype} eq 'ARRAY' ;
123 0 0         return unless @res; # Got no results at all. Signal that.
124 0 0         return $res[0] if @res == 1; # If we've got only one value, then there is no ambiguity. Just return that.
125              
126 0 0         if ( $opt{want_reftype} eq 'HASH' ) {
127 0 0         my %res = map {; ref ($_) =~ /HASH/ix ? ( %{$_} ) : () } reverse @res; # effectively shallow-merge the resulting hashes
  0            
  0            
128 0           return +{ %res };
129             }
130              
131             # At this stage, even if we have more than one value, we only return the first found.
132             # CONSIDER: raising an error, perhaps.
133 0           return $res[0];
134             }
135              
136             #
137             # #######################################
138             # around _fallback => sub { # DEBUG wrapper.
139             # #######################################
140             # my $orig = shift;
141             # my ($o, %opt) = &_normalize_fallback_opts;
142             # my @keys = tidy_arrayify( $opt{keys} );
143             # my $debug = $opt{debug};
144             # my %info = (keys => [@keys] );
145             #
146             # if (wantarray) {
147             # say STDERR "\n\nFallback in ARRAY context for keys [@keys] ... " if $debug;
148             # my @r = $o->$orig($o, \%opt, @_);
149             # $info{result} = [@r];
150             # say STDERR "Fallback in ARRAY context. info : " . np %info if $debug;
151             # return @r;
152             # } else {
153             # say STDERR "\n\nFallback in SCALAR context for keys [@keys] ... " if $debug;
154             # my $r = scalar($o->$orig($o, \%opt, @_));
155             # $info{result} = $r;
156             # say STDERR " Fallback result in SCALAR context. info : " . np %info if $debug;
157             # return $r;
158             # }
159             #
160             # };
161             #
162              
163             #######################################
164             sub _smart_lookup {
165             #######################################
166             # Returns the first found item (corresponding to any of the given keys) in any of the hash sources.
167 0     0     local $_;
168 0           my ($o, %opt) = &_normalize_fallback_opts;
169             my @keys = tidy_arrayify( $opt{keys} )
170 0 0         or die "No keys given for us to lookup during staged fallback!";
171 0           my @res;
172 0   0       my @sfx = tidy_arrayify( $opt{implicit_suffix} // '_implicit' );
173 0           my @blankers = tidy_arrayify( $opt{blanker_token} );
174 0           my @no_implicit = tidy_arrayify( $opt{no_implicit} );
175 0           my $debug = $opt{debug};
176              
177 0 0   0     push @no_implicit, sub {; shift; shift; intersect(@blankers, @_) } if (@blankers);
  0            
  0            
  0            
178              
179             # say STDERR " Smart-lookup keys : " . np @keys if $debug;
180              
181             SUFFIX:
182 0           foreach my $suffix ('', @sfx) {
183 0 0         if ($suffix) {
184             # An non-empty suffix means we are dealing with an 'implcit' lookup.
185 0 0   0     last if any { ( reftype($_) eq 'CODE') ? $_->($o, \%opt, @res) : $_ } @no_implicit;
  0 0          
186             }
187              
188 0           my @mkeys = map {; $_ . $suffix } @keys;
  0            
189 0           my $found;
190              
191             # Try $o->$key_$suffix(...)
192 0 0 0       if ( $suffix && !$opt{no_implicit_accessor_calls} ) {
193             # Try to invoke a subroutine by the given name (only if we've got a suffix)
194 0           foreach my $k (@mkeys) {
195 0           my $method = sanitize_subroutine_name ($k);
196 0 0 0       $found //= eval { $o->$method(@_) } if blessed ($o) && $o->can($method);
  0   0        
197 0 0         last if defined $found;
198             }
199             }
200 0 0         $found = eval { hash_lookup_staged( %opt, keys=> [@mkeys] ) } unless defined ($found);
  0            
201 0 0 0       push @res, ($found) if defined($found) && !$@;
202             }
203 0           @res = tidy_arrayify(@res);
204              
205             # say STDERR " Smart-lookup got (raw) : " . np @res if $debug;
206              
207             # If are asked to do so, make sure we return an array-reference
208             # if ($opt{want_reftype} eq 'ARRAY') {
209             # $res = [tidy_arrayify($res)];
210             # $res = [ Text::ParseWords::quotewords('\s+', 0, @$res) ] if $opt{'parsewords'};
211             # }
212              
213             # Parse words and make them into array items (if asked to do so)
214             #@res = tidy_arrayify(quotewords('\s+', 0, @res)) if $opt{'parsewords'};
215              
216             # say STDERR " Smart-lookup got (after-parsewords) : " . np @res if $debug;
217              
218 0 0         die "Can't lookup any of the given keys [@keys]!" unless scalar(@res);
219              
220 0 0         wantarray ? (@res) : \@res;
221             # return [ @res ] if $opt{want_reftype} eq 'ARRAY' ;
222             # return unless @res; # Got no results at all. Signal that.
223             # return $res[0] if @res == 1; # If we've got only one value, then there is no ambiguity. Just return that.
224             #
225             # if ( $opt{want_reftype} eq 'HASH' ) {
226             # my %res = map {; ref ($_) =~ /HASH/ix ? ( %{$_} ) : () } reverse @res; # effectively shallow-merge the resulting hashes
227             # return +{ %res };
228             # }
229             #
230             # # At this stage, even if we have more than one value, we only return the first found.
231             # # CONSIDER: raising an error, perhaps.
232             # return $res[0];
233             }
234              
235              
236             #######################################
237             sub _normalize_fallback_opts {
238             #######################################
239 0     0     local $_;
240 0           my $o = shift;
241 0 0         my %opt = %{ ref $_[0] eq 'HASH' ? shift : +{} };
  0            
242              
243 0 0 0       unless ( exists $opt{_normalized_} && ($opt{_normalized_} // 0) ) {
      0        
244             # these may be overridden by the caller.
245 0           %opt = ( payload=>1, author_specific=>1, generic=>1, %opt );
246 0   0       my $fbs = eval { $o->_fallback_settings(%opt) } // eval { $o->fallback_settings(%opt) } // +{};
  0   0        
  0            
247 0           %opt = ( %$fbs, %opt );
248              
249 0 0         my $isam = exists $opt{isam} ? $opt{isam} : '';
250 0           $isam = "$isam"; # We are only able to consider plain scalars.
251              
252              
253             #say STDERR "metam : " . np %meta;
254             #say STDERR "isam : '$isam'";
255              
256 0   0       my $rt = $opt{want_reftype} // '';
257 0 0 0 0     $rt = 'ARRAY' if $opt{multivalue} || any { defined && /^ARRAY/ix } ($rt, $isam);
  0 0          
258 0 0   0     $rt = 'HASH' if any { defined && /^HASH/ix } ($rt, $isam);
  0 0          
259 0           $opt{want_reftype} = $rt;
260 0   0       $opt{multivalue} //= $rt =~ /^ARRAY/ix || 0;
      0        
261 0   0       $opt{parsewords} //= $rt =~ /^ARRAY/ix || 0;
      0        
262             $opt{keys} = [ tidy_arrayify( # we are quite forgiving in terms of specifiying keys/aliases
263             $opt{key}, $opt{keys},
264             $opt{name}, $opt{names},
265             $opt{aka}, $opt{alias}, $opt{aliases}, )
266 0           ];
267             }
268              
269             # This is done even if opts are already normalized.
270 0 0 0       $opt{keys} = [ tidy_arrayify($opt{keys}, splice @_) ] if delete($opt{args_are_keys}) // 0;
271 0           my @keys = tidy_arrayify($opt{keys});
272 0           $opt{debug} = 0;
273             # $opt{debug} = grep { m/(installer|stopword)/ix } @keys;
274              
275 0 0         if ( !$opt{_normalized_} ){ # DEBUG
276 0           my %mopt = (%opt);
277 0           delete @mopt{qw(sources)};
278             #say STDERR "mopt : " . np %mopt;
279             }
280              
281 0           $opt{_normalized_} = 1;
282 0 0         return wantarray ? ($o, %opt) : \%opt;
283             }
284              
285              
286              
287             1;
288              
289             =pod
290              
291             =encoding UTF-8
292              
293             =head1 NAME
294              
295             Banal::Role::Fallback::Tiny - A tiny role that provides a 'fallback' method which helps building default values for object attributes.
296              
297             =head1 VERSION
298              
299             version 0.001
300              
301             =head1 SYNOPSIS
302              
303             Role::Tiny::With;
304             with Banal::Role::Fallback::Tiny;
305              
306             has username => (
307             is => ro,
308             isa => Str,
309             lazy => 1,
310             default => sub { fallback(keys=> [qw(username user)}
311             );
312              
313             =head1 DESCRIPTION
314              
315             =for stopwords isa lazy ro Str
316              
317             =for stopwords TABULO
318             =for stopwords GitHub DZIL
319              
320             This is a tiny role that provides a 'fallback' method for building default values for your attributes.
321              
322             =head2 WARNING
323              
324             Please note that, although this module needs to be on CPAN for obvious reasons,
325             it is really intended to be a collection of personal preferences, which are
326             expected to be in great flux, at least for the time being.
327              
328             Therefore, please do NOT base your own distributions on this one, since anything
329             can change at any moment without prior notice, while I get accustomed to dzil
330             myself and form those preferences in the first place...
331             Absolutely nothing in this distribution is guaranteed to remain constant or
332             be maintained at this point. Who knows, I may even give up on dzil altogether...
333              
334             You have been warned.
335              
336             =head1 SEE ALSO
337              
338             =over 4
339              
340             =item *
341              
342             L
343              
344             =back
345              
346             =head1 SUPPORT
347              
348             Bugs may be submitted through L
349             (or L).
350              
351             =head1 AUTHOR
352              
353             Ayhan ULUSOY
354              
355             =head1 COPYRIGHT AND LICENSE
356              
357             This software is copyright (c) 2018 by Ayhan ULUSOY.
358              
359             This is free software; you can redistribute it and/or modify it under
360             the same terms as the Perl 5 programming language system itself.
361              
362             =cut
363              
364             __END__