File Coverage

blib/lib/Quaint.pm
Criterion Covered Total %
statement 208 216 96.3
branch 40 48 83.3
condition 9 15 60.0
subroutine 57 57 100.0
pod 0 6 0.0
total 314 342 91.8


line stmt bran cond sub pod time code
1             package Quaint;
2              
3 5     12   660931 use 5.006;
  5         17  
4 5     5   27 use strict;
  5         8  
  5         174  
5 5     5   50 use warnings;
  5         22  
  5         324  
6 5     5   2759 use meta;
  5         5681  
  5         269  
7 5     5   2645 use Module::Runtime qw/use_module/;
  5         10090  
  5         32  
8 5     5   3473 use Types::Standard qw/Any Bool Str Int Num HashRef ArrayRef Object/;
  5         809809  
  5         112  
9              
10             our $VERSION = '0.04';
11              
12             sub import {
13 9     9   127 my ($caller) = caller();
14              
15 9         19 my $metapkg;
16             {
17 5     5   23950 no warnings;
  5         11  
  5         8535  
  9         17  
18 9         410 $metapkg = meta::get_package( $caller );
19             }
20 9         74 $metapkg->add_symbol( '%META', \my %META );
21              
22 9         16 my $INDEX = 1;
23              
24             $metapkg->add_named_sub('ro', sub {
25 7     7   431942 my @ro;
        7      
        107      
26 7         17 for (@_) {
27 9 100       22 if (ref $_) {
28 5         11 $_->{is} = 'ro';
29 5         10 push @ro, $_;
30             } else {
31 4         35 $META{attribute}{$_} = {
32             is => 'ro',
33             index => $INDEX++,
34             name => $_
35             };
36 4         18 push @ro, $META{attribute}{$_};
37             }
38             }
39 7         33 return @ro;
40 9         159 })->set_prototype('@');
41              
42             $metapkg->add_named_sub('req', sub {
43 3     3   6 my @req;
        3      
        106      
44 3         7 for (@_) {
45 5 100       10 if (ref $_) {
46 2         6 $_->{required} = 1;
47 2         4 push @req, $_;
48             } else {
49 3         17 $META{attribute}{$_} = {
50             required => 1,
51             index => $INDEX++,
52             name => $_
53             };
54 3         43 push @req, $META{attribute}{$_};
55             }
56             }
57 3         12 return @req;
58 9         128 })->set_prototype('@');
59              
60             $metapkg->add_named_sub('default', sub {
61 10     13   18 my $default = shift;
        10      
        122      
62 10         16 my @def;
63 10         19 for (@_) {
64 12 100       25 if (ref $_) {
65 4         13 $_->{default} = $default;
66 4         12 push @def, $_;
67             } else {
68 8         51 $META{attribute}{$_} = {
69             default => $default,
70             index => $INDEX++,
71             name => $_
72             };
73 8         17 push @def, $META{attribute}{$_};
74             }
75             }
76 10         66 return @def;
77 9         72 })->set_prototype('&@');
78              
79             $metapkg->add_named_sub('trigger', sub {
80 2     12   454559 my $trigger = shift;
        2      
        122      
81 2         6 my @tri;
82 2         9 for (@_) {
83 2 50       28 if (ref $_) {
84 0         0 $_->{trigger} = $trigger;
85 0         0 push @tri, $_;
86             } else {
87 2         24 $META{attribute}{$_} = {
88             trigger => $trigger,
89             index => $INDEX++,
90             name => $_
91             };
92 2         11 push @tri, $META{attribute}{$_};
93             }
94             }
95 2         16 return @tri;
96 9         126 })->set_prototype('&@');
97              
98             $metapkg->add_named_sub('before', sub {
99 2     4   5 my $function = shift;
        2      
100 2         3 my @before;
101 2         5 for (@_) {
102 2 100       6 if (ref $_) {
103 1         2 push @{ $_->{before} }, $function;
  1         3  
104 1         3 push @before, $_;
105             } else {
106 1   0     6 my $item = $META{attribute}{$_} || $META{function}{$_} || { name => $_, before => [] };
107 1         2 push @{$item->{before}}, $function;
  1         4  
108 1         3 push @before, $item;
109             }
110             }
111 2         5 return @before;
112 9         125 })->set_prototype('&@');
113              
114             $metapkg->add_named_sub('around', sub {
115 2     4   5 my $function = shift;
        2      
116 2         4 my @around;
117 2         5 for (@_) {
118 2 50       5 if (ref $_) {
119 0         0 push @{ $_->{around} }, $function;
  0         0  
120 0         0 push @around, $_;
121             } else {
122 2   50     13 my $item = $META{attribute}{$_} || $META{function}{$_} || { name => $_, around => [] };
123 2         5 push @{$item->{around}}, $function;
  2         5  
124 2         4 push @around, $item;
125             }
126             }
127 2         6 return @around;
128 9         73 })->set_prototype('&@');
129              
130             $metapkg->add_named_sub('after', sub {
131 2     4   5 my $function = shift;
        2      
132 2         4 my @after;
133 2         4 for (@_) {
134 2 100       5 if (ref $_) {
135 1         3 push @{ $_->{after} }, $function;
  1         4  
136 1         3 push @after, $_;
137             } else {
138 1   0     5 my $item = $META{attribute}{$_} || $META{function}{$_} || { name => $_, after => [] };
139 1         2 push @{$item->{after}}, $function;
  1         3  
140 1         3 push @after, $item;
141             }
142             }
143 2         6 return @after;
144 9         71 })->set_prototype('&@');
145              
146              
147             my $make_attribute = sub {
148 18     20   105 my $type = shift;
149 18         32 for (@_) {
150 22 100       50 if (ref $_) {
151 17         36 $_->{type} = $type;
152 17         48 $metapkg->add_named_sub($_->{name}, attribute($_));
153             } else {
154 5         29 $META{attribute}{$_} = { name => $_, type => $type, index => $INDEX++ };
155 5         16 $metapkg->add_named_sub($_, attribute($META{attribute}{$_}));
156             }
157             }
158 9         34 };
159              
160             $metapkg->add_named_sub('any', sub {
161 12     12   58 $make_attribute->(Any, @_);
        12      
162 9         70 })->set_prototype('@');
163              
164             $metapkg->add_named_sub('bool', sub {
165 1     13   5 $make_attribute->(Bool, @_);
        1      
166 9         82 })->set_prototype('@');
167              
168             $metapkg->add_named_sub('str', sub {
169 1     2   5 $make_attribute->(Str, @_);
        1      
170 9         63 })->set_prototype('@');
171              
172             $metapkg->add_named_sub('num', sub {
173 1     2   6 $make_attribute->(Num, @_);
        1      
174 9         51 })->set_prototype('@');
175              
176             $metapkg->add_named_sub('array', sub {
177 1     2   5 $make_attribute->(ArrayRef, @_);
        1      
178 9         64 })->set_prototype('@');
179              
180             $metapkg->add_named_sub('hash', sub {
181 1     2   13 $make_attribute->(HashRef, @_);
        1      
182 9         67 })->set_prototype('@');
183              
184             $metapkg->add_named_sub('obj', sub {
185 1     2   10 $make_attribute->(Object, @_);
        1      
186 9         62 })->set_prototype('@');
187              
188             $metapkg->add_named_sub('function', sub {
189 2     3   15 my $function = shift;
        2      
190 2         5 for (@_) {
191 2 50       8 if (ref $_) {
192 0         0 $_->{function} = $function;
193 0         0 $metapkg->add_named_sub($_->{name}, function($_));
194             } else {
195 2         41 $META{function}{$_} = { name => $_, function => $function };
196 2         29 $metapkg->add_named_sub($_, function($META{function}{$_}));
197             }
198             }
199 9         89 })->set_prototype('&@');
200              
201              
202             $metapkg->add_named_sub('extends', sub {
203 4     6   14 for (@_) {
        4      
204 4         9 eval { use_module($_); };
  4         34  
205 4         1720 my $extend;
206             {
207 5     5   58 no warnings;
  5         11  
  5         5980  
  4         11  
208 4         214 $extend = meta::get_package( $_ );
209             }
210 4         62 my %local = $extend->get_symbol('%META')->value;
211 4         32 my $isa = '@' . $caller . '::ISA';
212 4         494 eval "push $isa, '$_'";
213 4         34 for (sort { $local{attribute}{$a}{index} <=> $local{attribute}{$b}{index} } keys %{$local{attribute}}) {
  34         78  
  4         34  
214 18 50       64 if (!$META{attribute}{$_}) {
215 18         28 $META{attribute}{$_} = {%{$local{attribute}{$_}}};
  18         94  
216 18         51 $META{attribute}{$_}{index} = $INDEX++;
217             }
218             }
219 4         13 for ( keys %{ $local{function} } ) {
  4         47  
220 1 50       5 if (!$META{function}{$_}) {
221 1         2 $META{function}{$_} = {%{$local{function}{$_}}};
  1         15  
222             }
223             }
224             }
225 9         90 })->set_prototype('@');
226              
227             $metapkg->add_named_sub('new', sub {
228 10     14   911 my $self = bless {}, shift;
        10      
229 10 50       58 my %params = scalar @_ == 1 ? %{$_[0]} : @_;
  0         0  
230 10         18 my @sorted_keys = sort { $META{attribute}{$a}{index} <=> $META{attribute}{$b}{index} } keys %{$META{attribute}};
  114         295  
  10         66  
231 10         34 $self->$_($META{attribute}{$_}{default}->($self)) for grep { $META{attribute}{$_}{default} } @sorted_keys;
  59         163  
232 10         50 $self->$_($params{$_}) for keys %params;
233 10         36 $self->$_ for grep { $META{attribute}{$_}{required} } @sorted_keys;
  59         141  
234 8         54 return $self;
235 9         10515 });
236             }
237              
238             sub attribute {
239 22     32 0 44 my ($attr) = @_;
240             return sub {
241 128     128   2312 $attr = scoped($_[0], 'attribute', $attr);
242 128 100       1359 if (scalar @_ > 1) {
243 64 100       149 before($attr, @_) if ($attr->{before});
244 64 100 66     266 if ( $attr->{is} && $attr->{is} eq 'ro' && scalar caller() ne 'Quaint' ) {
      100        
245 2         20 die "attribute $attr->{name} is readonly";
246             }
247 62 100       153 my $val = $attr->{trigger} ? $attr->{trigger}->($_[0], $_[1]) : $_[1];
248 62 100       155 ($val) = around($attr, $_[0], $val) if ($attr->{around});
249 62 50       461 $_[0]->{$attr->{name}} = $attr->{type} ? $attr->{type}->($val) : $val;
250 62 100       1778 after($attr, $_[0], $_[0]->{$attr->{name}}) if ($attr->{after});
251             }
252 126 100 100     406 if ($attr->{required} && ! defined $_[0]->{$attr->{name}}) {
253 2         37 die "attribute $attr->{name} is required";
254             }
255             return $_[0]->{$attr->{name}}
256 124         574 }
257 22         498 }
258              
259             sub function {
260 2     76 0 6 my ($attr) = @_;
261             return sub {
262 3     109   39 my ($self, @params) = @_;
263 3         13 $attr = scoped($self, 'function', $attr);
264 3 100       20 before($attr, $self, @params) if ($attr->{before});
265 3 100       16 @params = around($attr, $self, @params) if ($attr->{around});
266 3         30 @params = $attr->{function}->($self, @params);
267 3 100       22 after($attr, $self, @params) if ($attr->{after});
268 3 50       26 return wantarray ? @params : $params[0];
269 2         29 };
270             }
271              
272             sub scoped {
273 131     239 0 309 my ($self, $type, $attr) = @_;
274 131         180 my $extend;
275             {
276 5     5   43 no warnings;
  5         7  
  5         1709  
  131         195  
277 131         3055 $extend = meta::get_package( ref $self );
278             }
279 131         921 my %local = $extend->get_symbol('%META')->value;
280 131         703 return $local{$type}{$attr->{name}};
281             }
282              
283             sub before {
284 3     109 0 11 my ($attr, @params) = @_;
285 3         5 for (@{$attr->{before}}) {
  3         11  
286 3         12 $_->(@params);
287             }
288             }
289              
290             sub around {
291 3     77 0 10 my ($attr, $self, @val) = @_;
292 3         6 for (@{$attr->{around}}) {
  3         9  
293 3         10 @val = $_->($self, @val);
294             }
295 3         18 return @val;
296             }
297              
298             sub after {
299 3     131 0 8 my ($attr, @params) = @_;
300 3         6 for (@{$attr->{after}}) {
  3         7  
301 3         9 $_->(@params);
302             }
303             }
304              
305              
306             1;
307              
308             __END__