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   686827 use strict;
  13         126  
  13         321  
3 13     13   56 use warnings;
  13         21  
  13         300  
4 13     13   51 use base 'Exporter';
  13         21  
  13         8823  
5              
6             our @EXPORT_OK = qw(match_gitignore build_gitignore_matcher);
7             our $VERSION = "0.03";
8              
9             sub match_gitignore {
10 37     37 1 72636 my ( $patterns, @paths ) = @_;
11              
12 37         100 my $matcher = build_gitignore_matcher($patterns);
13              
14 37         58 my @matched;
15 37         71 for my $path (@paths) {
16 55 100       88 push @matched, $path if $matcher->($path);
17             }
18              
19 37         266 return @matched;
20             }
21              
22             sub build_gitignore_matcher {
23 88     88 1 1135 my ($patterns) = @_;
24              
25 88 100       246 $patterns = [$patterns] unless ref $patterns eq 'ARRAY';
26 88         165 $patterns = [ grep { !/^#/ } @$patterns ];
  106         337  
27              
28             # Escaped comments and trailing spaces
29 88         181 for my $pattern (@$patterns) {
30 105         215 $pattern =~ s{(?!\\)\s+$}{};
31 105         176 $pattern =~ s{^\\#}{#};
32             }
33              
34             # Empty lines
35 88         179 $patterns = [ grep { length $_ } @$patterns ];
  105         198  
36              
37             my $build_pattern = sub {
38 104     104   191 my ($pattern) = @_;
39              
40 104         161 $pattern = quotemeta $pattern;
41              
42 104         168 $pattern =~ s{\\\*\\\*\\/}{.*}g;
43 104         157 $pattern =~ s{\\\*\\\*}{.*}g;
44 104         188 $pattern =~ s{\\\*}{[^/]*}g;
45 104         140 $pattern =~ s{\\\?}{[^/]}g;
46 104         134 $pattern =~ s{^\\\/}{^};
47 104         178 $pattern =~ s{\\\[(.*?)\\\]}{
48 10 100       22 '[' . do { my $c = $1; $c =~ s{^\\!}{} ? '^' : '' }
  10         43  
49 10         48 . do { my $c = $1; $c =~ s/\\\-/\-/; $c }
  10         18  
  10         17  
  10         42  
50             . ']'
51             }eg;
52              
53 104 100       377 $pattern .= '(\/|$)' unless $pattern =~ m{\/$};
54              
55 104         308 return $pattern;
56 88         338 };
57              
58 88         125 my @patterns_re;
59 88         129 foreach my $pattern (@$patterns) {
60 104 100       225 if ( $pattern =~ m/^!/ ) {
61 18         42 my $re = $build_pattern->(substr $pattern, 1);
62              
63 18         57 push @patterns_re,
64             {
65             re => $re,
66             negative => 1
67             };
68             }
69             else {
70              
71             # Transform escaped negation to normal path
72 86         131 $pattern =~ s{^\\!}{!};
73              
74 86         136 push @patterns_re, { re => $build_pattern->($pattern) };
75             }
76             }
77              
78 88         150 my @negatives = grep { /^!/ } @$patterns;
  104         239  
79              
80             return sub {
81 106     106   165 my $path = shift;
82              
83 106         128 my $match = 0;
84              
85 106         144 foreach my $pattern (@patterns_re) {
86 127         166 my $re = $pattern->{re};
87              
88 127 100 100     266 next if $match && !$pattern->{negative};
89              
90 126 100       198 if ( $pattern->{negative} ) {
91 23 100       224 if ( $path =~ m/$re/ ) {
92 12         26 $match = 0;
93             }
94             }
95             else {
96 103         1289 $match = !!( $path =~ m/$re/ );
97              
98 103 100 100     404 if ( $match && !@negatives ) {
99 55         244 return $match;
100             }
101             }
102             }
103              
104 51         163 return $match;
105 88         506 };
106             }
107              
108             1;
109             __END__