File Coverage

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


line stmt bran cond sub pod time code
1             package Text::Gitignore;
2 14     14   885685 use strict;
  14         158  
  14         407  
3 14     14   70 use warnings;
  14         27  
  14         405  
4 14     14   67 use base 'Exporter';
  14         23  
  14         11882  
5              
6             our @EXPORT_OK = qw(match_gitignore build_gitignore_matcher);
7             our $VERSION = "0.04";
8              
9             sub match_gitignore {
10 37     37 1 94022 my ( $patterns, @paths ) = @_;
11              
12 37         136 my $matcher = build_gitignore_matcher($patterns);
13              
14 37         70 my @matched;
15 37         69 for my $path (@paths) {
16 55 100       108 push @matched, $path if $matcher->($path);
17             }
18              
19 37         304 return @matched;
20             }
21              
22             sub build_gitignore_matcher {
23 89     89 1 1555 my ($patterns) = @_;
24              
25 89 100       299 $patterns = [$patterns] unless ref $patterns eq 'ARRAY';
26 89         207 $patterns = [ grep { !/^#/ } @$patterns ];
  109         420  
27              
28             # Escaped comments and trailing spaces
29 89         220 for my $pattern (@$patterns) {
30 108         266 $pattern =~ s{(?!\\)\s+$}{};
31 108         199 $pattern =~ s{^\\#}{#};
32             }
33              
34             # Empty lines
35 89         174 $patterns = [ grep { length $_ } @$patterns ];
  108         254  
36              
37             my $build_pattern = sub {
38 107     107   216 my ($pattern) = @_;
39              
40 107         209 $pattern = quotemeta $pattern;
41              
42 107         222 $pattern =~ s{\\\*\\\*\\/}{.*}g;
43 107         189 $pattern =~ s{\\\*\\\*}{.*}g;
44 107         229 $pattern =~ s{\\\*}{[^/]*}g;
45 107         170 $pattern =~ s{\\\?}{[^/]}g;
46 107         171 $pattern =~ s{^\\\/}{^};
47 107         212 $pattern =~ s{\\\[(.*?)\\\]}{
48 10 100       30 '[' . do { my $c = $1; $c =~ s{^\\!}{} ? '^' : '' }
  10         43  
49 10         60 . do { my $c = $1; $c =~ s/\\\-/\-/; $c }
  10         21  
  10         20  
  10         43  
50             . ']'
51             }eg;
52              
53 107 100       485 $pattern .= '(\/|$)' unless $pattern =~ m{\/$};
54              
55 107         375 return $pattern;
56 89         406 };
57              
58 89         148 my @patterns_re;
59 89         163 foreach my $pattern (@$patterns) {
60 107 100       288 if ( $pattern =~ m/^!/ ) {
61 19         68 my $re = $build_pattern->(substr $pattern, 1);
62              
63 19         73 push @patterns_re,
64             {
65             re => $re,
66             negative => 1
67             };
68             }
69             else {
70              
71             # Transform escaped negation to normal path
72 88         187 $pattern =~ s{^\\!}{!};
73              
74 88         180 push @patterns_re, { re => $build_pattern->($pattern) };
75             }
76             }
77              
78 89         220 my @negatives = grep { /^!/ } @$patterns;
  107         291  
79              
80             return sub {
81 109     109   6101 my $path = shift;
82              
83 109         149 my $match = undef;
84              
85 109         188 foreach my $pattern (@patterns_re) {
86 136         227 my $re = $pattern->{re};
87              
88 136 100 100     333 next if $match && !$pattern->{negative};
89              
90 135 100       256 if ( $pattern->{negative} ) {
91 26 100       262 if ( $path =~ m/$re/ ) {
92 14         36 $match = 0;
93             }
94             }
95             else {
96 109 100       1920 $match = 1 if $path =~ m/$re/;
97              
98 109 100 100     510 if ( $match && !@negatives ) {
99 55         268 return $match;
100             }
101             }
102             }
103              
104 54         203 return $match;
105 89         630 };
106             }
107              
108             1;
109             __END__