File Coverage

blib/lib/MooX/Purple.pm
Criterion Covered Total %
statement 344 545 63.1
branch 40 64 62.5
condition 27 60 45.0
subroutine 39 40 97.5
pod n/a
total 450 709 63.4


line stmt bran cond sub pod time code
1             package MooX::Purple;
2              
3 12     12   826799 use 5.006;
  12         155  
4 12     12   78 use strict;
  12         35  
  12         378  
5 12     12   75 use warnings;
  12         21  
  12         653  
6             our $VERSION = '0.16';
7 12     12   11144 use Keyword::Declare;
  12         1524000  
  12         117  
8             our ($PREFIX, %MACROS);
9              
10 0         0 sub import {
11 14     14   1265 my ($class, %args) = @_;
12 14 100       76 $PREFIX = $args{-prefix} unless $PREFIX;
  14         28  
13 12     12   450178 keytype GATTRS is m{
14             (?:
15             allow (?&PerlNWS)
16             (?:(?!qw)(?&PerlQualifiedIdentifier)|
17             (?&PerlList))
18             |
19             with (?&PerlNWS)
20             (?:(?!qw)(?&PerlPrefixUnaryOperator)*(?&PerlQualifiedIdentifier)|
21             (?&PerlList))
22             |
23             is (?&PerlNWS)
24             (?:(?!qw)(?&PerlPrefixUnaryOperator)*(?&PerlQualifiedIdentifier)|
25             (?&PerlList))
26             |
27             use (?&PerlNWS)
28             (?:(?&PerlQualifiedIdentifier)\s*(?&PerlList)|(?:(?!qw)(?&PerlQualifiedIdentifier)|
29             (?&PerlList)))
30             )?+
31 14         27 }xms;
32 12     12   440501 keytype SATTRS is m{
33             (?:
34             allow (?&PerlNWS)
35             (?:(?!qw)(?&PerlQualifiedIdentifier)|
36             (?&PerlList))
37             |
38             )?+
39 14         25 }xms;
40 12     12   430814 keytype PATTRS is m{
41             (?:
42             describe (?&PerlNWS)
43             (?:(?&PerlString))
44             )?+
45 14         31 }xms;
46 0 50 50 7   0 keyword role (QualIdent $class, GATTRS @roles, Block $block) {
  0         0  
  0         0  
  14         258  
  7         864820  
  7         20  
  7         23  
47 0         0 _handle_role($class, $block, @roles)
  14         82  
  7         24  
48 0         0 }
  7         164  
  14         369  
49 0 50 50 9   0 keyword role (PrefixUnaryOperator $pre, QualIdent $class, GATTRS @roles, Block $block) {
  0         0  
  0         0  
  14         164  
  9         1039021  
  9         26  
  9         31  
50 0         0 $class = $PREFIX . '::' . $class if $pre;
  0         0  
  0         0  
  7         278  
  7         16  
  14         62  
  9         27  
51 0         0 _handle_role($class, $block, @roles)
  0         0  
  7         21  
  9         248  
52 0   0     0 }
  7   100     140793  
  14         372  
53 0 50 50 12   0 keyword class (QualIdent $class, GATTRS @roles, Block $block) {
  0         0  
  0         0  
  0         0  
  0         0  
  9         362  
  9         29  
  14         143  
  12         2423497  
  12         48  
  12         46  
54 0         0 _handle_class($class, $block, @roles);
  0         0  
  9         34  
  14         63  
  12         44  
55 0         0 }
  0         0  
  9         190  
  12         333  
  14         328  
56 0 50 50 6   0 keyword class (PrefixUnaryOperator $pre, QualIdent $class, GATTRS @roles, Block $block) {
  0         0  
  0         0  
  14         154  
  6         925292  
  6         19  
  6         23  
57 0         0 $class = $PREFIX . '::' . $class if $pre;
  0         0  
  0         0  
  0         0  
  0         0  
  9         213  
  9         53  
  12         489  
  12         32  
  14         253  
  6         18  
58 0         0 _handle_class($class, $block, @roles);
  0         0  
  0         0  
  9         24  
  12         37  
  6         160  
59 0   0     0 }
  0   0     0  
  9   33     72  
  12   100     314664  
  14         326  
60 0 50 50 0   0 keyword class (PrefixUnaryOperator $pre, QualIdent $class, Block $block) {
  0         0  
  0         0  
  0         0  
  0         0  
  6         242  
  6         19  
  14         164  
  0         0  
  0         0  
  0         0  
61 0         0 $class = $PREFIX . '::' . $class if $pre;
  0         0  
  6         22  
  14         60  
  0         0  
62 0         0 _handle_class($class, $block);
  0         0  
  6         112  
  0         0  
63 14         315 }
64 0 50 50 2   0 keyword macro (Ident $macro, Block $block) {
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  6         146  
  6         17  
  0         0  
  0         0  
  14         134  
  2         306911  
  2         5  
  2         6  
65 0         0 return _handle_macro($macro, $block);
  0         0  
  0         0  
  6         16  
  0         0  
  14         59  
  2         4  
66 0   0     0 }
  0   100     0  
  0         0  
  6         173118  
  0         0  
  2         37  
  14         314  
67 0 50 50 6   0 keyword private (Ident $method, SATTRS @roles, Block $block) {
  0         0  
  0         0  
  14         143  
  6         462949  
  6         19  
  6         20  
68 0         0 return _handle_private($method, $block, @roles);
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  2         62  
  2         5  
  14         72  
  6         20  
69 0         0 }
  0         0  
  0         0  
  0         0  
  2         5  
  6         226  
  14         320  
70 0 50 50 29   0 keyword public (Ident $method, Block $block, PATTRS @pod) {
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  2         28  
  14         141  
  29         3166051  
  29         85  
  29         92  
71 0         0 return _handle_public($method, $block, @pod);
  0         0  
  0         0  
  0         0  
  12         281905  
  6         397  
  6         51  
  14         57  
  29         94  
72 0         0 }
  0         0  
  0         0  
  0         0  
  0         0  
  2         40  
  6         14  
  29         713  
  14         322  
73 0 0 0 1   0 keyword start (Ident $method, Block $block) {
  0 0 66     0  
  0 50 50     0  
  0         0  
  0         0  
  0         0  
  0         0  
  2         8  
  6         206834  
  14         131  
  1         75444  
  1         3  
  1         2  
74 0         0 return _handle_when('-', $method, $block);
  0         0  
  12         93  
  0         0  
  0         0  
  0         0  
  7         138866  
  0         0  
  14         93  
  29         1097  
  29         84  
  14         59  
  1         4  
75 12         102 }
  0         0  
  0         0  
  14         89  
  14         458  
  29         543  
  1         20  
  14         325  
76 0 50 50 12   0 keyword during (Ident $method, Block $block) {
  0     1   0  
  12         495980  
  0         0  
  0         0  
  0         0  
  0         0  
  7         20  
  7         25  
  14         473  
  29         585  
  14         134  
  1         76723  
  1         3  
  1         3  
77 0     12   0 return _handle_when('~', $method, $block);
  12         512363  
  0         0  
  0         0  
  0         0  
  7         446  
  1         29  
  1         4  
  14         56  
  1         3  
78 0         0 }
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  7         196  
  0         0  
  25         560876  
  29         635  
  29         80  
  1         4  
  1         23  
  14         348  
79 0 50 50 1   0 keyword trigger (Ident $method, Block $block) {
  0         0  
  0         0  
  0         0  
  0         0  
  29         66  
  1         15  
  14         131  
  1         75579  
  1         3  
  1         2  
80 0   0     0 return _handle_when('=', $method, $block);
  0   100     0  
  0         0  
  0         0  
  0         0  
  7         151  
  29         68790  
  1         37  
  1         3  
  14         55  
  1         5  
81 0         0 }
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  7         45  
  9         34  
  12         313659  
  12         210180  
  1         22  
  1         118  
  1         22  
  14         347  
82 12 50 50 1   103 keyword end (Ident $method, Block $block) {
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  14         134  
  1         4  
  1         20  
  14         128  
  1         76258  
  1         3  
  1         3  
83 0         0 return _handle_when('+', $method, $block);
  0         0  
  0         0  
  0         0  
  12         92  
  0         0  
  0         0  
  0         0  
  14         713  
  9         18  
  9         25  
  12         47  
  12         48  
  14         96  
  1         34  
  1         3  
  14         55  
  1         3  
84 12     12   490777 }
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  9         538  
  12         785  
  6         207952  
  14         455  
  1         25  
  1         3  
  1         22  
85 0     12   0 }
  0         0  
  0         0  
  12         506003  
  0         0  
  0         0  
  9         127  
  12         338  
  10         175240  
  1         6  
  1         15  
