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 171     171   3173 use v5.24;
  171         498  
4 171     171   790 use warnings;
  171         296  
  171         7764  
5 171     171   873 use Data::Dumper;
  171         423  
  171         7564  
6 171     171   755 use Carp;
  171         272  
  171         8238  
7              
8 171     171   734 use Exporter 'import';
  171         235  
  171         9781  
9             our @EXPORT = ();
10             our %EXPORT_TAGS = ();
11             our @EXPORT_OK = qw();
12              
13 171     171   653 use App::Greple::Pattern;
  171         292  
  171         111198  
14              
15             sub new {
16 170     170 0 364 my $class = shift;
17 170         425 my $obj = bless [], $class;
18 170         411 $obj;
19             }
20              
21             sub append {
22 274     274 0 469 my $obj = shift;
23 274 50       944 my $arg = ref $_[0] eq 'HASH' ? shift : {};
24              
25 274 50       748 return $obj unless @_;
26              
27 274   100     944 $arg->{type} //= 'pattern';
28              
29 274 100       768 if ($arg->{type} eq 'file') {
30 9         45 $obj->load_file($arg, @_);
31 9         55 return $obj;
32             }
33              
34 265 100       790 if ($arg->{flag} & FLAG_LEXICAL) {
35 33         73 for (@_) {
36 33         129 $obj->lexical_opt($arg, $_);
37             }
38 33         141 return $obj;
39             }
40              
41 232 100       615 if ($arg->{flag} & FLAG_OR) {
42 9         18 $arg->{flag} &= ~FLAG_OR;
43             my @p = map {
44 9         21 App::Greple::Pattern->new
45 40         146 ($_, flag => $arg->{flag} & ~FLAG_IGNORECASE)
46             ->cooked;
47             } @_;
48 9         411 my $p = "(?x)\n " . join("\n| ", map qr/$_/m, @p);
49 9         47 $arg->{flag} |= FLAG_REGEX;
50 9         19 $arg->{flag} &= ~FLAG_COOK;
51 9         57 push @$obj, App::Greple::Pattern->new($p, flag => $arg->{flag});
52 9         252 return $obj;
53             }
54              
55 223         479 for (@_) {
56 223         2141 push @$obj, App::Greple::Pattern->new($_, flag => $arg->{flag});
57             }
58              
59 221         899 $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 101 my($obj, $arg, $opt) = @_;
74              
75 33 50       99 unless ($arg->{flag} & FLAG_LEXICAL) {
76 0         0 die "Unexpected flag value ($arg->{flag})";
77             }
78 33         78 my $orig_flag = $arg->{flag} & ~FLAG_LEXICAL;
79              
80 33         62 my $or;
81             my @pattern;
82 33         230 for (split /(?
83              
84 44 50       116 next if $_ eq "";
85              
86 44         87 my $flag = $orig_flag;
87              
88 44 100       330 if (s/^\+//) { # +pattern
    100          
    100          
89 3         6 $flag |= FLAG_REQUIRED;
90             }
91             elsif (s/^-//) { # -pattern
92 2         4 $flag |= FLAG_NEGATIVE;
93             }
94             elsif (s/^\?//) { # ?pattern
95 2         3 $flag |= FLAG_OPTIONAL;
96             }
97              
98 44 50       130 if (s/^\&//) { # &func(...)
99 0         0 $flag |= FLAG_FUNCTION;
100             }
101             else {
102 44         65 $flag |= FLAG_REGEX;
103             }
104              
105 44 50       209 push @pattern, [ { flag => $flag }, $_ ] if $_ ne '';
106             }
107              
108 33         80 for (@pattern) {
109 44         174 $obj->append(@$_);
110             }
111             }
112              
113 171     171   73426 use Getopt::EX::Numbers;
  171         188037  
  171         123422  
114              
115             sub load_file {
116 9     9 0 14 my $obj = shift;
117 9 50       28 my $arg = ref $_[0] eq 'HASH' ? shift : {};
118              
119 9         57 $arg->{type} = 'pattern';
120 9   50     37 my $flag = ( $arg->{flag} // 0 ) | FLAG_REGEX | FLAG_OR;
121              
122 9         16 for my $file (@_) {
123 9         14 my $select;
124 9 100 66     269 if (!-f $file and $file =~ s/\@ (?[\d:,]+) $//x) {
125 1         9 $select = $+{n};
126             }
127 9 50       387 open my $fh, '<:encoding(utf8)', $file or die "$file: $!\n";
128 9         372 my @p = map s/\\(?=\R)//gr, split /(? };
  9         38  
  9         247  
129 9 100 100     443 if ($select //= $arg->{select}) {
130 5         46 my $numbers = Getopt::EX::Numbers->new(min => 1, max => int @p);
131 5         232 my @select = do {
132 25         35 map { $_ - 1 }
133 37         43 sort { $a <=> $b }
134 25         331 grep { $_ <= @p }
135 5         21 map { $numbers->parse($_)->sequence }
  7         143  
136             split /,/, $select;
137             };
138 5         43 @p = @p[@select];
139             }
140             ##
141             ## Collect DEFINE patterns
142             ##
143 9         17 my %DEFINE;
144 9         21 for (@p) {
145 46 50       93 if (/^\Q(?(DEFINE)(?<\E(?[^>]+)/) {
146 0         0 $DEFINE{$+{name}} = $_;
147             }
148             }
149 9         15 @p = do {
150 40         52 map { chomp; s{\s*//.*$}{}r }
  40         71  
151 46         90 grep { not m{^\s*(?:#|//|$)} }
152 9         18 grep { not m{^\Q(?(DEFINE)\E} }
  46         71  
153             @p;
154             };
155             ##
156             ## Append DEFINE to each pattern that references it
157             ##
158 9 50       29 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         57 $obj->append({ flag => $flag }, @p);
176             }
177             }
178              
179             sub patterns {
180 829     829 0 1090 my $obj = shift;
181 829         936 @{ $obj };
  829         1834  
182             }
183              
184             1;