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.0.0';
12 3     3   208135 use strict;
  3         34  
  3         100  
13              
14 3     3   1754 use Locale::TextDomain qw(File-Globstar);
  3         57384  
  3         19  
15 3     3   91691 use Scalar::Util 1.21 qw(reftype);
  3         129  
  3         162  
16 3     3   1715 use IO::Handle;
  3         19077  
  3         150  
17              
18 3     3   1438 use File::Globstar qw(translatestar pnmatchstar);
  3         9  
  3         202  
19              
20 3     3   23 use constant RE_NONE => File::Globstar::RE_NONE();
  3         5  
  3         161  
21 3     3   18 use constant RE_NEGATED => File::Globstar::RE_NEGATED();
  3         20  
  3         161  
22 3     3   20 use constant RE_FULL_MATCH => File::Globstar::RE_FULL_MATCH;
  3         6  
  3         145  
23 3     3   20 use constant RE_DIRECTORY => File::Globstar::RE_DIRECTORY;
  3         9  
  3         3381  
24              
25             sub new {
26 40     40 1 6807 my ($class, $input, %options) = @_;
27              
28 40         73 my $self = {};
29 40         83 bless $self, $class;
30 40         109 $self->{__ignore_case} = delete $options{ignoreCase};
31 40         78 $self->{__filename} = delete $options{filename};
32              
33 40 100       153 if (ref $input) {
    100          
34 38         109 my $type = reftype $input;
35 38 100       97 if ('SCALAR' eq $type) {
    100          
36 36         91 $self->_readString($$input);
37             } elsif ('ARRAY' eq $type) {
38 1         5 $self->_readArray($input);
39             } else {
40 1         3 $self->_readFileHandle($input);
41             }
42             } elsif ("GLOB" eq reftype \$input) {
43 1         4 $self->_readFileHandle(\$input, );
44             } else {
45 1         3 $self->_readFile($input);
46             }
47              
48 40         323 return $self;
49             }
50              
51             sub __match {
52 89     89   214 my ($self, $imode, $path, $is_directory) = @_;
53              
54 89         113 my $match;
55 89         171 foreach my $pattern ($self->patterns) {
56 136         232 my $type = ref $pattern;
57 136         180 my $negated;
58 136 100       284 if ($type & RE_NEGATED) {
59 47 100       89 next if !$match;
60 44         74 $negated = 1;
61             } else {
62 89 50       187 next if $match;
63             }
64              
65 133         310 $match = pnmatchstar $pattern, $path, isDirectory => $is_directory;
66             }
67              
68 89 100       347 return 1 if $match;
69              
70             # Check that none of its parent directories has been ignored.
71 53 100       96 if (!$imode) {
72 35         56 $path =~ s{/$}{};
73              
74 35   100     125 while ($path =~ s{/[^/]*$}{} && length $path) {
75 15 100       57 return 1 if $self->__match(undef, $path, 1);
76             }
77             }
78              
79 50         277 return;
80             }
81              
82             sub match {
83 43     43 1 204 my ($self) = shift @_;
84              
85 43         95 return $self->__match(undef, @_);
86             }
87              
88             sub matchExclude {
89 2     2 1 6 &match;
90             }
91              
92             sub matchInclude {
93 31     31 1 123 my ($self) = shift @_;
94              
95 31         72 return $self->__match(1, @_);
96             }
97              
98             sub patterns {
99 100     100 1 829 return @{shift->{__patterns}};
  100         287  
100             }
101              
102             sub _readArray {
103 40     40   76 my ($self, $lines) = @_;
104              
105 40         59 my @patterns;
106 40         81 $self->{__patterns} = \@patterns;
107              
108 40         109 my $ignore_case = $self->{__ignore_case};
109 40         77 foreach my $line (@$lines) {
110 78         120 my $transpiled = eval {
111 78         206 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       286 $transpiled = qr{^/$} if $@;
121 78         204 push @patterns, $transpiled;
122             }
123              
124 40         120 return $self;
125             }
126              
127             sub _readString {
128 39     39   87 my ($self, $string) = @_;
129              
130 39         57 my @lines;
131 39         135 foreach my $line (split /\n/, $string) {
132 92 100       211 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       236 if ($line =~ s/(\\*)([\x{9}-\x{13} ])[\x{9}-\x{13} ]*$//) {
137 12         32 my ($bs, $first) = ($1, $2);
138 12 100       22 if ($bs) {
139 4         8 $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       12 $line .= $first if $num_bs & 1;
146             }
147             }
148 86 100       160 next if '' eq $line;
149              
150 75         163 push @lines, $line;
151             }
152              
153 39         101 return $self->_readArray(\@lines);
154             }
155              
156             sub _readFileHandle {
157 3     3   8 my ($self, $fh) = @_;
158              
159 3         7 my $filename = $self->{__filename};
160 3 50       10 $filename = __["in memory string"] if File::Globstar::empty($filename);
161              
162 3         17 $fh->clearerr;
163 3         83 my @lines = $fh->getlines;
164              
165 3 50       254 die __x("Error reading '{filename}': {error}!\n",
166             filename => $filename, error => $!) if $fh->error;
167              
168 3         16 return $self->_readString(join '', @lines);
169             }
170              
171             sub _readFile {
172 1     1   4 my ($self, $filename) = @_;
173              
174             $self->{__filename} = $filename
175 1 50       5 if File::Globstar::empty($self->{__filename});
176              
177 1 50       42 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;