File Coverage

blib/lib/App/Greple/Pattern.pm
Criterion Covered Total %
statement 71 88 80.6
branch 14 26 53.8
condition 1 2 50.0
subroutine 26 28 92.8
pod 0 21 0.0
total 112 165 67.8


line stmt bran cond sub pod time code
1             package App::Greple::Pattern;
2              
3 134     134   1995 use v5.24;
  134         590  
4 134     134   954 use warnings;
  134         234  
  134         12726  
5             BEGIN {
6 134     134   450 eval { warnings->unimport('experimental::regex_sets') };
  134         2856  
7 134         472 eval { warnings->unimport('experimental::vlb') };
  134         5253  
8             }
9 134     134   1010 use Data::Dumper;
  134         302  
  134         9042  
10              
11 134     134   798 use Exporter 'import';
  134         256  
  134         12134  
12             our @EXPORT = ();
13             our %EXPORT_TAGS = ();
14             our @EXPORT_OK = qw();
15              
16 134     134   877 use Getopt::EX::Func qw(parse_func);
  134         236  
  134         12455  
17              
18             ##
19             ## Flags
20             ##
21             use constant {
22 134         128679 FLAG_NONE => 0,
23             FLAG_NEGATIVE => 1,
24             FLAG_REQUIRED => 2,
25             FLAG_OPTIONAL => 4,
26             FLAG_REGEX => 8,
27             FLAG_IGNORECASE => 16,
28             FLAG_COOK => 32,
29             FLAG_OR => 64,
30             FLAG_LEXICAL => 128,
31             FLAG_FUNCTION => 256,
32 134     134   969 };
  134         333  
