File Coverage

blib/lib/File/Globstar/ListMatch.pm
Criterion Covered Total %
statement 98 98 100.0
branch 35 40 87.5
condition 3 3 100.0
subroutine 19 19 100.0
pod 5 5 100.0
total 160 165 96.9


line stmt bran cond sub pod time code
1             # Copyright (C) 2016-2023 Guido Flohr ,
2             # all rights reserved.
3              
4             # This file is distributed under the same terms and conditions as
5             # Perl itself.
6              
7             # This next lines is here to make Dist::Zilla happy.
8             # ABSTRACT: Perl Globstar (double asterisk globbing) and utils
9              
10             package File::Globstar::ListMatch;
11             $File::Globstar::ListMatch::VERSION = 'v1.1.0';
12 3     3   363977 use strict;
  3         7  
  3         142  
13              
14 3     3   1883 use Locale::TextDomain qw(File-Globstar);
  3         98137  
  3         26  
15 3     3   115378 use Scalar::Util 1.21 qw(reftype);
  3         59  
  3         182  
16 3     3   1959 use IO::Handle;
  3         24138  
  3         312  
17              
18 3     3   1882 use File::Globstar qw(translatestar pnmatchstar);
  3         11  
  3         350  
19              
20 3     3   25 use constant RE_NONE => File::Globstar::RE_NONE();
  3         6  
  3         202  
21 3     3   17 use constant RE_NEGATED => File::Globstar::RE_NEGATED();
  3         6  
  3         172  
22 3     3   19 use constant RE_FULL_MATCH => File::Globstar::RE_FULL_MATCH;
  3         6  
  3         199  
23 3     3   17 use constant RE_DIRECTORY => File::Globstar::RE_DIRECTORY;
  3         5  
  3         4233  
24              
25             sub new {
26 40     40 1 686226 my ($class, $input, %options) = @_;
27              
28 40         84 my $self = {};
29 40         126 bless $self, $class;
30 40         133 $self->{__ignore_case} = delete $options{ignoreCase};
31 40         100 $self->{__filename} = delete $options{filename};
32              
33 40 100       117 if (ref $input) {
    100          
34 38         83 my $type = reftype $input;
35 38 100       99 if ('SCALAR' eq $type) {
    100          
36 36         112 $self->_readString($$input);
37             } elsif ('ARRAY' eq $type) {
38 1         5 $self->_readArray($input);
39             } else {
40 1         4 $self->_readFileHandle($input);
41             }
42             } elsif ("GLOB" eq reftype \$input) {
43 1         8 $self->_readFileHandle(\$input, );
44             } else {
45 1         4 $self->_readFile($input);
46             }
47              
48 40         386 return $self;
49             }
50              
51             sub __match {
52 89     89   224 my ($self, $imode, $path, $is_directory) = @_;
53              
54 89         131 my $match;
55 89         169 foreach my $pattern ($self->patterns) {
56 136         225 my $type = ref $pattern;
57 136         197 my $negated;
58 136 100       301 if ($type & RE_NEGATED) {
59 47 100       96 next if !$match;
60 44         59 $negated = 1;
61             } else {
62 89 50       197 next if $match;
63             }
64              
65 133         336 $match = pnmatchstar $pattern, $path, isDirectory => $is_directory;
66             }
67              
68 89 100       325 return 1 if $match;
69              
70             # Check that none of its parent directories has been ignored.
71 53 100       128 if (!$imode) {
72 35         56 $path =~ s{/$}{};
73              
74 35   100     133 while ($path =~ s{/[^/]*$}{} && length $path) {
75 15 100       35 return 1 if $self->__match(undef, $path, 1);
76             }
77             }
78              
79 50         276 return;
80             }
81              
82             sub match {
83 43     43 1 144 my ($self) = shift @_;
84              
85 43         100 return $self->__match(undef, @_);
86             }
87              
88             sub matchExclude {
89 2     2 1 4 &match;
90             }
91              
92             sub matchInclude {
93 31     31 1 191 my ($self) = shift @_;
94              
95 31         86 return $self->__match(1, @_);
96             }
97              
98             sub patterns {
99 100     100 1 1207 return @{shift->{__patterns}};
  100         310  
100             }
101              
102             sub _readArray {
103 40     40   76 my ($self, $lines) = @_;
104              
105 40         73 my @patterns;
106 40         136 $self->{__patterns} = \@patterns;
107              
108 40         114 my $ignore_case = $self->{__ignore_case};
109 40         76 foreach my $line (@$lines) {
110 78         142 my $transpiled = eval {
111 78         313 translatestar $line,
112             ignoreCase => $ignore_case,
113             pathMode => 1
114             };
115              
116             # Why a slash? When matching, we discard a trailing slash from the
117             # string to match. The regex '/$' can therefore never match. And the
118             # leading caret is there in order to save Perl at least reading the
119             # string to the end.
120 78 100       324 $transpiled = qr{^/$} if $@;
121 78         248 push @patterns, $transpiled;
122             }
123              
124 40         171 return $self;
125             }
126              
127             sub _readString {
128 39     39   86 my ($self, $string) = @_;
129              
130 39         63 my @lines;
131 39         203 foreach my $line (split /\n/, $string) {
132 92 100       246 next if $line =~ /^#/;
133              
134             # If the string contains trailing whitespace we have to count the
135             # number of backslashes in front of the first whitespace character.
136 86 100       280 if ($line =~ s/(\\*)([\x{9}-\x{13} ])[\x{9}-\x{13} ]*$//) {
137 12         35 my ($bs, $first) = ($1, $2);
138 12 100       27 if ($bs) {
139 4         7 $line .= $bs;
140              
141 4         11 my $num_bs = $bs =~ y/\\/\\/;
142              
143             # If the number of backslashes is odd, the last space was
144             # escaped.
145 4 100       13 $line .= $first if $num_bs & 1;
146             }
147             }
148 86 100       190 next if '' eq $line;
149              
150 75         212 push @lines, $line;
151             }
152              
153 39         125 return $self->_readArray(\@lines);
154             }
155              
156             sub _readFileHandle {
157 3     3   9 my ($self, $fh) = @_;
158              
159 3         9 my $filename = $self->{__filename};
160 3 50       13 $filename = __["in memory string"] if File::Globstar::empty($filename);
161              
162 3         26 $fh->clearerr;
163 3         134 my @lines = $fh->getlines;
164              
165 3 50       23 die __x("Error reading '{filename}': {error}!\n",
166             filename => $filename, error => $!) if $fh->error;
167              
168 3         26 return $self->_readString(join '', @lines);
169             }
170              
171             sub _readFile {
172 1     1   3 my ($self, $filename) = @_;
173              
174             $self->{__filename} = $filename
175 1 50       6 if File::Globstar::empty($self->{__filename});
176              
177 1 50       55 open my $fh, '<', $filename
178             or die __x("Error reading '{filename}': {error}!\n",
179             filename => $filename, error => $!);
180              
181 1         5 return $self->_readFileHandle($fh);
182             }
183              
184             1;