File Coverage

blib/lib/Class/Enumeration/Builder.pm
Criterion Covered Total %
statement 114 114 100.0
branch 26 26 100.0
condition n/a
subroutine 28 28 100.0
pod n/a
total 168 168 100.0


line stmt bran cond sub pod time code
1             # Prefer numeric version for backwards compatibility
2 7     7   844666 BEGIN { require 5.010001 }; ## no critic ( RequireUseStrict, RequireUseWarnings )
3 7     7   34 use strict;
  7         11  
  7         200  
4 7     7   30 use warnings;
  7         19  
  7         374  
5 7     7   36 use feature 'state';
  7         11  
  7         1212  
6              
7             package Class::Enumeration::Builder;
8              
9             $Class::Enumeration::Builder::VERSION = 'v1.3.1';
10              
11 7     7   2930 use subs qw( _create_enum_object _is_equal );
  7         1575  
  7         40  
12              
13 7     7   414 use Carp qw( carp croak );
  7         13  
  7         462  
14 7     7   37 use Sub::Util qw( set_subname );
  7         10  
  7         438  
15              
16 7     7   3435 use Class::Enumeration ();
  7         21  
  7         677  
17              
18             sub import {
19 12     12   220557 shift;
20              
21             # TODO: Some options are relevant when import() is called at compile time;
22             # others if import() is called at runtime.
23             # If ( caller( 1 ) )[ 3 ] matches .*::BEGIN import() is called at compile
24             # time.
25 12 100       51 my $options = ref $_[ 0 ] eq 'HASH' ? shift : {};
26              
27             # $class == enum class
28 12 100       74 my $class = exists $options->{ class } ? delete $options->{ class } : caller;
29             carp( "Enum class '$class' already built, warned" ), return $class ## no critic ( ProhibitCommaSeparatedStatements )
30 7 100   7   40 if do { no strict 'refs'; defined &{ "$class\::values" } }; ## no critic ( ProhibitNoStrict )
  7         13  
  7         565  
  12         77  
  12         22  
  12         117  
31              
32             # Now start building the enum class
33             {
34 7     7   37 no strict 'refs'; ## no critic ( ProhibitNoStrict )
  7         21  
  7         1875  
  11         23  
35 11         17 push @{ "$class\::ISA" }, 'Class::Enumeration'
  11         155  
36             }
37              
38 11         28 my @values;
39 11 100   19   72 my $counter = exists $options->{ counter } ? delete $options->{ counter } : sub { state $i = 0; $i++ };
  19         38  
  19         161  
40 11 100       34 my $prefix = exists $options->{ prefix } ? delete $options->{ prefix } : '';
41             # Check if custom attributes were provided
42 11 100       34 if ( ref $_[ 1 ] eq 'HASH' ) {
43 5         17 my ( $reference_name, $reference_attributes ) = @_[ 0 .. 1 ];
44             # Build list (@values) of enum objects
45 5         22 while ( my ( $name, $attributes ) = splice @_, 0, 2 ) {
46 12 100       30 croak "'$reference_name' enum and '$name' enum have different custom attributes, stopped"
47             unless _is_equal $reference_attributes, $attributes;
48 10         23 push @values, _create_enum_object $class, $counter, $prefix, $name, $attributes
49             }
50             # Build getters for custom attributes
51 3         10 for my $getter ( keys %$reference_attributes ) {
52 7     7   48 no strict 'refs'; ## no critic ( ProhibitNoStrict )
  7         10  
  7         1397  
53 3     10   22 *{ "$class\::$getter" } = set_subname "$class\::$getter" => sub { my ( $self ) = @_; $self->{ $getter } }
  10     1   118  
  10     1   45  
        9      
54 3         32 }
55             } else {
56             # Build list (@values) of enum objects
57 6         15 foreach my $name ( @_ ) {
58 16         42 push @values, _create_enum_object $class, $counter, $prefix, $name;
59             }
60             }
61              
62             {
63 9         14 {
64 7     7   40 no strict 'refs'; ## no critic ( ProhibitNoStrict )
  7         13  
  7         937  
  9         14  
65             # Inject list of enum objects
66 9         53 *{ "$class\::values" } = sub {
67 55     55   1123016 sort { $a->ordinal <=> $b->ordinal } @values
  141         811  
68             }
69 9         34 }
70             # Optionally build enum constants and set @EXPORT_OK and %EXPORT_TAGS
71 9 100       53 if ( delete $options->{ export } ) {
72 3         6 my @names;
73 7     7   38 no strict 'refs'; ## no critic ( ProhibitNoStrict )
  7         10  
  7         1162  
74 3         6 for my $self ( @values ) {
75 10         45 push @names, my $name = $self->name;
76 10     13   52 *{ "$class\::$name" } = sub () { $self }
  13         6016  
77 10         38 }
78 3         6 *{ "$class\::EXPORT_OK" } = \@names;
  3         11  
79 3         9 *{ "$class\::EXPORT_TAGS" } = { all => \@names };
  3         11  
80             }
81             # Optionally build enum object predicate methods
82 9 100       37 if ( delete $options->{ predicate } ) {
83 7     7   37 no strict 'refs'; ## no critic ( ProhibitNoStrict )
  7         11  
  7         941  
84 1         1 for my $self ( @values ) {
85 2         5 my $name = $self->name;
86 2     4   8 *{ "$class\::is_$name" } = sub { $_[ 0 ] == $self }
  4         11  
87 2         3 }
88             }
89 9 100       27 if ( delete $options->{ to_json } ) {
90 7     7   37 no strict 'refs'; ## no critic ( ProhibitNoStrict )
  7         10  
  7         1887  
91 1     2   15 *{ "$class\::TO_JSON" } = sub { $_[ 0 ]->name }
  2         11  
92 1         3 }
93             }
94              
95 9 100       37 croak "Unknown options '${ \( join( q/', '/, keys %$options ) ) }' detected, stopped"
  1         18  
96             if %$options;
97              
98 8         305 $class
99             }
100              
101             sub _create_enum_object ( $$$$;$ ) {
102 26     26   63 my ( $class, $counter, $prefix, $name, $attributes ) = @_;
103              
104             # Put each enum object in its own (dedicated) child class of the parent
105             # enum class
106 26         50 my $child_class = "$class\::$name";
107             {
108 7     7   40 no strict 'refs'; ## no critic ( ProhibitNoStrict )
  7         18  
  7         1615  
  26         65  
109 26         36 push @{ "$child_class\::ISA" }, $class
  26         303  
110             }
111              
112 26         88 $child_class->_new( $counter->(), $prefix . $name, $attributes )
113             }
114              
115             # Compare 2 sets of hash keys
116             sub _is_equal ( $$ ) {
117 12     12   25 my ( $reference_attributes, $attributes ) = @_;
118              
119 12         28 my @reference_attributes = keys %$reference_attributes;
120 12 100       71 return unless @reference_attributes == keys %$attributes;
121 11         29 for ( @reference_attributes ) {
122 10 100       42 return unless exists $attributes->{ $_ }
123             }
124             1
125 10         26 }
126              
127             1