33             push @EXPORT, qw(
34             FLAG_NONE
35             FLAG_NEGATIVE
36             FLAG_REQUIRED
37             FLAG_OPTIONAL
38             FLAG_REGEX
39             FLAG_IGNORECASE
40             FLAG_COOK
41             FLAG_OR
42             FLAG_LEXICAL
43             FLAG_FUNCTION
44             );
45              
46             sub new {
47 201     201 0 494 my $class = shift;
48 201         1627 my $obj = bless {
49             STRING => undef,
50             COOKED => undef,
51             FLAG => FLAG_NONE,
52             REGEX => undef,
53             CATEGORY => undef,
54             FUNCTION => undef,
55             }, $class;
56              
57 201 50       1279 $obj->setup(@_) if @_;
58              
59 199         922 $obj;
60             }
61              
62             sub setup {
63 201     201 0 706 my $obj = shift;
64 201         443 my $target = shift;
65 201         706 my %opt = @_;
66              
67 201   50     1150 $obj->flag($opt{flag} // FLAG_NONE);
68              
69 201 50       842 if ($obj->is_function) {
70 0 0       0 if ($target->can('call')) {
71 0         0 $obj->string('*FUNCTION');
72 0         0 $obj->cooked('*FUNCTION');
73 0         0 $obj->function($target);
74             } else {
75 0         0 $obj->string($target);
76 0         0 $obj->cooked($target);
77 0         0 $obj->function(parse_func({ PACKAGE => 'main' }, $target));
78             }
79             } else {
80 201         836 $obj->string($target);
81 201 100       703 $obj->cooked($obj->is_multiline
82             ? cook_pattern($target, flag => $obj->flag)
83             : $target);
84             $obj->regex(
85 201         425 do {
86 201 50       679 my $p = $obj->is_regex ? $obj->cooked : quotemeta($obj->cooked);
87 201 100       679 $obj->is_ignorecase ? qr/$p/mi : qr/$p/m;
88             } );
89             }
90              
91 199         647 $obj;
92             }
93              
94             sub field : lvalue {
95 3561     3561 0 4914 my $obj = shift;
96 3561         4989 my $name = shift;
97 3561 100       6106 if (@_) {
98 807         3313 $obj->{$name} = shift;
99 807         1506 $obj;
100             } else {
101 2754         18564 $obj->{$name};
102             }
103             }
104              
105 2564     2564 0 5310 sub flag : lvalue { shift->field ( FLAG => @_ ) }
106 201     201 0 604 sub string : lvalue { shift->field ( STRING => @_ ) }
107 442     442 0 1137 sub cooked : lvalue { shift->field ( COOKED => @_ ) }
108 353     353 0 1052 sub regex : lvalue { shift->field ( REGEX => @_ ) }
109 0     0 0 0 sub category : lvalue { shift->field ( CATEGORY => @_ ) }
110 0     0 0 0 sub function : lvalue { shift->field ( FUNCTION => @_ ) }
111 1     1 0 2 sub group_count : lvalue { shift->field ( GROUP_COUNT => @_ ) }
112              
113 465     465 0 1269 sub is_positive { !($_[0]->flag & FLAG_NEGATIVE) };
114 299     299 0 798 sub is_negative { $_[0]->flag & FLAG_NEGATIVE };
115 315     315 0 975 sub is_required { $_[0]->flag & FLAG_REQUIRED };
116 165     165 0 499 sub is_optional { $_[0]->flag & FLAG_OPTIONAL };
117 201     201 0 636 sub is_regex { $_[0]->flag & FLAG_REGEX };
118 201     201 0 563 sub is_ignorecase { $_[0]->flag & FLAG_IGNORECASE };
119 201     201 0 654 sub is_multiline { $_[0]->flag & FLAG_COOK };
120 355     355 0 1096 sub is_function { $_[0]->flag & FLAG_FUNCTION };
121              
122             sub IsWide {
123 134     134 0 123815 return <<'END';
124             +utf8::East_Asian_Width=Wide
125             +utf8::East_Asian_Width=FullWidth
126             END
127             }
128              
129             my $wclass_re = qr{ \[ \p{IsWide}+ (?: \- \p{IsWide}+ )* \] }x;
130             my $wstr_re = qr{ (?: \p{IsWide} | $wclass_re )+ }x;
131              
132             sub wstr {
133 34     34 0 203 local $_ = shift;
134 34 50       2528 my @wchars = m{ \G ( $wclass_re | \X ) }gx or die;
135 34         360 join '\\s*', @wchars;
136             }
137              
138             sub cook_pattern {
139 151     151 0 359 my $p = shift;
140 151         536 my %opt = @_;
141              
142 151 50       1010 if ($p =~ s/^\\Q//) {
143 0         0 return quotemeta($p);
144             }
145              
146             COOK:
147             {
148 151         338 $p =~ s{
  151         25282  
149             (?
150             (?
151             \[[^\]]*\] [\?\*]? # character-class
152             )
153             |
154             (?
155             \(\?[=!][^\)]*\) # look-ahead pattern
156             )
157             |
158             (?
159             \(\?\<[=!][^\)]*\) # look-behind pattern
160             )
161             |
162             (? $wstr_re)
163             |
164             (? [A-Z0-9_]+ | . )
165             )
166             }{
167 580 50       4834 if (defined $+{ahead}) {
    100          
168             $+{match}
169 0         0 =~ s{\A \( \? [=!] \K
170             ( (?: $wstr_re | \| )+ )
171             }{
172 0         0 join '|', (map { wstr($_) } split /\|/, $1);
  0         0  
173             }erx;
174             } elsif (defined $+{wstr}) {
175 34         201 wstr($+{match});
176             } else {
177 546         3444 $+{match};
178             }
179             }egx;
180              
181             # ( [
182 151         1505 $p =~ s/\p{IsWide} \K (?= [\(\[] )/\\s*+/gx;
183              
184             # ) ]
185 151         979 $p =~ s{
186             (# look-behind ending wchar
187             \(\?<[=!][^\)]*\p{IsWide}\) (?! [|] | $ )
188             )
189             |
190             (# skip look-ahead/behind
191             \(\?
192             )
193             |
194             (# whcar before ) or ]
195             \p{IsWide} [\)\]]+ [?]?+ (?! \\s\* | [|] | $ )
196             )
197             }{
198 0 0       0 if (defined $1) {
    0          
199 0         0 $1 . "\\s*+";
200             } elsif (defined $2) {
201 0         0 $2;
202             } else {
203 0         0 $3 . "\\s*+";
204             }
205             }egx;
206              
207             # convert space not preceded by \ to \s+,
208             # removing \s* and \s*+ arround it
209 151         761 $p =~ s{
210             (?: \Q\s*\E \+?+ )*
211             (?: (?
212             (?: \Q\s*\E \+?+ )*
213             }{\\s+}gx;
214             }
215              
216 151         971 $p;
217             }
218              
219             1;