File Coverage

blib/lib/Text/Gitignore.pm
Criterion Covered Total %
statement 63 63 100.0
branch 18 18 100.0
condition 6 6 100.0
subroutine 7 7 100.0
pod 2 2 100.0
total 96 96 100.0


line stmt bran cond sub pod time code
1             package Text::Gitignore;
2 13     13   794842 use strict;
  13         120  
  13         384  
3 13     13   65 use warnings;
  13         23  
  13         397  
4 13     13   69 use base 'Exporter';
  13         20  
  13         10698  
5              
6             our @EXPORT_OK = qw(match_gitignore build_gitignore_matcher);
7             our $VERSION = "0.02";
8              
9             sub match_gitignore {
10 35     35 1 73914 my ( $patterns, @paths ) = @_;
11              
12 35         102 my $matcher = build_gitignore_matcher($patterns);
13              
14 35         67 my @matched;
15 35         69 for my $path (@paths) {
16 52 100       126 push @matched, $path if $matcher->($path);
17             }
18              
19 35         278 return @matched;
20             }
21              
22             sub build_gitignore_matcher {
23 86     86 1 1309 my ($patterns) = @_;
24              
25 86 100       282 $patterns = [$patterns] unless ref $patterns eq 'ARRAY';
26 86         185 $patterns = [ grep { !/^#/ } @$patterns ];
  104         385  
27              
28             # Escaped comments and trailing spaces
29 86         213 for my $pattern (@$patterns) {
30 103         263 $pattern =~ s{(?!\\)\s+$}{};
31 103         196 $pattern =~ s{^\\#}{#};
32             }
33              
34             # Empty lines
35 86         163 $patterns = [ grep { length $_ } @$patterns ];
  103         253  
36              
37             my $build_pattern = sub {
38 102     102   207 my ($pattern) = @_;
39              
40 102         196 $pattern = quotemeta $pattern;
41              
42 102         196 $pattern =~ s{\\\*\\\*\\/}{.*}g;
43 102         234 $pattern =~ s{\\\*\\\*}{.*}g;
44 102         217 $pattern =~ s{\\\*}{[^/]*}g;
45 102         159 $pattern =~ s{\\\?}{[^/]}g;
46 102         159 $pattern =~ s{^\\\/}{^};
47 102         202 $pattern =~ s{\\\[(.*?)\\\]}{
48 10 100       29 '[' . do { my $c = $1; $c =~ s{^\\!}{} ? '^' : '' }
  10         41  
49 10         66 . do { my $c = $1; $c =~ s/\\\-/\-/; $c }
  10         19  
  10         29  
  10         52  
50             . ']'
51             }eg;
52              
53 102 100       421 $pattern .= '$' unless $pattern =~ m{\/$};
54              
55 102         374 return $pattern;
56 86         400 };
57              
58 86         156 my @patterns_re;
59 86         150 foreach my $pattern (@$patterns) {
60 102 100       258 if ( $pattern =~ m/^!/ ) {
61 18         47 my $re = $build_pattern->(substr $pattern, 1);
62              
63 18         68 push @patterns_re,
64             {
65             re => $re,
66             negative => 1
67             };
68             }
69             else {
70              
71             # Transform escaped negation to normal path
72 84         188 $pattern =~ s{^\\!}{!};
73              
74 84         162 push @patterns_re, { re => $build_pattern->($pattern) };
75             }
76             }
77              
78 86         189 my @negatives = grep { /^!/ } @$patterns;
  102         289  
79              
80             return sub {
81 103     103   194 my $path = shift;
82              
83 103         139 my $match = 0;
84              
85 103         170 foreach my $pattern (@patterns_re) {
86 124         213 my $re = $pattern->{re};
87              
88 124 100 100     295 next if $match && !$pattern->{negative};
89              
90 123 100       252 if ( $pattern->{negative} ) {
91 23 100       208 if ( $path =~ m/$re/ ) {
92 12         30 $match = 0;
93             }
94             }
95             else {
96 100         1267 $match = !!( $path =~ m/$re/ );
97              
98 100 100 100     460 if ( $match && !@negatives ) {
99 52         285 return $match;
100             }
101             }
102             }
103              
104 51         224 return $match;
105 86         598 };
106             }
107              
108             1;
109             __END__