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-2019 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 = '0.6';
12 3     3   200239 use common::sense;
  3         36  
  3         24  
13              
14 3     3   1905 use Locale::TextDomain qw(File-Globstar);
  3         55415  
  3         21  
15 3     3   82772 use Scalar::Util 1.21 qw(reftype);
  3         69  
  3         152  
16 3     3   1705 use IO::Handle;
  3         17957  
  3         150  
17              
18 3     3   1389 use File::Globstar qw(translatestar pnmatchstar);
  3         8  
  3         211  
19              
20 3     3   24 use constant RE_NONE => File::Globstar::RE_NONE();
  3         4  
  3         167  
21 3     3   18 use constant RE_NEGATED => File::Globstar::RE_NEGATED();
  3         6  
  3         133  
22 3     3   16 use constant RE_FULL_MATCH => File::Globstar::RE_FULL_MATCH;
  3         3  
  3         152  
23 3     3   17 use constant RE_DIRECTORY => File::Globstar::RE_DIRECTORY;
  3         5  
  3         3997  
24              
25             sub new {
26 40     40 1 6892 my ($class, $input, %options) = @_;
27              
28 40         77 my $self = {};
29 40         80 bless $self, $class;
30 40         105 $self->{__ignore_case} = delete $options{ignoreCase};
31 40         79 $self->{__filename} = delete $options{filename};
32              
33 40 100       111 if (ref $input) {
    100          
34 38         111 my $type = reftype $input;
35 38 100       83 if ('SCALAR' eq $type) {
    100          
36 36         90 $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         5 $self->_readFileHandle(\$input, );
44             } else {
45 1         4 $self->_readFile($input);
46             }
47              
48 40         283 return $self;
49             }
50              
51             sub __match {
52 89     89   187 my ($self, $imode, $path, $is_directory) = @_;
53              
54 89         126 my $match;
55 89         170 foreach my $pattern ($self->patterns) {
56 136         239 my $type = ref $pattern;
57 136         219 my $negated;
58 136 100       297 if ($type & RE_NEGATED) {
59 47 100       94 next if !$match;
60 44         55 $negated = 1;
61             } else {
62 89 50       201 next if $match;
63             }
64              
65 133         302 $match = pnmatchstar $pattern, $path, isDirectory => $is_directory;
66             }
67              
68 89 100       300 return 1 if $match;
69              
70             # Check that none of its parent directories has been ignored.
71 53 100       96 if (!$imode) {
72 35         54 $path =~ s{/$}{};
73              
74 35   100     127 while ($path =~ s{/[^/]*$}{} && length $path) {
75 15 100       43 return 1 if $self->__match(undef, $path, 1);
76             }
77             }
78              
79 50         215 return;
80             }
81              
82             sub match {
83 43     43 1 160 my ($self) = shift @_;
84              
85 43         106 return $self->__match(undef, @_);
86             }
87              
88             sub matchExclude {
89 2     2 1 6 &match;
90             }
91              
92             sub matchInclude {
93 31     31 1 112 my ($self) = shift @_;
94              
95 31         71 return $self->__match(1, @_);
96             }
97              
98             sub patterns {
99 100     100 1 841 return @{shift->{__patterns}};
  100         264  
100             }
101              
102             sub _readArray {
103 40     40   76 my ($self, $lines) = @_;
104              
105 40         58 my @patterns;
106 40         73 $self->{__patterns} = \@patterns;
107              
108 40         67 my $ignore_case = $self->{__ignore_case};
109 40         75 foreach my $line (@$lines) {
110 78         117 my $transpiled = eval { translatestar $line,
  78         203  
111             ignoreCase => $ignore_case,
112             pathMode => 1 };
113              
114             # Why a slash? When matching, we discard a trailing slash from the
115             # string to match. The regex '/$' can therefore never match. And the
116             # leading caret is there in order to save Perl at least reading the
117             # string to the end.
118 78 100       287 $transpiled = qr{^/$} if $@;
119 78         200 push @patterns, $transpiled;
120             }
121              
122 40         109 return $self;
123             }
124              
125             sub _readString {
126 39     39   79 my ($self, $string) = @_;
127              
128 39         59 my @lines;
129 39         134 foreach my $line (split /\n/, $string) {
130 92 100       226 next if $line =~ /^#/;
131              
132             # If the string contains trailing whitespace we have to count the
133             # number of backslashes in front of the first whitespace character.
134 86 100       230 if ($line =~ s/(\\*)([\x{9}-\x{13} ])[\x{9}-\x{13} ]*$//) {
135 12         29 my ($bs, $first) = ($1, $2);
136 12 100       24 if ($bs) {
137 4         6 $line .= $bs;
138              
139 4         9 my $num_bs = $bs =~ y/\\/\\/;
140              
141             # If the number of backslashes is odd, the last space was
142             # escaped.
143 4 100       9 $line .= $first if $num_bs & 1;
144             }
145             }
146 86 100       157 next if '' eq $line;
147              
148 75         166 push @lines, $line;
149             }
150              
151 39         106 return $self->_readArray(\@lines);
152             }
153              
154             sub _readFileHandle {
155 3     3   8 my ($self, $fh) = @_;
156              
157 3         7 my $filename = $self->{__filename};
158 3 50       11 $filename = __["in memory string"] if File::Globstar::empty($filename);
159              
160 3         17 $fh->clearerr;
161 3         94 my @lines = $fh->getlines;
162              
163 3 50       221 die __x("Error reading '{filename}': {error}!\n",
164             filename => $filename, error => $!) if $fh->error;
165              
166 3         15 return $self->_readString(join '', @lines);
167             }
168              
169             sub _readFile {
170 1     1   3 my ($self, $filename) = @_;
171              
172             $self->{__filename} = $filename
173 1 50       4 if File::Globstar::empty($self->{__filename});
174              
175 1 50       39 open my $fh, '<', $filename
176             or die __x("Error reading '{filename}': {error}!\n",
177             filename => $filename, error => $!);
178              
179 1         5 return $self->_readFileHandle($fh);
180             }
181              
182             1;