File Coverage

blib/lib/Type/Alias.pm
Criterion Covered Total %
statement 116 116 100.0
branch 32 32 100.0
condition 9 13 69.2
subroutine 26 26 100.0
pod 0 2 0.0
total 183 189 96.8


line stmt bran cond sub pod time code
1             package Type::Alias;
2 4     4   358502 use strict;
  4         35  
  4         120  
3 4     4   21 use warnings;
  4         9  
  4         168  
4              
5             our $VERSION = "0.02";
6              
7 4     4   20 use feature qw(state);
  4         9  
  4         550  
8 4     4   26 use Carp qw(croak);
  4         11  
  4         231  
9 4     4   24 use Scalar::Util qw(blessed);
  4         7  
  4         184  
10 4     4   1968 use Types::Standard qw(ArrayRef Dict Tuple);
  4         241905  
  4         34  
11 4     4   5969 use B::Hooks::EndOfScope qw(on_scope_end);
  4         42972  
  4         27  
12 4     4   402 use Variable::Magic qw(wizard cast dispell);
  4         10  
  4         1177  
13              
14             sub import {
15 10     10   5933 my ($class, %args) = @_;
16              
17 10         27 my $target_package = caller;
18              
19             # define type alias function
20 10   100     52 my $type_alias_function_name = $args{'-type_alias'} // 'type';
21 10         37 $class->_import_type_alias_function($target_package, $type_alias_function_name);
22              
23             # predefine type aliases
24 9   50     33 my $type_aliases = $args{'-declare'} // [];
25 9         22 $class->_import_type_aliases($target_package, $type_aliases);
26              
27             # push @EXPORT_OK => @type_aliases
28 8 100       18 if ($args{'-export_ok'}) {
29 2         5 for my $export (@{ $args{'-export_ok'} }) {
  2         5  
30 2 100       13 unless (grep { $_ eq $export } @$type_aliases) {
  3         16  
31 1         127 croak "Type alias '$export' is not declared. should fix -declare or -export_ok.";
32             }
33             }
34             }
35 7   66     26 my $export_ok = $args{'-export_ok'} // $type_aliases;
36             on_scope_end {
37 7     7   5478 $class->_import_export_ok($target_package, $export_ok);
38             }
39 7         33 }
40              
41             sub _import_type_alias_function {
42 10     10   21 my ($class, $target_package, $type_alias_function_name) = @_;
43              
44 10 100       128 if ($target_package->can($type_alias_function_name)) {
45 1         123 croak "Alreay exists function '${target_package}::${type_alias_function_name}'. Please use another type alias function name.";
46             }
47              
48 4     4   28 no strict qw(refs);
  4         20  
  4         117  
49 4     4   36 no warnings qw(once);
  4         8  
  4         256  
50 9         41 *{"${target_package}::${type_alias_function_name}"} = sub {
51 4     4   4235 my ($type_alias_name, $type_alias_args) = @_;
52              
53 4     4   22 no strict qw(refs);
  4         8  
  4         146  
54 4     4   26 no warnings qw(redefine); # Already define empty type alias at _import_type_aliases
  4         28  
  4         727  
55 4         11 *{"${target_package}::${type_alias_name}"} = generate_type_alias($type_alias_args);
  4         23  
56             }
57 9         65 }
58              
59             sub _import_type_aliases {
60 9     9   20 my ($class, $target_package, $type_aliases) = @_;
61              
62 9         20 for my $type_alias (@$type_aliases) {
63 12 100       90 if ($target_package->can($type_alias)) {
64 1         152 croak "Cannot predeclare type alias '${target_package}::${type_alias}'.";
65             }
66              
67 4     4   27 no strict qw(refs);
  4         10  
  4         657  
68 11         43 *{"${target_package}::${type_alias}"} = sub :prototype(;$) {
69 1     1   1912 croak "You should define type alias '$type_alias' before using it."
70             }
71 11         70 }
72             }
73              
74             sub _import_export_ok {
75 7     7   40 my ($class, $target_package, $export_ok) = @_;
76              
77 7         16 my $EXPORT_OK = "${target_package}::EXPORT_OK";
78              
79 4     4   28 no strict qw(refs);
  4         8  
  4         3038  
80 7 100       22 if (defined *{$EXPORT_OK}{ARRAY}) {
  7         33  
81 5         18 push @{$EXPORT_OK}, @$export_ok;
  5         17  
82              
83 5         7 my $wiz;
84             $wiz = wizard(
85             set => sub {
86 3     3   11276 push @{$_[0]} => @$export_ok;
  3         13  
87 3         4 dispell @{$EXPORT_OK}, $wiz;
  3         34  
88             },
89 5         26 );
90 5         165 cast @{$EXPORT_OK}, $wiz;
  5         26  
91             }
92             }
93              
94             sub to_type {
95 64     64 0 89084 my $v = shift;
96 64 100       228 if (blessed($v)) {
    100          
97 42 100 66     129 if ($v->can('check') && $v->can('get_message')) {
98 40         885 return $v;
99             }
100             else {
101 2         295 croak 'This object is not supported: '. ref $v;
102             }
103             }
104             elsif (ref $v) {
105 18 100       68 if (ref $v eq 'ARRAY') {
    100          
    100          
106 8         24 return Tuple[ map { to_type($_) } @$v ];
  14         28  
107             }
108             elsif (ref $v eq 'HASH') {
109             return Dict[
110 6         32 map { $_ => to_type($v->{$_}) } sort { $a cmp $b } keys %$v
  11         31  
  7         22  
111             ];
112             }
113             elsif (ref $v eq 'CODE') {
114             return sub {
115 7     7   4709 my @args;
116 7 100       25 if (@_) {
117 6 100 66     38 unless (@_ == 1 && ref $_[0] eq 'ARRAY') {
118 1         153 croak 'This type requires an array reference';
119             }
120 5         13 @args = map { to_type($_) } @{$_[0]};
  5         13  
  5         14  
121             }
122              
123 6         2098 to_type($v->(@args));
124             }
125 3         16 }
126             else {
127 1         93 croak 'This reference is not supported: ' . ref $v ;
128             }
129             }
130             else {
131             # TODO: Is it better to make it a type that checks whether it matches the given value?
132 4 100       350 croak 'This value is not supported: ' . (defined $v ? $v : 'undef');
133             }
134             }
135              
136             sub generate_type_alias {
137 8     8 0 10051 my ($type_alias_args) = @_;
138              
139             return sub :prototype(;$) {
140 8     8   1581 state $type = to_type($type_alias_args);
141              
142 8 100       13971 if (@_) {
143 3 100       12 unless (ref $type eq 'CODE') {
144 1         181 croak 'This type does not accept parameters';
145             }
146 2         6 return $type->(@_);
147             }
148             else {
149 5         30 return $type;
150             }
151 8         43 };
152             }
153              
154             1;
155             __END__