File Coverage

blib/lib/CXC/Exporter/Util.pm
Criterion Covered Total %
statement 238 240 99.1
branch 36 46 78.2
condition 43 64 67.1
subroutine 37 37 100.0
pod 4 4 100.0
total 358 391 91.5


line stmt bran cond sub pod time code
1             package CXC::Exporter::Util;
2              
3             # ABSTRACT: Tagged Based Exporting
4              
5 8     8   1571605 use v5.22;
  8         45  
6              
7 8     8   43 use strict;
  8         16  
  8         221  
8 8     8   44 use warnings;
  8         15  
  8         535  
9              
10             #<<<
11             our $VERSION = '0.09'; # TRIAL
12             #>>>
13              
14 8     8   49 use Scalar::Util 'reftype';
  8         38  
  8         545  
15 8     8   41 use List::Util 1.45 'uniqstr';
  8         153  
  8         644  
16 8     8   674 use Import::Into;
  8         3224  
  8         258  
17 8     8   675 use experimental 'signatures', 'postderef', 'lexical_subs';
  8         1853  
  8         52  
18              
19 8     8   2031 use constant HAVE_EXPORTER_TINY => eval { require Exporter::Tiny; 1; }; ## no critic ( ReturnValueOfEval )
  8         39  
  8         19  
  8         4308  
  8         29423  
20 8     8   58 use if HAVE_EXPORTER_TINY, parent => 'Exporter::Tiny';
  8         14  
  8         3084  
21 8     8   2506 use if !HAVE_EXPORTER_TINY, Exporter => 'import';
  8         13  
  8         231  
22              
23 8     8   34 use constant UI_HELPERS => 'ui_helpers';
  8         13  
  8         1653  
