Class-Enumeration/lib/Class/Enumeration/Builder.pm
# Prefer numeric version for backwards compatibility
BEGIN { require 5.010_001 }; ## no critic ( RequireUseStrict, RequireUseWarnings )
use strict;
use warnings;
use feature 'state';
package Class::Enumeration::Builder;
$Class::Enumeration::Builder::VERSION = 'v1.3.0';
use subs qw( _create_enum_object _is_equal );
use Carp qw( carp croak );
use Sub::Util qw( set_subname );
use Class::Enumeration ();
sub import {
shift;
# TODO: Some options are relevant when import() is called at compile time;
# others if import() is called at runtime.
# If ( caller( 1 ) )[ 3 ] matches .*::BEGIN import() is called at compile
# time.
my $options = ref $_[ 0 ] eq 'HASH' ? shift : {};
# $class == enum class
my $class = exists $options->{ class } ? delete $options->{ class } : caller;
carp( "Enum class '$class' already built, warned" ), return $class ## no critic ( ProhibitCommaSeparatedStatements )
if do { no strict 'refs'; defined &{ "$class\::values" } }; ## no critic ( ProhibitNoStrict )
# Now start building the enum class
{
no strict 'refs'; ## no critic ( ProhibitNoStrict )
push @{ "$class\::ISA" }, 'Class::Enumeration'
}
my @values;
my $counter = exists $options->{ counter } ? delete $options->{ counter } : sub { state $i = 0; $i++ };
my $prefix = exists $options->{ prefix } ? delete $options->{ prefix } : '';
# Check if custom attributes were provided
if ( ref $_[ 1 ] eq 'HASH' ) {
my ( $reference_name, $reference_attributes ) = @_[ 0 .. 1 ];
# Build list (@values) of enum objects
while ( my ( $name, $attributes ) = splice @_, 0, 2 ) {
croak "'$reference_name' enum and '$name' enum have different custom attributes, stopped"
unless _is_equal $reference_attributes, $attributes;
push @values, _create_enum_object $class, $counter, $prefix, $name, $attributes
}
# Build getters for custom attributes
for my $getter ( keys %$reference_attributes ) {
no strict 'refs'; ## no critic ( ProhibitNoStrict )
*{ "$class\::$getter" } = set_subname "$class\::$getter" => sub { my ( $self ) = @_; $self->{ $getter } }
}
} else {
# Build list (@values) of enum objects
foreach my $name ( @_ ) {
push @values, _create_enum_object $class, $counter, $prefix, $name;
}
}
{
{
no strict 'refs'; ## no critic ( ProhibitNoStrict )
# Inject list of enum objects
*{ "$class\::values" } = sub {
sort { $a->ordinal <=> $b->ordinal } @values
}
}
# Optionally build enum constants and set @EXPORT_OK and %EXPORT_TAGS
if ( delete $options->{ export } ) {
my @names;
no strict 'refs'; ## no critic ( ProhibitNoStrict )
for my $self ( @values ) {
push @names, my $name = $self->name;
*{ "$class\::$name" } = sub () { $self }
}
*{ "$class\::EXPORT_OK" } = \@names;
*{ "$class\::EXPORT_TAGS" } = { all => \@names };
}
# Optionally build enum object predicate methods
if ( delete $options->{ predicate } ) {
no strict 'refs'; ## no critic ( ProhibitNoStrict )
for my $self ( @values ) {
my $name = $self->name;
*{ "$class\::is_$name" } = sub { $_[ 0 ] == $self }
}
}
if ( delete $options->{ to_json } ) {
no strict 'refs'; ## no critic ( ProhibitNoStrict )
*{ "$class\::TO_JSON" } = sub { $_[ 0 ]->name }
}
}
croak "Unknown options '${ \( join( q/', '/, keys %$options ) ) }' detected, stopped"
if %$options;
$class
}
sub _create_enum_object ( $$$$;$ ) {
my ( $class, $counter, $prefix, $name, $attributes ) = @_;
# Put each enum object in its own (dedicated) child class of the parent
# enum class
my $child_class = "$class\::$name";
{
no strict 'refs'; ## no critic ( ProhibitNoStrict )
push @{ "$child_class\::ISA" }, $class
}
$child_class->_new( $counter->(), $prefix . $name, $attributes )
}
# Compare 2 sets of hash keys
sub _is_equal ( $$ ) {
my ( $reference_attributes, $attributes ) = @_;
my @reference_attributes = keys %$reference_attributes;
return unless @reference_attributes == keys %$attributes;
for ( @reference_attributes ) {
return unless exists $attributes->{ $_ }
}
1
}
1