File Coverage

blib/lib/Exporter/Handy/Util.pm
Criterion Covered Total %
statement 70 105 66.6
branch 19 36 52.7
condition 23 72 31.9
subroutine 13 20 65.0
pod 2 2 100.0
total 127 235 54.0


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