File Coverage

blib/lib/Exporter/Handy/Util.pm
Criterion Covered Total %
statement 70 105 66.6
branch 19 34 55.8
condition 23 70 32.8
subroutine 13 18 72.2
pod 0 2 0.0
total 125 229 54.5


line stmt bran cond sub pod time code
1             package Exporter::Handy::Util;
2              
3             # ABSTRACT: Routines useful when exporting symbols thru Exporter and friends
4             our $VERSION = '1.000002';
5              
6 1     1   102228 use utf8;
  1         26  
  1         5  
7 1     1   30 use strict;
  1         2  
  1         20  
8 1     1   4 use warnings;
  1         2  
  1         30  
9              
10             # Automatically fall back to pure perl for older versions of List::Util (circa Perl version < 5.20)
11 1     1   455 use List::Util::MaybeXS qw( pairs unpairs uniq );
  1         760  
  1         91  
12 1     1   688 use Exporter::Extensible -exporter_setup => 1;
  1         7456  
  1         7  
13              
14             export(qw(
15             =xtags
16             =expand_xtags
17             ));
18              
19              
20             # Generators for exported functions
21             sub _generate_xtags {
22 2     2   306 my ($exporter, $symbol, $opts) = @_;
23 6   100 6   4604 sub {; xtags_($opts // {}, @_ ) } # curried
24 2         11 }
25              
26             sub _generate_expand_xtags {
27 0     0   0 my ($exporter, $symbol, $opts) = @_;
28 0   0 0   0 sub {; expand_xtags_(@_, $opts // { }) } # curried
29 0         0 }
30              
31              
32              
33              
34             sub xtags_ { # useful for building export tags
35             # say STDERR 'xtag ARGS: ' . np(@_);
36              
37 6     6 0 13 my %opt; %opt = ( %opt, %{; shift } ) while _is_plain_hashref($_[0]); # merge options given by any leading hash-refs
  6         20  
  6         21  
38 6         12 my @res;
39              
40              
41 6         28 for (pairs @_) {
42 6         14 my ($k ,$v) = @$_;
43              
44 6 100       29 if ( ref($v) =~ /^HASH$/ ) {
45 4         10 push @res, _xtag_group( \%opt, $k => $v );
46             } else {
47 2         7 push @res, _xtag_group( \%opt, '' => { $k => $v } );
48             }
49             }
50 6 50       29 wantarray ? @res : \@res; ## no critic
51             }
52              
53             sub _xtag_group {
54             # say STDERR '_xtag_group ARGS: ' . np(@_);
55              
56             # options may be given by one or more leading hash-refs (that we merge)
57 8     8   13 my %opt; %opt = ( %opt, %{; shift } ) while _is_plain_hashref($_[0]);
  8         16  
  8         26  
58              
59 8 50 33     42 my $group = ( @_ && !ref( $_[0] ) ) ? shift : undef;
60 8         12 my %items = %{; shift };
  8         22  
61 8   50     17 %opt = ( %opt, %{; delete $items{'%'} // {} } );
  8         56  
62              
63 8   33     24 $group = $group // delete $opt{group} // delete $opt{name} // '';
      0        
      0        
64 8         14 $group =~ s/^([:])//;
65              
66 8         18 my %subopt= %opt;
67 8   66     33 my $sig = delete $opt{sig} // $1 // ''; # like a sigil... It's typically either ':' or empty string.
      50        
68 8   50     25 my $sep = delete $opt{sep} // '_';
69 8   50     22 my $nogroup = delete $opt{nogroup} // 0;
70              
71 8 100 66     52 my @pfx = _flat( delete $opt{pfx} // ( $group ? "${group}${sep}" : "" ));
72              
73 8         15 my %tags;
74 8         14 for my $pfx (@pfx) {
75 8 100 66     41 $pfx = $sig . $pfx if $sig && ($pfx !~ /^\Q$sig\E/);
76              
77 8         25 for my $k (sort keys %items) {
78 8         27 my $v = $items{$k};
79 8         85 $k =~ s/^\Q$sig\E//;
80 8         23 my $key = "${pfx}${k}";
81 8 100       47 my %subtags = ( ref($v) =~ /^HASH$/ ) ? ( _xtag_group(\%subopt, $key => $v) ) : ( $key => $v);
82 8         34 %tags = (%tags, %subtags);
83             }
84             }
85             # umbrella entry (that encompasses all subtags)
86 8 100 33     48 if (!$nogroup && defined $group && $group) {
      66        
87 6         11 my $g = $group;
88 6 100 66     28 $g = $sig . $g if $sig && ($g !~ /^\Q$sig\E/);
89             $tags{$g} = [
90 6         16 map {;
91 8         12 my $item = $_;
92 8 100 33     42 $item = ':' . $_ if defined $_ && $_ && !m/^[:]/;
      66        
93 8   33     33 $item // ()
94             } ( sort keys %tags ) ]
95             }
96              
97 8         24 my @tags = _kv_sort(%tags); # sort on keys
98 8 50       55 wantarray ? @tags : \@tags; ## no critic
99             }
100              
101              
102              
103             sub expand_xtags_ {
104 0     0 0 0 local $_;
105 0         0 my %tags; %tags = ( %tags, %{; shift } ) while _is_plain_hashref($_[0]); # tags at start
  0         0  
  0         0  
106 0         0 my %opt; %opt = ( %opt, %{; pop } ) while _is_plain_hashref($_[-1]); # options at the end.
  0         0  
  0         0  
107              
108             # Handle special requests given via options
109 0   0     0 my @keys = _flat( delete $opt{key} // (), delete $opt{keys} // () );
      0        
110 0         0 for (@keys) {
111 0 0 0     0 if (_is_plain_scalarref($_) and ($$_ =~ /[*]|ALL/i ) ) {
112             # A scalar ref indicates special handling!
113             # If it deferences to '*' (or 'ALL'), it means "ALL KEYS".
114 0         0 push @_, values %tags;
115             next
116 0         0 }
117 0         0 push @_, $tags{$_};
118             }
119 0         0 @_ = uniq(@_);
120              
121 0         0 my %seen;
122             my @res;
123              
124 0         0 while (@_) {
125 0         0 $_ = shift;
126 0 0       0 next unless defined;
127 0 0       0 ref($_) eq 'ARRAY' and do { unshift @_, @$_; next };
  0         0  
  0         0  
128              
129 0 0 0     0 next if exists $seen{$_} && ( $seen{$_} // 0 );
      0        
130 0         0 $seen{$_} = 1;
131              
132 0 0       0 m/^([:](.*))$/ and do {
133 0   0     0 unshift @_, delete $tags{$1} // (), delete $tags{$2} // ();
      0        
134 0         0 next;
135             };
136 0         0 push @res, $_;
137             }
138             @res
139 0         0 }
140              
141              
142             # PRIVATE routines
143             # ref
144 8     8   24 sub _is_plain_arrayref { ref( $_[0] ) eq 'ARRAY' }
145 28     28   83 sub _is_plain_hashref { ref( $_[0] ) eq 'HASH' }
146 0     0   0 sub _is_plain_scalarref { ref( $_[0] ) eq 'SCALAR' }
147 0     0   0 sub _is_plain_scalar { !ref( $_[0] ) }
148              
149             # List
150             sub _flat { # shamelessly copied from: [List::_flat](https://metacpan.org/pod/List::_flat)
151 8     8   12 my @results;
152              
153 8         57 while (@_) {
154 8 50       23 if ( _is_plain_arrayref( my $element = shift @_ ) ) {
155 0         0 unshift @_, @{$element};
  0         0  
156             }
157             else {
158 8         25 push @results, $element;
159             }
160             }
161 8 50       22 return wantarray ? @results : \@results; ## no critic
162             }
163              
164             sub _kv_sort {
165 8     8   55 unpairs sort { $a->[0] cmp $b->[0] } pairs(@_)
  9         56  
166             }
167              
168             1;
169              
170             __END__