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-2017 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.5';
12 3     3   182904 use strict;
  3         24  
  3         79  
13              
14 3     3   1337 use Locale::TextDomain qw(File-Globstar);
  3         45261  
  3         16  
15 3     3   18500 use Scalar::Util 1.21 qw(reftype);
  3         62  
  3         120  
16 3     3   1366 use IO::Handle;
  3         14607  
  3         117  
17              
18 3     3   1075 use File::Globstar qw(translatestar pnmatchstar);
  3         6  
  3         160  
19              
20 3     3   19 use constant RE_NONE => File::Globstar::RE_NONE();
  3         3  
  3         128  
21 3     3   14 use constant RE_NEGATED => File::Globstar::RE_NEGATED();
  3         4  
  3         154  
22 3     3   16 use constant RE_FULL_MATCH => File::Globstar::RE_FULL_MATCH;
  3         5  
  3         105  
23 3     3   14 use constant RE_DIRECTORY => File::Globstar::RE_DIRECTORY;
  3         4  
  3         2571  
24              
25             sub new {
26 40     40 1 5463 my ($class, $input, %options) = @_;
27              
28 40         64 my $self = {};
29 40         65 bless $self, $class;
30 40         84 $self->{__ignore_case} = delete $options{ignoreCase};
31 40         58 $self->{__filename} = delete $options{filename};
32              
33 40 100       88 if (ref $input) {
    100          
34 38         87 my $type = reftype $input;
35 38 100       70 if ('SCALAR' eq $type) {
    100          
36 36         70 $self->_readString($$input);
37             } elsif ('ARRAY' eq $type) {
38 1         3 $self->_readArray($input);
39             } else {
40 1         3 $self->_readFileHandle($input);
41             }
42             } elsif ("GLOB" eq reftype \$input) {
43 1         3 $self->_readFileHandle(\$input, );
44             } else {
45 1         3 $self->_readFile($input);
46             }
47              
48 40         224 return $self;
49             }
50              
51             sub __match {
52 89     89   156 my ($self, $imode, $path, $is_directory) = @_;
53              
54 89         100 my $match;
55 89         121 foreach my $pattern ($self->patterns) {
56 136         179 my $type = ref $pattern;
57 136         140 my $negated;
58 136 100       232 if ($type & RE_NEGATED) {
59 47 100       76 next if !$match;
60 44         48 $negated = 1;
61             } else {
62 89 50       146 next if $match;
63             }
64              
65 133         256 $match = pnmatchstar $pattern, $path, isDirectory => $is_directory;
66             }
67              
68 89 100       241 return 1 if $match;
69              
70             # Check that none of its parent directories has been ignored.
71 53 100       79 if (!$imode) {
72 35         45 $path =~ s{/$}{};
73              
74 35   100     104 while ($path =~ s{/[^/]*$}{} && length $path) {
75 15 100       33 return 1 if $self->__match(undef, $path, 1);
76             }
77             }
78              
79 50         155 return;
80             }
81              
82             sub match {
83 43     43 1 110 my ($self) = shift @_;
84            
85 43         82 return $self->__match(undef, @_);
86             }
87              
88             sub matchExclude {
89 2     2 1 5 &match;
90             }
91              
92             sub matchInclude {
93 31     31 1 89 my ($self) = shift @_;
94            
95 31         58 return $self->__match(1, @_);
96             }
97              
98             sub patterns {
99 100     100 1 672 return @{shift->{__patterns}};
  100         214  
100             }
101              
102             sub _readArray {
103 40     40   62 my ($self, $lines) = @_;
104              
105 40         44 my @patterns;
106 40         63 $self->{__patterns} = \@patterns;
107              
108 40         52 my $ignore_case = $self->{__ignore_case};
109 40         57 foreach my $line (@$lines) {
110 78         99 my $transpiled = eval { translatestar $line,
  78         163  
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       219 $transpiled = qr{^/$} if $@;
119 78         166 push @patterns, $transpiled;
120             }
121              
122 40         103 return $self;
123             }
124              
125             sub _readString {
126 39     39   58 my ($self, $string) = @_;
127              
128 39         55 my @lines;
129 39         102 foreach my $line (split /\n/, $string) {
130 92 100       184 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       184 if ($line =~ s/(\\*)([\x{9}-\x{13} ])[\x{9}-\x{13} ]*$//) {
135 12         23 my ($bs, $first) = ($1, $2);
136 12 100       21 if ($bs) {
137 4         5 $line .= $bs;
138              
139 4         8 my $num_bs = $bs =~ y/\\/\\/;
140              
141             # If the number of backslashes is odd, the last space was
142             # escaped.
143 4 100       7 $line .= $first if $num_bs & 1;
144             }
145             }
146 86 100       134 next if '' eq $line;
147              
148 75         125 push @lines, $line;
149             }
150              
151 39         89 return $self->_readArray(\@lines);
152             }
153              
154             sub _readFileHandle {
155 3     3   6 my ($self, $fh) = @_;
156              
157 3         6 my $filename = $self->{__filename};
158 3 50       15 $filename = __["in memory string"] if File::Globstar::empty($filename);
159              
160 3         13 $fh->clearerr;
161 3         66 my @lines = $fh->getlines;
162              
163 3 50       189 die __x("Error reading '{filename}': {error}!\n",
164             filename => $filename, error => $!) if $fh->error;
165            
166 3         12 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       3 if File::Globstar::empty($self->{__filename});
174              
175 1 50       35 open my $fh, '<', $filename
176             or die __x("Error reading '{filename}': {error}!\n",
177             filename => $filename, error => $!);
178            
179 1         4 return $self->_readFileHandle($fh);
180             }
181              
182             1;