File Coverage

blib/lib/Venus/Match.pm
Criterion Covered Total %
statement 107 113 94.6
branch 39 48 81.2
condition 9 15 60.0
subroutine 27 27 100.0
pod 11 12 91.6
total 193 215 89.7


line stmt bran cond sub pod time code
1             package Venus::Match;
2              
3 15     15   268 use 5.018;
  15         73  
4              
5 15     15   78 use strict;
  15         30  
  15         334  
6 15     15   81 use warnings;
  15         45  
  15         533  
7              
8 15     15   94 use Venus::Class 'attr', 'base', 'with';
  15         34  
  15         122  
9              
10             base 'Venus::Kind::Utility';
11              
12             with 'Venus::Role::Valuable';
13             with 'Venus::Role::Buildable';
14             with 'Venus::Role::Accessible';
15              
16 15     15   110 use Scalar::Util ();
  15         29  
  15         21336  
17              
18             # ATTRIBUTES
19              
20             attr 'on_none';
21             attr 'on_only';
22             attr 'on_then';
23             attr 'on_when';
24              
25             # BUILDERS
26              
27             sub build_self {
28 2443     2443 0 4092 my ($self, $data) = @_;
29              
30 2443 100   3   5393 $self->on_none(sub{}) if !$self->on_none;
31 2443 100   833   6147 $self->on_only(sub{1}) if !$self->on_only;
  833         1943  
32 2443 100       5557 $self->on_then([]) if !$self->on_then;
33 2443 100       5533 $self->on_when([]) if !$self->on_when;
34              
35 2443         4941 return $self;
36             }
37              
38             # METHODS
39              
40             sub clear {
41 187     187 1 342 my ($self) = @_;
42              
43 187     3   782 $self->on_none(sub{});
44 187     95   871 $self->on_only(sub{1});
  95         228  
45 187         621 $self->on_then([]);
46 187         643 $self->on_when([]);
47              
48 187         574 return $self;
49             }
50              
51             sub data {
52 4     4 1 16 my ($self, $data) = @_;
53              
54 4         21 while(my($key, $value) = each(%$data)) {
55 16         37 $self->just($key)->then($value);
56             }
57              
58 4         54 return $self;
59             }
60              
61             sub expr {
62 8     8 1 21 my ($self, $topic) = @_;
63              
64             $self->when(sub{
65 6     6   35 my $value = $self->value;
66              
67 6 50       23 if (!defined $value) {
68 0         0 return false;
69             }
70 6 50 33     23 if (Scalar::Util::blessed($value) && !overload::Overloaded($value)) {
71 0         0 return false;
72             }
73 6 50 33     30 if (!Scalar::Util::blessed($value) && ref($value)) {
74 0         0 return false;
75             }
76 6 100 100     78 if (ref($topic) eq 'Regexp' && "$value" =~ qr/$topic/) {
    100          
77 4         19 return true;
78             }
79             elsif ("$value" eq "$topic") {
80 1         4 return true;
81             }
82             else {
83 1         10 return false;
84             }
85 8         47 });
86              
87 8         32 return $self;
88             }
89              
90             sub just {
91 701     701 1 1180 my ($self, $topic) = @_;
92              
93             $self->when(sub{
94 615     615   1235 my $value = $self->value;
95              
96 615 50       1340 if (!defined $value) {
97 0         0 return false;
98             }
99 615 50 66     1624 if (Scalar::Util::blessed($value) && !overload::Overloaded($value)) {
100 0         0 return false;
101             }
102 615 50 66     2277 if (!Scalar::Util::blessed($value) && ref($value)) {
103 0         0 return false;
104             }
105 615 100       1836 if ("$value" eq "$topic") {
106 176         406 return true;
107             }
108             else {
109 439         939 return false;
110             }
111 701         3957 });
112              
113 701         1610 return $self;
114             }
115              
116             sub none {
117 314     314 1 639 my ($self, $code) = @_;
118              
119 314 100   3   1136 $self->on_none(UNIVERSAL::isa($code, 'CODE') ? $code : sub{$code});
  3         9  
120              
121 314         1324 return $self;
122             }
123              
124             sub only {
125 2     2 1 6 my ($self, $code) = @_;
126              
127 2         9 $self->on_only($code);
128              
129 2         38 return $self;
130             }
131              
132             sub result {
133 924     924 1 3238 my ($self, @args) = @_;
134              
135 924 100       2102 $self->value($args[0]) if @args;
136              
137 924         1471 my $result;
138 924         1354 my $matched = 0;
139 924         2067 my $value = $self->value;
140              
141 924         1687 local $_ = $value;
142 924 50       2042 return wantarray ? ($result, $matched) : $result if !$self->on_only->($value);
    100          
143              
144 923         2210 for (my $i = 0; $i < @{$self->on_when}; $i++) {
  1641         3431  
145 1220 100       2622 if ($self->on_when->[$i]->($value)) {
146 502         1397 $result = $self->on_then->[$i]->($value);
147 502         929 $matched++;
148 502         903 last;
149             }
150             }
151              
152 923 100       2196 if (!$matched) {
153 421         695 local $_ = $value;
154 421         1011 $result = $self->on_none->($value);
155             }
156              
157 923 100       9030 return wantarray ? ($result, $matched) : $result;
158             }
159              
160             sub test {
161 3     3 1 7 my ($self) = @_;
162              
163 3         3 my $matched = 0;
164              
165 3         7 my $value = $self->value;
166              
167 3         6 local $_ = $value;
168              
169 3 50       8 return $matched if !$self->on_only->($value);
170              
171 3         8 for (my $i = 0; $i < @{$self->on_when}; $i++) {
  7         14  
172 6 100       14 if ($self->on_when->[$i]->($value)) {
173 2         3 $matched++;
174 2         6 last;
175             }
176             }
177              
178 3         39 return $matched;
179             }
180              
181             sub then {
182 1473     1473 1 2517 my ($self, $code) = @_;
183              
184 1473         2214 my $next = $#{$self->on_when};
  1473         2758  
185              
186 1473 100   177   6596 $self->on_then->[$next] = UNIVERSAL::isa($code, 'CODE') ? $code : sub{$code};
  177         314  
187              
188 1473         5680 return $self;
189             }
190              
191             sub when {
192 1469     1469 1 3289 my ($self, $code, @args) = @_;
193              
194 1469 50       1972 my $next = (@{$self->on_when}-$#{$self->on_then}) > 1 ? -1 : @{$self->on_when};
  1469         2970  
  1469         2933  
  1469         2924  
195              
196             $self->on_when->[$next] = sub {
197 1226     1226   4045 (local $_ = $_[0])->$code(@args);
198 1469         6207 };
199              
200 1469         4522 return $self;
201             }
202              
203             sub where {
204 8     8 1 19 my ($self) = @_;
205              
206 8         26 my $where = $self->new;
207              
208 8     8   55 $self->then(sub{$where->result(@_)});
  8         34  
209              
210 8         186 return $where;
211             }
212              
213             1;
214              
215              
216              
217             =head1 NAME
218              
219             Venus::Match - Match Class
220              
221             =cut
222              
223             =head1 ABSTRACT
224              
225             Match Class for Perl 5
226              
227             =cut
228              
229             =head1 SYNOPSIS
230              
231             package main;
232              
233             use Venus::Match;
234              
235             my $match = Venus::Match->new(5);
236              
237             $match->when(sub{$_ < 5})->then(sub{"< 5"});
238             $match->when(sub{$_ > 5})->then(sub{"> 5"});
239              
240             $match->none(sub{"?"});
241              
242             my $result = $match->result;
243              
244             # "?"
245              
246             =cut
247              
248             =head1 DESCRIPTION
249              
250             This package provides an object-oriented interface for complex pattern matching
251             operations on scalar values. See L for operating on collections
252             of data, e.g. array references.
253              
254             =cut
255              
256             =head1 ATTRIBUTES
257              
258             This package has the following attributes:
259              
260             =cut
261              
262             =head2 on_none
263              
264             on_none(CodeRef)
265              
266             This attribute is read-write, accepts C<(CodeRef)> values, is optional, and defaults to C.
267              
268             =cut
269              
270             =head2 on_only
271              
272             on_only(CodeRef)
273              
274             This attribute is read-write, accepts C<(CodeRef)> values, is optional, and defaults to C.
275              
276             =cut
277              
278             =head2 on_then
279              
280             on_then(ArrayRef[CodeRef])
281              
282             This attribute is read-write, accepts C<(ArrayRef[CodeRef])> values, is optional, and defaults to C<[]>.
283              
284             =cut
285              
286             =head2 on_when
287              
288             on_when(ArrayRef[CodeRef])
289              
290             This attribute is read-write, accepts C<(ArrayRef[CodeRef])> values, is optional, and defaults to C<[]>.
291              
292             =cut
293              
294             =head1 INHERITS
295              
296             This package inherits behaviors from:
297              
298             L
299              
300             =cut
301              
302             =head1 INTEGRATES
303              
304             This package integrates behaviors from:
305              
306             L
307              
308             L
309              
310             L
311              
312             =cut
313              
314             =head1 METHODS
315              
316             This package provides the following methods:
317              
318             =cut
319              
320             =head2 clear
321              
322             clear() (Match)
323              
324             The clear method resets all match conditions and returns the invocant.
325              
326             I>
327              
328             =over 4
329              
330             =item clear example 1
331              
332             # given: synopsis
333              
334             package main;
335              
336             my $clear = $match->clear;
337              
338             # bless(..., "Venus::Match")
339              
340             =back
341              
342             =cut
343              
344             =head2 data
345              
346             data(HashRef $data) (Match)
347              
348             The data method takes a hashref (i.e. lookup table) and creates match
349             conditions and actions based on the keys and values found.
350              
351             I>
352              
353             =over 4
354              
355             =item data example 1
356              
357             package main;
358              
359             use Venus::Match;
360              
361             my $match = Venus::Match->new('a');
362              
363             $match->data({
364             'a' => 'b',
365             'c' => 'd',
366             'e' => 'f',
367             'g' => 'h',
368             });
369              
370             my $result = $match->none('z')->result;
371              
372             # "b"
373              
374             =back
375              
376             =over 4
377              
378             =item data example 2
379              
380             package main;
381              
382             use Venus::Match;
383              
384             my $match = Venus::Match->new('x');
385              
386             $match->data({
387             'a' => 'b',
388             'c' => 'd',
389             'e' => 'f',
390             'g' => 'h',
391             });
392              
393             my $result = $match->none('z')->result;
394              
395             # "z"
396              
397             =back
398              
399             =cut
400              
401             =head2 expr
402              
403             expr(Str | RegexpRef $expr) (Match)
404              
405             The expr method registers a L condition that check if the match value is
406             an exact string match of the C<$topic> if the topic is a string, or that it
407             matches against the topic if the topic is a regular expression.
408              
409             I>
410              
411             =over 4
412              
413             =item expr example 1
414              
415             package main;
416              
417             use Venus::Match;
418              
419             my $match = Venus::Match->new('1901-01-01');
420              
421             $match->expr('1901-01-01')->then(sub{[split /-/]});
422              
423             my $result = $match->result;
424              
425             # ["1901", "01", "01"]
426              
427             =back
428              
429             =over 4
430              
431             =item expr example 2
432              
433             package main;
434              
435             use Venus::Match;
436              
437             my $match = Venus::Match->new('1901-01-01');
438              
439             $match->expr(qr/^1901-/)->then(sub{[split /-/]});
440              
441             my $result = $match->result;
442              
443             # ["1901", "01", "01"]
444              
445             =back
446              
447             =cut
448              
449             =head2 just
450              
451             just(Str $topic) (Match)
452              
453             The just method registers a L condition that check if the match value is
454             an exact string match of the C<$topic> provided.
455              
456             I>
457              
458             =over 4
459              
460             =item just example 1
461              
462             package main;
463              
464             use Venus::Match;
465              
466             my $match = Venus::Match->new('a');
467              
468             $match->just('a')->then('a');
469             $match->just('b')->then('b');
470             $match->just('c')->then('c');
471              
472             my $result = $match->result;
473              
474             # "a"
475              
476             =back
477              
478             =over 4
479              
480             =item just example 2
481              
482             package main;
483              
484             use Venus::Match;
485             use Venus::String;
486              
487             my $match = Venus::Match->new(Venus::String->new('a'));
488              
489             $match->just('a')->then('a');
490             $match->just('b')->then('b');
491             $match->just('c')->then('c');
492              
493             my $result = $match->result;
494              
495             # "a"
496              
497             =back
498              
499             =over 4
500              
501             =item just example 3
502              
503             package main;
504              
505             use Venus::Match;
506             use Venus::String;
507              
508             my $match = Venus::Match->new(Venus::String->new('c'));
509              
510             $match->just('a')->then('a');
511             $match->just('b')->then('b');
512             $match->just('c')->then('c');
513              
514             my $result = $match->result;
515              
516             # "c"
517              
518             =back
519              
520             =over 4
521              
522             =item just example 4
523              
524             package main;
525              
526             use Venus::Match;
527              
528             my $match = Venus::Match->new(1.23);
529              
530             $match->just('1.230')->then('1.230');
531             $match->just(01.23)->then('123');
532             $match->just(1.230)->then(1.23);
533              
534             my $result = $match->result;
535              
536             # "1.23"
537              
538             =back
539              
540             =over 4
541              
542             =item just example 5
543              
544             package main;
545              
546             use Venus::Match;
547             use Venus::Number;
548              
549             my $match = Venus::Match->new(Venus::Number->new(1.23));
550              
551             $match->just('1.230')->then('1.230');
552             $match->just(01.23)->then('123');
553             $match->just(1.230)->then(1.23);
554              
555             my $result = $match->result;
556              
557             # "1.23"
558              
559             =back
560              
561             =over 4
562              
563             =item just example 6
564              
565             package main;
566              
567             use Venus::Match;
568             use Venus::Number;
569              
570             my $match = Venus::Match->new(1.23);
571              
572             $match->just(Venus::Number->new('1.230'))->then('1.230');
573             $match->just(Venus::Number->new(01.23))->then('123');
574             $match->just(Venus::Number->new(1.230))->then(1.23);
575              
576             my $result = $match->result;
577              
578             # "1.23"
579              
580             =back
581              
582             =cut
583              
584             =head2 none
585              
586             none(Any | CodeRef $code) (Match)
587              
588             The none method registers a special condition that returns a result only when
589             no other conditions have been matched.
590              
591             I>
592              
593             =over 4
594              
595             =item none example 1
596              
597             package main;
598              
599             use Venus::Match;
600              
601             my $match = Venus::Match->new('z');
602              
603             $match->just('a')->then('a');
604             $match->just('b')->then('b');
605             $match->just('c')->then('c');
606              
607             $match->none('z');
608              
609             my $result = $match->result;
610              
611             # "z"
612              
613             =back
614              
615             =over 4
616              
617             =item none example 2
618              
619             package main;
620              
621             use Venus::Match;
622              
623             my $match = Venus::Match->new('z');
624              
625             $match->just('a')->then('a');
626             $match->just('b')->then('b');
627             $match->just('c')->then('c');
628              
629             $match->none(sub{"($_) not found"});
630              
631             my $result = $match->result;
632              
633             # "(z) not found"
634              
635             =back
636              
637             =cut
638              
639             =head2 only
640              
641             only(CodeRef $code) (Match)
642              
643             The only method registers a special condition that only allows matching on the
644             match value only if the code provided returns truthy.
645              
646             I>
647              
648             =over 4
649              
650             =item only example 1
651              
652             package main;
653              
654             use Venus::Match;
655              
656             my $match = Venus::Match->new(5);
657              
658             $match->only(sub{$_ != 5});
659              
660             $match->just(5)->then(5);
661             $match->just(6)->then(6);
662              
663             my $result = $match->result;
664              
665             # undef
666              
667             =back
668              
669             =over 4
670              
671             =item only example 2
672              
673             package main;
674              
675             use Venus::Match;
676              
677             my $match = Venus::Match->new(6);
678              
679             $match->only(sub{$_ != 5});
680              
681             $match->just(5)->then(5);
682             $match->just(6)->then(6);
683              
684             my $result = $match->result;
685              
686             # 6
687              
688             =back
689              
690             =cut
691              
692             =head2 result
693              
694             result(Any $data) (Any)
695              
696             The result method evaluates the registered conditions and returns the result of
697             the action (i.e. the L code) or the special L condition if there
698             were no matches. In list context, this method returns both the result and
699             whether or not a condition matched. Optionally, when passed an argument this
700             method assign the argument as the value/topic and then perform the operation.
701              
702             I>
703              
704             =over 4
705              
706             =item result example 1
707              
708             package main;
709              
710             use Venus::Match;
711              
712             my $match = Venus::Match->new('a');
713              
714             $match->just('a')->then('a');
715             $match->just('b')->then('b');
716             $match->just('c')->then('c');
717              
718             my $result = $match->result;
719              
720             # "a"
721              
722             =back
723              
724             =over 4
725              
726             =item result example 2
727              
728             package main;
729              
730             use Venus::Match;
731              
732             my $match = Venus::Match->new('a');
733              
734             $match->just('a')->then('a');
735             $match->just('b')->then('b');
736             $match->just('c')->then('c');
737              
738             my ($result, $matched) = $match->result;
739              
740             # ("a", 1)
741              
742             =back
743              
744             =over 4
745              
746             =item result example 3
747              
748             package main;
749              
750             use Venus::Match;
751              
752             sub fibonacci {
753             my ($n) = @_;
754             my $match = Venus::Match->new($n)
755             ->just(1)->then(1)
756             ->just(2)->then(1)
757             ->none(sub{fibonacci($n - 1) + fibonacci($n - 2)})
758             ->result
759             }
760              
761             my $result = [fibonacci(4), fibonacci(6), fibonacci(12)]
762              
763             # [3, 8, 144]
764              
765             =back
766              
767             =over 4
768              
769             =item result example 4
770              
771             package main;
772              
773             use Venus::Match;
774              
775             my $match = Venus::Match->new('a');
776              
777             $match->just('a')->then('a');
778             $match->just('b')->then('b');
779             $match->just('c')->then('c');
780              
781             my $result = $match->result('b');
782              
783             # "b"
784              
785             =back
786              
787             =over 4
788              
789             =item result example 5
790              
791             package main;
792              
793             use Venus::Match;
794              
795             my $match = Venus::Match->new('a');
796              
797             $match->just('a')->then('a');
798             $match->just('b')->then('b');
799             $match->just('c')->then('c');
800              
801             my $result = $match->result('z');
802              
803             # undef
804              
805             =back
806              
807             =cut
808              
809             =head2 then
810              
811             then(Any | CodeRef $code) (Match)
812              
813             The then method registers an action to be executed if the corresponding match
814             condition returns truthy.
815              
816             I>
817              
818             =over 4
819              
820             =item then example 1
821              
822             package main;
823              
824             use Venus::Match;
825              
826             my $match = Venus::Match->new('b');
827              
828             $match->just('a');
829             $match->then('a');
830              
831             $match->just('b');
832             $match->then('b');
833              
834             my $result = $match->result;
835              
836             # "b"
837              
838             =back
839              
840             =over 4
841              
842             =item then example 2
843              
844             package main;
845              
846             use Venus::Match;
847              
848             my $match = Venus::Match->new('b');
849              
850             $match->just('a');
851             $match->then('a');
852              
853             $match->just('b');
854             $match->then('b');
855             $match->then('x');
856              
857             my $result = $match->result;
858              
859             # "x"
860              
861             =back
862              
863             =cut
864              
865             =head2 when
866              
867             when(Str | CodeRef $code, Any @args) (Match)
868              
869             The when method registers a match condition that will be passed the match value
870             during evaluation. If the match condition returns truthy the corresponding
871             action will be used to return a result. If the match value is an object, this
872             method can take a method name and arguments which will be used as a match
873             condition.
874              
875             I>
876              
877             =over 4
878              
879             =item when example 1
880              
881             package main;
882              
883             use Venus::Match;
884              
885             my $match = Venus::Match->new('a');
886              
887             $match->when(sub{$_ eq 'a'});
888             $match->then('a');
889              
890             $match->when(sub{$_ eq 'b'});
891             $match->then('b');
892              
893             $match->when(sub{$_ eq 'c'});
894             $match->then('c');
895              
896             my $result = $match->result;
897              
898             # "a"
899              
900             =back
901              
902             =over 4
903              
904             =item when example 2
905              
906             package main;
907              
908             use Venus::Match;
909             use Venus::Type;
910              
911             my $match = Venus::Match->new(Venus::Type->new(1)->deduce);
912              
913             $match->when('isa', 'Venus::Number');
914             $match->then('Venus::Number');
915              
916             $match->when('isa', 'Venus::String');
917             $match->then('Venus::String');
918              
919             my $result = $match->result;
920              
921             # "Venus::Number"
922              
923             =back
924              
925             =over 4
926              
927             =item when example 3
928              
929             package main;
930              
931             use Venus::Match;
932             use Venus::Type;
933              
934             my $match = Venus::Match->new(Venus::Type->new('1')->deduce);
935              
936             $match->when('isa', 'Venus::Number');
937             $match->then('Venus::Number');
938              
939             $match->when('isa', 'Venus::String');
940             $match->then('Venus::String');
941              
942             my $result = $match->result;
943              
944             # "Venus::String"
945              
946             =back
947              
948             =cut
949              
950             =head2 where
951              
952             where() (Match)
953              
954             The where method registers an action as a sub-match operation, to be executed
955             if the corresponding match condition returns truthy. This method returns the
956             sub-match object.
957              
958             I>
959              
960             =over 4
961              
962             =item where example 1
963              
964             package main;
965              
966             use Venus::Match;
967              
968             my $match = Venus::Match->new;
969              
970             my $submatch1 = $match->expr(qr/^p([a-z]+)ch/)->where;
971              
972             $submatch1->just('peach')->then('peach-123');
973             $submatch1->just('patch')->then('patch-456');
974             $submatch1->just('punch')->then('punch-789');
975              
976             my $submatch2 = $match->expr(qr/^m([a-z]+)ch/)->where;
977              
978             $submatch2->just('merch')->then('merch-123');
979             $submatch2->just('march')->then('march-456');
980             $submatch2->just('mouch')->then('mouch-789');
981              
982             my $result = $match->result('peach');
983              
984             # "peach-123"
985              
986             =back
987              
988             =over 4
989              
990             =item where example 2
991              
992             package main;
993              
994             use Venus::Match;
995              
996             my $match = Venus::Match->new;
997              
998             my $submatch1 = $match->expr(qr/^p([a-z]+)ch/)->where;
999              
1000             $submatch1->just('peach')->then('peach-123');
1001             $submatch1->just('patch')->then('patch-456');
1002             $submatch1->just('punch')->then('punch-789');
1003              
1004             my $submatch2 = $match->expr(qr/^m([a-z]+)ch/)->where;
1005              
1006             $submatch2->just('merch')->then('merch-123');
1007             $submatch2->just('march')->then('march-456');
1008             $submatch2->just('mouch')->then('mouch-789');
1009              
1010             my $result = $match->result('march');
1011              
1012             # "march-456"
1013              
1014             =back
1015              
1016             =over 4
1017              
1018             =item where example 3
1019              
1020             package main;
1021              
1022             use Venus::Match;
1023              
1024             my $match = Venus::Match->new;
1025              
1026             my $submatch1 = $match->expr(qr/^p([a-z]+)ch/)->where;
1027              
1028             $submatch1->just('peach')->then('peach-123');
1029             $submatch1->just('patch')->then('patch-456');
1030             $submatch1->just('punch')->then('punch-789');
1031              
1032             my $submatch2 = $match->expr(qr/^m([a-z]+)ch/)->where;
1033              
1034             $submatch2->just('merch')->then('merch-123');
1035             $submatch2->just('march')->then('march-456');
1036             $submatch2->just('mouch')->then('mouch-789');
1037              
1038             my $result = $match->result('pirch');
1039              
1040             # undef
1041              
1042             =back
1043              
1044             =cut
1045              
1046             =head1 AUTHORS
1047              
1048             Awncorp, C
1049              
1050             =cut
1051              
1052             =head1 LICENSE
1053              
1054             Copyright (C) 2000, Al Newkirk.
1055              
1056             This program is free software, you can redistribute it and/or modify it under
1057             the terms of the Apache license version 2.0.
1058              
1059             =cut