86 0         0  
  0         0  
  0         0  
  12         90  
  0         0  
  0         0  
  6         20  
  6         26  
  4         69602  
  14         79  
  1         41  
  1         2  
87 0         0 sub _handle_macro {
  0         0  
  0         0  
  0         0  
  0         0  
  9         156  
  12         293  
  6         34  
  14         517  
  1         22  
  1         4  
88 0 0   12   0 my ($macro, $block) = @_;
  0 50   2   0  
  0         0  
  0         0  
  12         508543  
  0         0  
  0         0  
  9         165  
  12         80  
  6         173761  
  6         282  
  1         6  
  1         15  
  2         6  
89 0         0 $block =~ s/^\n*\{\n*\s*|;\n*\t*\}\n*$//g;
  12         103  
  0         0  
  12         92  
  9         220  
  14         122  
  29         68713  
  14         75  
  2         10  
90 12         111 $MACROS{$macro} = $block;
  0         0  
  0         0  
  0         0  
  0         0  
  14         106  
  14         517  
  6         20  
  6         24  
  6         389  
  14         476  
  1         21  
  2         38  
91 12     12   505622 return '';
  0     12   0  
  0         0  
  0         0  
  12         504092  
  0         0  
  14         575  
  6         222  
  6         36  
  29         72  
  1         7  
  2         19  
92 12     12   516440 }
  0         0  
  12         96  
  0         0  
  12         115  
  6         208  
  14         86  
  29         195  
  14         74  
