File Coverage

blib/lib/Exporter/Handy/Util.pm
Criterion Covered Total %
statement 70 110 63.6
branch 19 36 52.7
condition 23 72 31.9
subroutine 13 20 65.0
pod 2 2 100.0
total 127 240 52.9


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