File Coverage

blib/lib/App/Greple/Pattern/Holder.pm
Criterion Covered Total %
statement 97 117 82.9
branch 26 42 61.9
condition 8 15 53.3
subroutine 12 14 85.7
pod 0 6 0.0
total 143 194 73.7


line stmt bran cond sub pod time code
1             package App::Greple::Pattern::Holder;
2              
3 134     134   3790 use v5.24;
  134         496  
4 134     134   715 use warnings;
  134         255  
  134         6789  
5 134     134   782 use Data::Dumper;
  134         256  
  134         7159  
6 134     134   729 use Carp;
  134         263  
  134         11286  
7              
8 134     134   825 use Exporter 'import';
  134         275  
  134         11363  
9             our @EXPORT = ();
10             our %EXPORT_TAGS = ();
11             our @EXPORT_OK = qw();
12              
13 134     134   959 use App::Greple::Pattern;
  134         413  
  134         126753  
14              
15             sub new {
16 133     133 0 366 my $class = shift;
17 133         449 my $obj = bless [], $class;
18 133         534 $obj;
19             }
20              
21             sub append {
22 203     203 0 441 my $obj = shift;
23 203 50       847 my $arg = ref $_[0] eq 'HASH' ? shift : {};
24              
25 203 50       752 return $obj unless @_;
26              
27 203   100     1018 $arg->{type} //= 'pattern';
28              
29 203 100       798 if ($arg->{type} eq 'file') {
30 9         61 $obj->load_file($arg, @_);
31 9         44 return $obj;
32             }
33              
34 194 100       772 if ($arg->{flag} & FLAG_LEXICAL) {
35 33         99 for (@_) {
36 33         165 $obj->lexical_opt($arg, $_);
37             }
38 33         224 return $obj;
39             }
40              
41 161 100       666 if ($arg->{flag} & FLAG_OR) {
42 9         24 $arg->{flag} &= ~FLAG_OR;
43             my @p = map {
44 9         23 App::Greple::Pattern->new
45 40         211 ($_, flag => $arg->{flag} & ~FLAG_IGNORECASE)
46             ->cooked;
47             } @_;
48 9         522 my $p = "(?x)\n " . join("\n| ", map qr/$_/m, @p);
49 9         62 $arg->{flag} |= FLAG_REGEX;
50 9         50 $arg->{flag} &= ~FLAG_COOK;
51 9         81 push @$obj, App::Greple::Pattern->new($p, flag => $arg->{flag});
52 9         328 return $obj;
53             }
54              
55 152         417 for (@_) {
56 152         2204 push @$obj, App::Greple::Pattern->new($_, flag => $arg->{flag});
57             }
58              
59 150         794 $obj;
60             }
61              
62             sub optimize {
63 0     0 0 0 my $obj = shift;
64              
65             # collect required pattern at the top of list
66 0         0 @$obj = ( grep( { $_->is_required } @$obj ),
67 0         0 grep( { ! $_->is_required } @$obj ) );
  0         0  
68              
69 0         0 $obj;
70             }
71              
72             sub lexical_opt {
73 33     33 0 139 my($obj, $arg, $opt) = @_;
74              
75 33 50       140 unless ($arg->{flag} & FLAG_LEXICAL) {
76 0         0 die "Unexpected flag value ($arg->{flag})";
77             }
78 33         103 my $orig_flag = $arg->{flag} & ~FLAG_LEXICAL;
79              
80 33         113 my $or;
81             my @pattern;
82 33         292 for (split /(?
83              
84 44 50       166 next if $_ eq "";
85              
86 44         97 my $flag = $orig_flag;
87              
88 44 100       530 if (s/^\+//) { # +pattern
    100          
    100          
89 3         6 $flag |= FLAG_REQUIRED;
90             }
91             elsif (s/^-//) { # -pattern
92 2         5 $flag |= FLAG_NEGATIVE;
93             }
94             elsif (s/^\?//) { # ?pattern
95 2         3 $flag |= FLAG_OPTIONAL;
96             }
97              
98 44 50       201 if (s/^\&//) { # &func(...)
99 0         0 $flag |= FLAG_FUNCTION;
100             }
101             else {
102 44         117 $flag |= FLAG_REGEX;
103             }
104              
105 44 50       284 push @pattern, [ { flag => $flag }, $_ ] if $_ ne '';
106             }
107              
108 33         99 for (@pattern) {
109 44         248 $obj->append(@$_);
110             }
111             }
112              
113 134     134   75169 use Getopt::EX::Numbers;
  134         216154  
  134         130953  
114              
115             sub load_file {
116 9     9 0 19 my $obj = shift;
117 9 50       40 my $arg = ref $_[0] eq 'HASH' ? shift : {};
118              
119 9         26 $arg->{type} = 'pattern';
120 9   50     45 my $flag = ( $arg->{flag} // 0 ) | FLAG_REGEX | FLAG_OR;
121              
122 9         26 for my $file (@_) {
123 9         15 my $select;
124 9 100 66     327 if (!-f $file and $file =~ s/\@ (?[\d:,]+) $//x) {
125 1         12 $select = $+{n};
126             }
127 9 50       374 open my $fh, '<:encoding(utf8)', $file or die "$file: $!\n";
128 9         525 my @p = map s/\\(?=\R)//gr, split /(? };
  9         50  
  9         281  
129 9 100 100     532 if ($select //= $arg->{select}) {
130 5         60 my $numbers = Getopt::EX::Numbers->new(min => 1, max => int @p);
131 5         325 my @select = do {
132 25         73 map { $_ - 1 }
133 37         52 sort { $a <=> $b }
134 25         430 grep { $_ <= @p }
135 5         55 map { $numbers->parse($_)->sequence }
  7         239  
136             split /,/, $select;
137             };
138 5         63 @p = @p[@select];
139             }
140             ##
141             ## Collect DEFINE patterns
142             ##
143 9         18 my %DEFINE;
144 9         29 for (@p) {
145 46 50       118 if (/^\Q(?(DEFINE)(?<\E(?[^>]+)/) {
146 0         0 $DEFINE{$+{name}} = $_;
147             }
148             }
149 9         45 @p = do {
150 40         73 map { chomp; s{\s*//.*$}{}r }
  40         96  
151 46         210 grep { not m{^\s*(?:#|//|$)} }
152 9         26 grep { not m{^\Q(?(DEFINE)\E} }
  46         100  
153             @p;
154             };
155             ##
156             ## Append DEFINE to each pattern that references it
157             ##
158 9 50       52 if (%DEFINE) {
159 0         0 for my $p (@p) {
160 0         0 my %define;
161             (sub {
162 0     0   0 my($str, $seen) = @_;
163 0   0     0 $seen //= {};
164 0         0 while ($str =~ /\(\?&(?[^)]+)\)/g) {
165 0         0 my $name = $+{name};
166 0 0       0 next if $seen->{$name}++;
167 0 0       0 my $def = $DEFINE{$name} or next;
168 0   0     0 $define{$name} //= $def;
169 0         0 __SUB__->($def, $seen);
170             }
171 0         0 })->($p);
172 0 0       0 $p .= "(?x:\n" . join("\n", values %define) . ")" if %define;
173             }
174             }
175 9         75 $obj->append({ flag => $flag }, @p);
176             }
177             }
178              
179             sub patterns {
180 644     644 0 1166 my $obj = shift;
181 644         979 @{ $obj };
  644         1997  
182             }
183              
184             1;