93 12         104  
  14         486  
  14         90  
  14         426  
94 0     12   0 sub _handle_public {
  12     12   512638  
  12         514212  
  6         173  
  14         490  
95 0 0   12   0 my ($method, $block) = @_;
  12 50   29   513967  
  6         79  
  29         104  
96 0         0 my $mac = join '|', keys %MACROS;
  6         148  
  29         169  
97 12         102 $block =~ s/&($mac)/$MACROS{$1}/g;
  14         161  
  29         395  
98 14         553 $block =~ s/(^{)|(}$)//g;
  29         307  
99 12     12   522693 return "sub $method {
  29         452  
100             my (\$self) = shift;
101             $block
102             }";
103             }
104              
105             sub _handle_private {
106 6     6   31 my ($method, $block, @roles) = @_;
107 6         36 my %attrs = _parse_role_attrs(@roles);
108 6 50       38 my $allowed = $attrs{allow} ? sprintf 'qw(%s)', join ' ', @{$attrs{allow}} : 'qw//';
  6         54  
109 6         33 my $mac = join '|', keys %MACROS;
110 6         119 $block =~ s/&($mac)/$MACROS{$1}/g;
111 6         57 $block =~ s/(^{)|(}$)//g;
112 6         110 return "sub $method {
113             my (\$self) = shift;
114             my \$caller = caller();
115             my \@allowed = $allowed;
116             unless (\$caller eq __PACKAGE__ || grep { \$_ eq \$caller } \@allowed) {
117             die \"cannot call private method $method from \$caller\";
118             }
119             $block
120             }";
121             }
122              
123             sub _handle_when {
124 4     4   18 my ($pre, $method, $block) = @_;
125 4         29 my %map = (
126             '-' => 'before',
127             '+' => 'after',
128             '~' => 'around',
129             '=' => 'around'
130             );
131 4         21 $block =~ s/(^{)|(}$)//g;
132 4 100       100 if ($pre eq '~') {
    100          
133 1         5 $block = "{
134             my (\$orig, \$self) = (shift, shift);
135             $block;
136             }";
137             } elsif ($pre eq '=') {
138 1         6 $block = "{
139             my (\$orig, \$self) = (shift, shift);
140             my \$out = \$self->\$orig(\@_);
141             $block;
142             }";
143             } else {
144 2         12 $block = "{
145             my (\$self) = (shift);
146             $block;
147             }"
148             }
149 4         16 return "$map{$pre} $method => sub $block;";
150             }
151              
152             sub _handle_class {
153 18     18   90 my ($class, $block, @roles) = @_;
154 18         110 my ($body, %attrs) = _set_class_role_attrs($block, _parse_role_attrs(@roles));
155 18         297 my $out = qq|{
156             package $class;
157             use Moo;
158             use MooX::LazierAttributes;
159             use MooX::ValidateSubs;
160             use Data::LnArray qw/arr/;
161             $attrs{is}
162             $attrs{with}
163             $attrs{use}
164             $body
165             1;
166             }|;
167 18         435 return $out;
168             }
169              
170             sub _handle_role {
171 16     16   73 my ($class, $block, @roles) = @_;
172 16         87 my ($body, %attrs) = _set_class_role_attrs($block, _parse_role_attrs(@roles));
173 16         297 return qq|{
174             package $class;
175             use Moo::Role;
176             use MooX::LazierAttributes;
177             use MooX::ValidateSubs;
178             use Data::LnArray qw/arr/;
179             $attrs{with}
180             $attrs{use}
181             $body
182             1;
183             }|;
184             }
185              
186             sub _parse_role_attrs {
187 40     40   144 my @roles = @_;
188 40         104 my %attrs;
189 40         102 my $i = 0;
190 40         163 for (@roles) {
191 59 100       2026606 if ($_ =~ m/\s*use\s*((?!qw)(?&PerlQualifiedIdentifier))\s*((?&PerlList)) $PPR::GRAMMAR/xms) {
192 2         35 $attrs{use}{sprintf "%s %s", $1, $2}++;
193 2         193 next;
194             }
195 57         7778 $_ =~ m/(with|allow|is|use)(.*)/i;
196 57         10551 my @list = eval($2); # || $2
197 57 100       487 push @list, $2 unless @list;
198 57         217 for (@list) {
199 73         549 $attrs{$1}{$_} = $i++;
200             }
201             }
202 40         150 for my $o (qw/with allow is use/) {
203 160 100       572 $attrs{$o} = [sort { $attrs{$o}{$a} <=> $attrs{$o}{$b} } keys %{$attrs{$o}}] if $attrs{$o};
  17         68  
  32         263  
204             }
205 40         361 return %attrs;
206             }
207              
208             =pod todo uncomment and remove above hack
209             sub _parse_role_attrs {
210             my @roles = @_;
211             my %attrs;
212             for (@roles) {
213             if ($_ =~ m/\s*use\s*((?!qw)(?&PerlQualifiedIdentifier))\s*((?&PerlList)) $PPR::GRAMMAR/xms) {
214             push @{$attrs{use}}, sprintf "%s %s", $1, $2;
215             next;
216             }
217             $_ =~ m/(with|allow|is|use)(.*)/i;
218             push @{$attrs{$1}}, eval($2) || $2;
219             }
220             return %attrs;
221             }
222             =cut
223              
224             sub _set_class_role_attrs {
225 34     34   176 my ($body, %attrs, %args) = @_;
226 34 100       163 if ($attrs{allow}) {
227 6         19 my $allow = join ' ', @{$attrs{allow}};
  6         33  
228 6         102 $body =~ s{private\s*(\p{XIDS}\p{XIDC}*)}{private $1 allow qw/$allow/}g;
229             }
230 34 100       272 $attrs{is} = $attrs{is} ? sprintf "extends qw/%s/;\n", join(' ', map { my $l = $_; $l =~ s/^\s*\+/$PREFIX\:\:/; $l; } @{$attrs{is}}) : '';
  7         20  
  7         57  
  7         68  
  7         34  
231 34         97 my $last;
232             $attrs{with} = $attrs{with}
233             ? sprintf "with qw/%s/;\n", join(' ', map {
234 20         43 my $l = $_;
235 20         69 $l =~ s/^\s*\+/$PREFIX\:\:/;
236 20 100       97 unless($l =~ s/^\s*\-/$last\:\:/) {
237 12         30 $last = $l;
238             }
239 20 100       86 if ($l =~ s/^\s*\~//) {
240 2 50       12 $last = $PREFIX ? ($PREFIX . '::' . $l) : $l;
241 2         5 $l = '';
242             }
243 20         119 $l;
244 34 100       170 } @{$attrs{with}})
  11         51  
245             : '';
246 34 100       165 $attrs{use} = $attrs{use} ? join('', map { sprintf("use %s;\n", $_) } @{$attrs{use}}) : '';
  4         19  
  2         7  
247 34         1118 $body =~ s/(^{)|(}$)//g;
248 34         504 return $body, %attrs;
249             }
250              
251             1;
252              
253             __END__
254              
255             =head1 NAME
256              
257             MooX::Purple - MooX::Purple
258              
259             =head1 VERSION
260              
261             Version 0.16
262              
263             =cut
264              
265             =head1 SYNOPSIS
266              
267             use MooX::Purple;
268              
269             role Before {
270             public seven { return '7' }
271             };
272              
273             role World allow Hello with Before {
274             private six { 'six' }
275             };
276              
277             class Hello with qw/World/ allow qw/main/ use Scalar::Util qw/reftype/ use qw/JSON/ {
278             use Types::Standard qw/Str HashRef ArrayRef Object/;
279              
280             attributes
281             one => [{ okay => 'one'}],
282             [qw/two three/] => [rw, Str, { default => 'the world is flat' }];
283              
284             validate_subs
285             four => {
286             params => {
287             message => [Str, sub {'four'}]
288             }
289             };
290              
291             public four { return $_[1]->{message} }
292             private five { return $_[0]->six }
293             public ten { reftype bless {}, 'Flat::World' }
294             public eleven { encode_json { flat => "world" } }
295             };
296              
297             class Night is qw/Hello/ {
298             public nine { return 'nine' }
299             };
300              
301             Night->new()->five();
302              
303             ...
304              
305             # ../Foo.pm
306             package Foo;
307             use MooX::Purple -prefix => 'Foo';
308             use Foo::Roles;
309             class +Class with qw/~Role -One -Two -Three -Four/ {
310             public print {
311             return $_[1];
312             }
313             }
314            
315             # ../Foo/Roles.pm
316             package Foo::Roles;
317             use MooX::Purple;
318             role +Role::One {
319             public one {
320             $_[0]->print(1);
321             }
322             }
323             role +Role::Two {
324             public two {
325             $_[0]->print(2);
326             }
327             }
328             role +Role::Three {
329             public three {
330             $_[0]->print(3);
331             }
332             }
333             role +Role::Four {
334             public four {
335             $_[0]->print(4);
336             }
337             }
338              
339             ...
340              
341             use MooX::Purple -prefix => 'Macro';
342              
343             class +Simple {
344             macro generic {
345             'crazy';
346             };
347             macro second {
348             my $x = 0;
349             $x++ for (0..100);
350             return $x;
351             };
352             public one { &generic; }
353            
354             start one {
355             print "before\n";
356             }
357            
358             during one {
359             print "around\n";
360             $self->$orig();
361             };
362              
363             trigger one {
364             print "trigger\n";
365             return 'insane';
366             }
367              
368             end one {
369             print "after\n";
370             }
371              
372             public two {
373             print &generic
374             &second;
375             } describe "Add Documentation for method 'two'"
376             };
377              
378             class +Inherit is +Simple {};
379              
380              
381              
382             =head1 AUTHOR
383              
384             lnation, C<< <thisusedtobeanemail at gmail.com> >>
385              
386             =head1 BUGS
387              
388             Please report any bugs or feature requests to C<bug-moox-purple at rt.cpan.org>, or through
389             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=MooX-Purple>. I will be notified, and then you'll
390             automatically be notified of progress on your bug as I make changes.
391              
392             =head1 SUPPORT
393              
394             You can find documentation for this module with the perldoc command.
395              
396             perldoc MooX::Purple
397              
398              
399             You can also look for information at:
400              
401             =over 4
402              
403             =item * RT: CPAN's request tracker (report bugs here)
404              
405             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=MooX-Purple>
406              
407             =item * AnnoCPAN: Annotated CPAN documentation
408              
409             L<http://annocpan.org/dist/MooX-Purple>
410              
411             =item * CPAN Ratings
412              
413             L<http://cpanratings.perl.org/d/MooX-Purple>
414              
415             =item * Search CPAN
416              
417             L<http://search.cpan.org/dist/MooX-Purple/>
418              
419             =back
420              
421              
422             =head1 ACKNOWLEDGEMENTS
423              
424              
425             =head1 LICENSE AND COPYRIGHT
426              
427             Copyright 2019 lnation.
428              
429             This program is free software; you can redistribute it and/or modify it
430             under the terms of the the Artistic License (2.0). You may obtain a
431             copy of the full license at:
432              
433             L<http://www.perlfoundation.org/artistic_license_2_0>
434              
435             Any use, modification, and distribution of the Standard or Modified
436             Versions is governed by this Artistic License. By using, modifying or
437             distributing the Package, you accept this license. Do not use, modify,
438             or distribute the Package, if you do not accept this license.
439              
440             If your Modified Version has been derived from a Modified Version made
441             by someone other than you, you are nevertheless required to ensure that
442             your Modified Version complies with the requirements of this license.
443              
444             This license does not grant you the right to use any trademark, service
445             mark, tradename, or logo of the Copyright Holder.
446              
447             This license includes the non-exclusive, worldwide, free-of-charge
448             patent license to make, have made, use, offer to sell, sell, import and
449             otherwise transfer the Package with respect to any patent claims
450             licensable by the Copyright Holder that are necessarily infringed by the
451             Package. If you institute patent litigation (including a cross-claim or
452             counterclaim) against any party alleging that the Package constitutes
453             direct or contributory patent infringement, then this Artistic License
454             to you shall terminate on the date that such litigation is filed.
455              
456             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
457             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
458             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
459             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
460             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
461             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
462             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
463             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
464              
465              
466             =cut
467              
468             1; # End of MooX::Purple