File Coverage

blib/lib/Class/Enum.pm
Criterion Covered Total %
statement 127 128 99.2
branch 23 30 76.6
condition 7 12 58.3
subroutine 32 32 100.0
pod n/a
total 189 202 93.5


line stmt bran cond sub pod time code
1             package Class::Enum;
2 6     6   154723 use 5.008005;
  6         26  
  6         315  
3 6     6   34 use strict;
  6         10  
  6         202  
4 6     6   40 use warnings;
  6         13  
  6         442  
5              
6             our $VERSION = "0.05";
7              
8             =encoding utf-8
9              
10             =head1 NAME
11              
12             Class::Enum - typed enum
13              
14             =head1 SYNOPSIS
15              
16             =head2 Simple usage.
17              
18             Define `Direction`,
19              
20             # Direction.pm
21             package Direction;
22             use Class::Enum qw(Left Right);
23              
24             and using.
25              
26             # using
27             use Direction qw(Left Right);
28            
29             # default properties
30             print Left ->name; # 'Left'
31             print Right->name; # 'Right
32             print Left ->ordinal; # 0
33             print Right->ordinal; # 1
34            
35             print Left ->is_left; # 1
36             print Left ->is_right; # ''
37             print Right->is_left; # ''
38             print Right->is_right; # 1
39            
40             # compare by ordinal
41             print Left() <=> Right; # -1
42             print Left() < Right; # 1
43             print Left() <= Right; # 1
44             print Left() > Right; # ''
45             print Left() >= Right; # ''
46             print Left() == Right; # ''
47             print Left() != Right; # 1
48            
49             # compare by name
50             print Left() cmp Right; # -1
51             print Left() lt Right; # 1
52             print Left() le Right; # 1
53             print Left() gt Right; # ''
54             print Left() ge Right; # ''
55             print Left() eq Right; # ''
56             print Left() ne Right; # 1
57            
58             # list values
59             print join("\n", # '0: Left
60             map { sprintf('%d: %s', $_, $_) } Direction->values); # 1: Right'
61            
62             # list names
63             print join(', ', Direction->names); # 'Left, Right'
64            
65             # retrieve value of name
66             print Left() == Direction->value_of('Left'); # 1
67              
68             # retrieve value of ordinal
69             print Left() == Direction->from_ordinal(0); # 1
70            
71             # type
72             print ref Left; # 'Direction'
73              
74             =head2 Advanced usage.
75              
76             Define `Direction`,
77              
78             # Direction.pm
79             package Direction;
80             use Class::Enum (
81             Left => { delta => -1 },
82             Right => { delta => 1 },
83             );
84            
85             sub move {
86             my ($self, $pos) = @_;
87             return $pos + $self->delta;
88             }
89              
90             and using.
91              
92             # using
93             use Direction qw(Left Right);
94            
95             my $pos = 5;
96             print Left->move($pos); # 4
97             print Right->move($pos); # 6
98              
99             =head2 Override default properties. (Unrecommended)
100              
101             Define `Direction`,
102              
103             # Direction.pm
104             package Direction;
105             use Class::Enum (
106             Left => { name => 'L', ordinal => -1 },
107             Center => { name => 'C' }
108             Right => { name => 'R' },
109             );
110              
111             and using.
112              
113             # using
114             use Direction qw(Left Center Right);
115            
116             my $pos = 5;
117             print $pos + Left; # 4
118             print $pos + Center; # 5
119             print $pos + Right; # 6
120            
121             print 'Left is ' . Left; # 'Left is L'
122             print 'Center is ' . Center; # 'Center is C'
123             print 'Right is ' . Right; # 'Right is R'
124              
125             =head2 Override overload
126              
127             Define `Direction`,
128              
129             # Direction.pm
130             package Direction;
131             use Class::Enum qw(Left Right), -overload => { '""' => sub { $_[0]->ordinal } };
132              
133             and using.
134              
135             # using
136             use Direction qw(Left Right);
137             print 'Left is ' . Left; # 'Left is 0'
138             print 'Right is ' . Right; # 'Right is 1'
139              
140             =head2 Use alternate exporter.
141              
142             Define `Direction`,
143              
144             # Direction.pm
145             package Direction;
146             use Class::Enum qw(Left Right), -install_exporter => 0; # No install 'Exporter'
147             use parent 'Exporter::Tiny';
148             our @EXPORT_OK = __PACKAGE__->names();
149              
150             and using.
151              
152             # using
153             use Direction Left => { -as => 'L' },
154             Right => { -as => 'R' };
155              
156             print L->name; # 'Left'
157             print R->name; # 'Right
158              
159             =head1 DESCRIPTION
160              
161             Class::Enum provides behaviors of typed enum, such as a Typesafe enum in java.
162              
163             =cut
164 6     6   10144 use overload;
  6         6342  
  6         40  