24              
25             our %EXPORT_TAGS;
26              
27             BEGIN {
28              
29 8     8   129 %EXPORT_TAGS = (
30             default => [qw( install_EXPORTS )],
31             constants => [qw( install_CONSTANTS )],
32             utils => [qw( install_constant_tag install_constant_func )],
33             );
34              
35 8         19 if ( HAVE_EXPORTER_TINY ) {
36 8         1058 $EXPORT_TAGS{ +UI_HELPERS }
37             = [qw( ui_list_constants ui_coerce_constant ui_assert_coerce_constant )];
38             }
39              
40             }
41              
42             my %REGISTRY = (
43             HOOK => {},
44             CONSTANTS_TAGS => {},
45             UI => {},
46             );
47              
48             install_EXPORTS();
49              
50             my sub _croak {
51 1     1   12 require Carp;
52 1         219 goto \&Carp::croak;
53             }
54              
55              
56 88     88   122 my sub _EXPORT_TAGS ( $caller = scalar caller ) {
  88         106  
  88         86  
57 8     8   49 no strict 'refs'; ## no critic (TestingAndDebugging::ProhibitNostrict)
  8         15  
  8         1376  
58 88   100     87 *${ \"${caller}::EXPORT_TAGS" }{HASH} // \%{ *${ \"${caller}::EXPORT_TAGS" } = {} };
  88         521  
  8         14  
  8         69  
59             }
60              
61 20     20   27 my sub _EXPORT_OK ( $caller = scalar caller ) {
  20         31  
  20         35  
62 8     8   67 no strict 'refs'; ## no critic (TestingAndDebugging::ProhibitNostrict)
  8         40  
  8         1158  
63 20   100     35 *${ \"${caller}::EXPORT_OK" }{ARRAY} // \@{ *${ \"${caller}::EXPORT_OK" } = [] };
  20         183  
  19         64  
  19         149  
64             }
65              
66 20     20   26 my sub _EXPORT ( $caller = scalar caller ) {
  20         33  
  20         22  
67 8     8   64 no strict 'refs'; ## no critic (TestingAndDebugging::ProhibitNostrict)
  8         15  
  8         11153  
68 20   50     27 *${ \"${caller}::EXPORT" }{ARRAY} // \@{ *${ \"${caller}::EXPORT" } = [] };
  20         116  
  20         35  
  20         121  
69             }
70              
71             my sub add_constant_to_tag;
72              
73              
74              
75              
76              
77              
78              
79              
80              
81              
82              
83              
84              
85              
86              
87              
88              
89              
90              
91              
92              
93              
94              
95              
96              
97              
98              
99              
100              
101              
102              
103              
104              
105              
106              
107              
108              
109              
110              
111              
112              
113              
114              
115              
116              
117              
118              
119              
120              
121              
122              
123              
124              
125              
126              
127              
128              
129              
130              
131              
132              
133             sub install_EXPORTS {
134              
135 20 100 100 20 1 934772 my $export_tags = ( reftype( $_[0] ) // q{} ) eq 'HASH' ? shift : undef;
136 20 100 100     105 my $u_opts = ( reftype( $_[-1] ) // q{} ) eq 'HASH' ? shift : {};
137              
138 20   50     174 my %options = (
139             overwrite => 0,
140             all => 'auto',
141             package => shift // scalar caller,
142             %$u_opts,
143             );
144              
145 20 50       59 _croak( 'too many arguments to INSTALL_EXPORTS' ) if @_;
146              
147 20         40 my $package = delete $options{package};
148 20         56 my $install_all = delete $options{all};
149              
150             # run hooks.
151 20 100       74 if ( defined( my $hooks = delete $REGISTRY{HOOK}{$package}{pre} ) ) {
152 4         15 $_->() for values $hooks->%*;
153             }
154              
155 20         48 my $EXPORT_TAGS = _EXPORT_TAGS( $package );
156              
157 20 100       50 if ( defined $export_tags ) {
158              
159 7 100       17 if ( delete $options{overwrite} ) {
160 1         8 $EXPORT_TAGS->%* = $export_tags->%*;
161             }
162              
163             else {
164             # cheap one layer deep hash merge
165 6         17 for my $tag ( keys $export_tags->%* ) {
166 10   50     44 push( ( $EXPORT_TAGS->{$tag} //= [] )->@*, $export_tags->{$tag}->@* );
167             }
168             }
169             }
170              
171 20 100       59 if ( defined( my $hooks = delete $REGISTRY{HOOK}{$package}{post} ) ) {
172 3         12 $_->() for values $hooks->%*;
173             }
174              
175             # Exporter::Tiny handles the 'all' tag, as does Sub::Exporter, but
176             # I don't know how to detect when the latter is being used.
177 20 100       203 $install_all = !$package->isa( 'Exporter::Tiny' )
178             if $install_all eq 'auto';
179              
180 20 100       51 if ( $install_all ) {
181             # Assign the all tag in two steps to avoid the situation
182             # where $EXPORT_TAGS->{all} is created with an undefined value
183             # before running values on $EXPORT_TAGS->%*;
184              
185 7         21 my @all = map { $_->@* } values $EXPORT_TAGS->%*;
  20         45  
186 7   50     40 $EXPORT_TAGS->{all} //= \@all;
187             }
188              
189 20   100     88 _EXPORT( $package )->@* = ( $EXPORT_TAGS->{default} // [] )->@*;
190 20         55 _EXPORT_OK( $package )->@* = uniqstr map { $_->@* } values $EXPORT_TAGS->%*;
  81         435  
191              
192 20         178 delete $REGISTRY{HOOK}{$package};
193             }
194              
195              
196              
197              
198              
199              
200              
201              
202              
203              
204              
205              
206              
207              
208              
209              
210              
211              
212              
213              
214              
215              
216              
217              
218              
219              
220              
221              
222              
223              
224              
225              
226              
227              
228              
229              
230              
231              
232              
233              
234              
235              
236              
237              
238              
239              
240              
241              
242              
243              
244              
245              
246              
247              
248              
249              
250              
251              
252              
253              
254              
255             sub install_CONSTANTS {
256 8 50   8 1 597339 my $package = !defined reftype( $_[-1] ) ? pop : scalar caller;
257              
258 8         19 for my $spec ( @_ ) {
259 11         25 my $type = reftype( $spec );
260              
261 11 100       33 if ( 'HASH' eq $type ) {
    50          
262 6         30 install_constant_tag( $_, $spec->{$_}, $package ) for keys $spec->%*;
263             }
264              
265             elsif ( 'ARRAY' eq $type ) {
266 5         8 my $idx = $spec->@*;
267 5 50       18 _croak( 'constant spec passed as array has an odd number of elements' )
268             unless 0 == $idx % 2;
269              
270 5         16 while ( $idx ) {
271 5         9 my $hash = $spec->[ --$idx ];
272 5         8 my $id = $spec->[ --$idx ];
273 5         14 install_constant_tag( $id, $hash, $package );
274             }
275             }
276              
277             else {
278 0         0 _croak( 'expect a HashRef or an ArrayRef' );
279             }
280             }
281             }
282              
283              
284              
285              
286              
287              
288              
289              
290              
291              
292              
293              
294              
295              
296              
297              
298              
299              
300              
301              
302              
303              
304              
305              
306              
307              
308              
309              
310              
311              
312              
313              
314              
315              
316              
317              
318              
319              
320              
321              
322              
323              
324              
325              
326              
327              
328              
329              
330              
331              
332              
333              
334              
335              
336              
337              
338              
339              
340              
341              
342              
343              
344              
345              
346              
347              
348              
349              
350              
351              
352              
353              
354              
355              
356              
357              
358              
359              
360              
361              
362              
363              
364              
365              
366              
367              
368              
369              
370              
371              
372              
373              
374              
375              
376              
377              
378              
379              
380              
381              
382              
383              
384              
385              
386              
387              
388              
389              
390              
391              
392              
393              
394              
395              
396              
397              
398              
399              
400              
401              
402              
403              
404              
405              
406              
407              
408              
409              
410              
411              
412              
413              
414              
415              
416              
417              
418              
419              
420              
421              
422              
423              
424              
425              
426              
427              
428              
429              
430              
431              
432              
433              
434              
435              
436              
437              
438              
439              
440              
441              
442              
443              
444              
445              
446              
447              
448              
449              
450              
451              
452              
453              
454              
455              
456              
457              
458              
459              
460              
461              
462              
463              
464              
465              
466              
467              
468              
469              
470              
471              
472              
473              
474              
475              
476              
477 11     11 1 19 sub install_constant_tag ( $id, $constants, $package = scalar caller ) {
  11         17  
  11         17  
  11         13  
  11         29  
478              
479             # caller may specify distinct tag and enumeration function names.
480 11 100 100     65 my ( $tag, $fn_values, $fn_names )
481             = 'ARRAY' eq ( reftype( $id ) // q{} )
482             ? ( $id->@* )
483             : ( lc( $id ), $id );
484              
485 11   33     20 $fn_values //= uc( $tag );
486 11   66     43 $fn_names //= $fn_values . '_NAMES';
487              
488 11         14 my ( @names );
489 11 100       32 if ( reftype( $constants ) eq 'HASH' ) {
    50          
490 5         11 @names = keys $constants->%*;
491             }
492             elsif ( reftype( $constants ) eq 'ARRAY' ) {
493             # the list of names must be returned in the order specified,
494             # hence this bizzarity
495 6         28 @names = $constants->@[ map { $_ * 2 } 0 .. $constants->$#* / 2 ];
  28         47  
496 6         31 $constants = { $constants->@* };
497             }
498             else {
499 0         0 _croak( '$constants argument should be either a hashref or an arrayref' );
500             }
501              
502 11         59 constant->import::into( $package, $constants );
503              
504 11   100     2764 push( ( _EXPORT_TAGS( $package )->{$tag} //= [] )->@*, @names );
505              
506             $REGISTRY{HOOK}{$package}{pre}{$fn_values} //= sub {
507 9     9   44 my $fqdn = join q{::}, $package, $fn_values;
508             _croak( "Error: attempt to redefine enumerating function $fqdn" )
509 9 50       22 if exists &{$fqdn};
  9         35  
510 8     8   58 no strict 'refs'; ## no critic (TestingAndDebugging::ProhibitNostrict)
  8         28  
  8         12075  
511             my @values
512 9         18 = map { &{"${package}::${_}"} } _EXPORT_TAGS( $package )->{$tag}->@*;
  47         44  
  47         175  
513 9         18 install_constant_func( $fn_values, \@values, $package );
514 11   66     85 };
515              
516             $REGISTRY{HOOK}{$package}{pre}{$fn_names} //= sub {
517 9     9   24 my $fqdn = join q{::}, $package, $fn_names;
518             _croak( "Error: attempt to redefine enumerating function $fqdn" )
519 9 50       10 if exists &{$fqdn};
  9         29  
520             add_constant_to_tag( 'constant_name_funcs', $fn_names,
521 9         18 [ _EXPORT_TAGS( $package )->{$tag}->@* ], $package );
522              
523 11   66     94 };
524              
525             $REGISTRY{HOOK}{$package}{pre}{"register_tag:$tag"} //= sub {
526 7   100 7   36 ( $REGISTRY{CONSTANTS_TAGS}{$package} //= {} )->{$tag} = $fn_names;
527 11   66     432 };
528              
529             }
530              
531              
532              
533              
534              
535              
536              
537              
538              
539              
540              
541              
542              
543              
544              
545              
546              
547              
548              
549              
550              
551              
552              
553              
554              
555              
556              
557              
558              
559              
560              
561              
562              
563              
564              
565              
566              
567              
568              
569              
570              
571              
572              
573              
574 9     9 1 12 sub install_constant_func ( $tag, $values, $caller = scalar caller ) {
  9         11  
  9         23  
  9         12  
  9         10  
575 9         21 add_constant_to_tag( 'constant_funcs', $tag, $values, $caller );
576             # for backwards compatibility
577 9         16 _EXPORT_TAGS( $caller )->{constants_funcs} = _EXPORT_TAGS( $caller )->{constant_funcs};
578             }
579              
580              
581              
582              
583              
584              
585              
586              
587              
588              
589              
590              
591              
592              
593              
594              
595              
596              
597              
598              
599              
600              
601              
602              
603              
604              
605              
606              
607              
608              
609              
610              
611              
612              
613              
614              
615              
616              
617              
618              
619              
620              
621              
622              
623              
624              
625              
626 18     18   20 sub add_constant_to_tag ( $tag, $name, $values, $caller = scalar caller ) {
  18         22  
  18         38  
  18         22  
  18         17  
  18         18  
627 18         74 constant->import::into( $caller, $name => $values->@* );
628 18   100     2788 push( ( _EXPORT_TAGS( $caller )->{$tag} //= [] )->@*, $name );
629             }
630              
631 11     11   14 my sub ui_entry ( $target, $tag, $pfx ) {
  11         17  
  11         30  
  11         13  
  11         16  
632              
633 11   33     41 my $tags = $REGISTRY{CONSTANTS_TAGS}{$target}
634             // _croak( "Can't find any constant tags associated with '$target'" );
635              
636 11   33     33 my $names = $tags->{$tag} // _croak( "unknown constant tag '$tag'" );
637              
638             exists $REGISTRY{UI}{$target}{$tag}{$pfx}
639 11 100       76 and return $REGISTRY{UI}{$target}{$tag}{$pfx};
640              
641 4         10 my $entry = $REGISTRY{UI}{$target}{$tag}{$pfx} = {};
642              
643 4 50       44 my $sub = $target->can( $names )
644             or die( "internal error: Can't find subroutine ${target}::$names" )
645             ; # yes, die. so error points to this line.
646              
647 4         12 my %values;
648             my %valsubs;
649 4         0 my @names;
650              
651             # Copy each returned name before mutation; $sub is generated by
652             # the 'constant' pragma, and may return aliases to the actual
653             # constants, which cannot be mutated.
654 4         14 for my $const_name ( $sub->() ) {
655 31         31 my $name = $const_name;
656              
657 31   33     61 my $valsub = $valsubs{$name} //= do {
658 31 50       71 my $coderef = $target->can( $name )
659             or die( "internal error: Can't find subroutine ${target}::$name" );
660 31         112 sub { $coderef->() };
  5         38  
661             };
662              
663 31 100 66     122 defined $pfx
664             and length $pfx
665             and $name =~ s/^\Q${pfx}\E_?//;
666              
667 31         31 my %names;
668 31         36 $name = lc $name;
669 31         48 @names{$name} = ();
670 31         39 $name =~ s/_/-/g;
671 31         33 @names{$name} = ();
672 31         53 my @lnames = keys %names;
673 31         37 push @names, @lnames;
674 31         76 @values{@lnames} = ( $valsub ) x @lnames;
675             }
676 4         29 $entry->{names} = [ sort @names ];
677 4         10 $entry->{values} = \%values;
678              
679 4         33 return $entry;
680             }
681              
682 3     3   247 sub _exporter_expand_tag ( $class, $name, $value, $globals ) {
  3         4  
  3         4  
  3         4  
  3         5  
  3         5  
683              
684 3 50 66     13 if ( $name eq UI_HELPERS or $name eq 'all' ) {
685 3         3 my $target = $globals->{into};
686             $REGISTRY{HOOK}{$target}{post}{add_helpers} //= sub {
687 3     3   16 _EXPORT_TAGS( $target )->{ +UI_HELPERS } = [ $EXPORT_TAGS{ +UI_HELPERS }->@* ];
688 3   33     25 };
689             }
690 3         19 $class->SUPER::_exporter_expand_tag( $name, $value, $globals );
691             }
692              
693              
694              
695              
696              
697              
698              
699              
700              
701              
702              
703              
704              
705              
706              
707 3     3   1162 sub _generate_ui_list_constants ( $class, $name, $args, $globals ) {
  3         6  
  3         4  
  3         5  
  3         4  
  3         4  
708 3         4 my $target = $globals->{into};
709 3     3   16096 sub ( $tag, $pfx = q{} ) {
  3         7  
  3         5  
  3         4  
710 3         9 return ui_entry( $target, $tag, $pfx )->{names}->@*;
711             }
712 3         21 }
713              
714              
715              
716              
717              
718              
719              
720              
721              
722              
723              
724              
725 3     3   153 sub _generate_ui_coerce_constant ( $class, $name, $args, $globals ) {
  3         7  
  3         3  
  3         14  
  3         4  
  3         3  
726 3         3 my $target = $globals->{into};
727 6     6   14887 sub ( $name, $tag, $pfx = q{} ) {
  6         13  
  6         11  
  6         13  
  6         9  
728 6   100     22 my $valsub = ui_entry( $target, $tag, $pfx )->{values}{$name} // return $name;
729 4         14 return $valsub->();
730             }
731 3         14 }
732              
733              
734              
735              
736              
737              
738              
739              
740              
741              
742              
743              
744 3     3   125 sub _generate_ui_assert_coerce_constant ( $class, $name, $args, $globals ) {
  3         4  
  3         4  
  3         3  
  3         3  
  3         3  
745 3         3 my $target = $globals->{into};
746 2     2   2280 sub ( $name, $tag, $pfx = q{} ) {
  2         6  
  2         4  
  2         3  
  2         4  
747 2   66     7 my $valsub = ui_entry( $target, $tag, $pfx )->{values}{$name}
748             // _croak( "unknown constant $tag: $name" );
749 1         3 return $valsub->();
750             }
751 3         16 }
752              
753             1;
754              
755             #
756             # This file is part of CXC-Exporter-Util
757             #
758             # This software is Copyright (c) 2022 by Smithsonian Astrophysical Observatory.
759             #
760             # This is free software, licensed under:
761             #
762             # The GNU General Public License, Version 3, June 2007
763             #
764              
765             __END__