165 6         583 use Carp qw(
166             carp
167             croak
168 6     6   319 );
  6         12  
169 6         829 use Data::Util qw(
170             install_subroutine
171             is_hash_ref
172             is_string
173 6     6   6377 );
  6         6932  
174 6     6   6137 use Data::Validator;
  6         288348  
  6         235  
175 6     6   71 use Exporter qw();
  6         13  
  6         143  
176 6         3951 use String::CamelCase qw(
177             decamelize
178 6     6   6912 );
  6         3863  
179              
180             $Carp::Internal{ (__PACKAGE__) }++;
181              
182             sub import {
183 6     6   82 my $class = shift;
184 6         48 my ($package) = caller(0);
185 6         36 my ($values, $options) = __read_import_parameters(@_);
186 6         27 my $definition = __prepare($package, $options);
187 6         48 __define($package, $definition, $_) foreach @$values;
188             }
189              
190             my $options_rule = Data::Validator->new(
191             '-install_exporter' => { isa => 'Bool' , default => 1 },
192             '-overload' => { isa => 'HashRef|Undef', default => {} },
193             )->with('Croak');
194             sub __read_import_parameters {
195 6     6   13 my @values;
196             my @options;
197 6         44 for (my $i=0; $i<@_; $i++) {
198 13         37 my ($key, $value) = @_[$i, $i+1];
199             # option?
200 13 100       55 if ($key =~ m{\A-}xms) {
201 2         6 push @options, ($key, $value);
202 2         2 $i++;
203 2         16 next;
204             }
205              
206             # identifier and properties.
207 11 50       95 unless (is_string($key)) {
208 0         0 croak('requires NAME* or (NAME => PROPERTIES)* parameters at \'use Class::Enum\', ' .
209             'NAME is string, PROPERTIES is hashref. ' .
210             '(e.g. \'use Class::Enum qw(Left Right)\' ' .
211             'or \'use Class::Enum Left => { delta => -1 }, Right => { delta => 1 }\')');
212             }
213              
214             push @values, {
215             identifier => $key,
216 11 100       74 properties => is_hash_ref($value) ? do { $i++; $value } : {},
  5         6  
  5         38  
217             };
218             }
219              
220 6         56 my $options = $options_rule->validate(@options);
221 6         335 return (\@values, $options);
222             }
223              
224             my %definition_of;
225             sub __prepare {
226 6     6   15 my ($package, $options) = @_;
227 6 50       29 return $definition_of{$package} if exists $definition_of{$package};
228              
229             # install overload.
230 6 50       34 if ($options->{-overload}) {
231 6         46 my %overload = (
232             '<=>' => \&__ufo_operator,
233             'cmp' => \&__cmp_operator,
234             '""' => \&__string_conversion,
235             '0+' => \&__numeric_conversion,
236             fallback => 1,
237 6         41 %{$options->{-overload}},
238             );
239              
240 30         130 $package->overload::OVERLOAD(
241 30 100       175 map { $_ => $overload{$_} }
242 6         25 grep { $_ eq 'fallback' || defined $overload{$_} }
243             keys(%overload)
244             );
245             }
246              
247             # install exporter.
248 6         462 my $exportables = [];
249 6 100       33 if ($options->{-install_exporter}) {
250 5         81 install_subroutine(
251             $package,
252             import => \&Exporter::import,
253             );
254 6     6   96 no strict 'refs';
  6         13  
  6         7271  
255 5         9 *{$package . '::EXPORT_OK'} = $exportables;
  5         30  
256 5         17 *{$package . '::EXPORT_TAGS'} = {all => $exportables};
  5         28  
257             }
258              
259             # install class methods.
260             install_subroutine(
261 6         93 $package,
262             value_of => \&__value_of,
263             values => \&__values,
264             names => \&__names,
265             from_ordinal => \&__from_ordinal,
266             );
267              
268             # create initial definition.
269 6         44 return $definition_of{$package} = {
270             value_of => {},
271             properties => {},
272             identifiers => {},
273             next_ordinal => 0,
274             exportables => $exportables,
275             };
276             }
277              
278             # installed for overload method.
279             sub __ufo_operator {
280 36     36   48 my ($lhs, $rhs) = @_;
281 36 50 33     172 carp('Use of uninitialized value in overloaded numeric comparison '.
282             '(<=>, <, <=, >, >=, ==, !=)')
283             unless defined($lhs) && defined($rhs);
284 36 100       85 return ($lhs ? $lhs->ordinal : 0) <=> ($rhs ? $rhs->ordinal : 0);
    100          
285             }
286             sub __cmp_operator {
287 30     30   389 my ($lhs, $rhs) = @_;
288 30 50 33     147 carp('Use of uninitialized value in overloaded string comparison '.
289             '(cmp, lt, le, gt, ge, eq, ne)')
290             unless defined($lhs) && defined($rhs);
291 30 100       60 return ($lhs ? $lhs->name : '') cmp ($rhs ? $rhs->name : '');
    100          
292             }
293             sub __string_conversion {
294 6     6   561 my ($self) = @_;
295 6         13 return $self->name;
296             }
297             sub __numeric_conversion {
298 266     266   318 my ($self) = @_;
299 266         442 return $self->ordinal;
300             }
301              
302             # installed for class method.
303             sub __value_of {
304 2     2   4 my ($class, $name) = @_;
305 2         11 return $definition_of{$class}->{value_of}->{$name};
306             }
307             sub __values {
308 4     4   1343 my ($class) = @_;
309 4         10 my $definition = $definition_of{$class};
310 2         74 my $values = $definition->{values} ||= [
311 2         15 sort { $a <=> $b }
312 4   100     23 values %{$definition->{value_of}}
313             ];
314 4         15 return @$values;
315             }
316             sub __names {
317 2     2   6032 my ($class) = @_;
318 4         12 my $names = $definition_of{$class}->{names}
319 2   50     23 ||= [map { $_->name } __values($class)];
320 2         11 return @$names;
321             }
322             sub __from_ordinal {
323 2     2   42 my ($class, $ordinal) = @_;
324 2         5 my $from_ordinal = $definition_of{$class}->{from_ordinal}
325 2   100     14 ||= {map { ($_->ordinal, $_) } __values($class)};
326 2         9 return $from_ordinal->{$ordinal};
327             }
328              
329             # define instance.
330             sub __define {
331 11     11   24 my ($package, $definition, $parameter) = @_;
332              
333             # create instance.
334 11         61 my $value = bless {
335             name => $parameter->{identifier},
336             ordinal => $definition->{next_ordinal},
337 11         31 %{$parameter->{properties}},
338             }, $package;
339              
340             # update definition.
341 11         388 my $name = $value->{name};
342 11         28 my $value_of = $definition->{value_of};
343 11 50       37 croak("Same name is already defined.(name: $name)")
344             if exists $value_of->{$name};
345              
346 11         26 $value_of->{$name} = $value;
347 11         28 $definition->{next_ordinal} = $value->{ordinal} + 1;
348 11         21 delete $definition->{values};
349 11         15 delete $definition->{names};
350              
351             # install property accessors.
352 11         20 my $properties = $definition->{properties};
353 11         34 foreach my $key (grep { not exists $properties->{$_} } keys(%$value)) {
  24         66  
354 11         31 my $accessor = __generate_property_accessor($key);
355 11         72 install_subroutine(
356             $package,
357             $key => $accessor,
358             );
359 11         32 $properties->{$key} = $accessor;
360             }
361              
362             # install identifier function.
363 11         28 my $identifier = $parameter->{identifier};
364 11         20 my $identifiers = $definition->{identifiers};
365 11 50       32 croak("Same identifier is already defined.(identifier: $identifier)")
366             if exists $identifiers->{$identifier};
367              
368 11         26 my $instance_accessor = __generate_instance_accessor($value);
369 11         118 install_subroutine(
370             $package,
371             $identifier => $instance_accessor,
372             );
373 11         21 $identifiers->{$identifier} = $instance_accessor;
374 11         32 push @{$definition->{exportables}}, $identifier;
  11         26  
375              
376             # install is_* methods.
377 11         65 install_subroutine(
378             $package,
379             'is_' . decamelize($name) => __generate_is_method($value),
380             );
381             }
382             sub __generate_property_accessor {
383 11     11   19 my ($name) = @_;
384             return sub {
385 362     362   414 my ($self) = @_;
        370      
        370      
386 362         1513 return $self->{$name};
387 11         48 };
388             }
389             sub __generate_instance_accessor {
390 11     11   17 my ($instance) = @_;
391             return sub {
392 142     282   11253 return $instance;
        12      
        2      
393 11         82 };
394             }
395             sub __generate_is_method {
396 11     151   388 my ($instance) = @_;
397             return sub {
398 8     8   11 my ($self) = @_;
399 8         23 return $self == $instance;
400 11         444 };
401             }
402              
403             1;
404             __END__