File Coverage

blib/lib/App/Ack/ConfigFinder.pm
Criterion Covered Total %
statement 55 57 96.4
branch 19 22 86.3
condition 3 3 100.0
subroutine 10 10 100.0
pod 2 2 100.0
total 89 94 94.6


line stmt bran cond sub pod time code
1             package App::Ack::ConfigFinder;
2              
3             =head1 NAME
4              
5             App::Ack::ConfigFinder
6              
7             =head1 DESCRIPTION
8              
9             A module that contains the logic for locating the various configuration
10             files.
11              
12             =head1 LOCATING CONFIG FILES
13              
14             First, ack looks for a global ackrc.
15              
16             =over
17              
18             =item On Windows, this is `ackrc` in either COMMON_APPDATA or APPDATA.
19             If `ackrc` is present in both directories, ack uses both files in that
20             order.
21              
22             =item On a non-Windows OS, this is `/etc/ackrc`.
23              
24             =back
25              
26             Then, ack looks for a user-specific ackrc if the HOME environment
27             variable is set. This is either F<$HOME/.ackrc> or F<$HOME/_ackrc>.
28              
29             Then, ack looks for a project-specific ackrc file. ack searches
30             up the directory hierarchy for the first `.ackrc` or `_ackrc` file.
31             If this is one of the ackrc files found in the previous steps, it is
32             not loaded again.
33              
34             It is a fatal error if a directory contains both F<.ackrc> and F<_ackrc>.
35              
36             After ack loads the options from the found ackrc files, ack looks
37             at the C environment variable.
38              
39             Finally, ack takes settings from the command line.
40              
41             =cut
42              
43 8     8   214879 use strict;
  8         38  
  8         236  
44 8     8   38 use warnings;
  8         17  
  8         200  
45              
46 8     8   979 use App::Ack ();
  8         20  
  8         183  
47 8     8   42 use Cwd 3.00 ();
  8         175  
  8         199  
48 8     8   102 use File::Spec 3.00 ();
  8         139  
  8         252  
49              
50 8     8   5281 use if ($^O eq 'MSWin32'), 'Win32';
  8         139  
  8         56  
51              
52             =head1 METHODS
53              
54             =head2 new
55              
56             Creates a new config finder.
57              
58             =cut
59              
60             sub new {
61 4     4 1 4443 my ( $class ) = @_;
62              
63 4         26 return bless {}, $class;
64             }
65              
66              
67             sub _remove_redundancies {
68 46     46   94 my @configs = @_;
69              
70 46         67 my %seen;
71             my @uniq;
72 46         129 foreach my $config (@configs) {
73 110         192 my $path = $config->{path};
74 110 100       3233 my $key = -e $path ? Cwd::realpath( $path ) : $path;
75 110 50       302 if ( not $App::Ack::is_windows ) {
76             # On Unix, uniquify on inode.
77 110         952 my ($dev, $inode) = (stat $key)[0, 1];
78 110 100       424 $key = "$dev:$inode" if defined $dev;
79             }
80 110 100       454 push( @uniq, $config ) unless $seen{$key}++;
81             }
82 46         256 return @uniq;
83             }
84              
85              
86             sub _check_for_ackrc {
87 136 100   136   291 return unless defined $_[0];
88              
89 228         2859 my @files = grep { -f }
90 114         194 map { File::Spec->catfile(@_, $_) }
  228         1767  
91             qw(.ackrc _ackrc);
92              
93 114 100       454 App::Ack::die( File::Spec->catdir(@_) . ' contains both .ackrc and _ackrc. Please remove one of those files.' )
94             if @files > 1;
95              
96 110 100       307 return wantarray ? @files : $files[0];
97             } # end _check_for_ackrc
98              
99              
100             =head2 $finder->find_config_files
101              
102             Locates config files, and returns a list of them.
103              
104             =cut
105              
106             sub find_config_files {
107 50     50 1 60619 my @config_files;
108              
109 50 50       133 if ( $App::Ack::is_windows ) {
110 0         0 push @config_files, map { +{ path => File::Spec->catfile($_, 'ackrc') } } (
  0         0  
111             Win32::GetFolderPath(Win32::CSIDL_COMMON_APPDATA()),
112             Win32::GetFolderPath(Win32::CSIDL_APPDATA()),
113             );
114             }
115             else {
116 50         131 push @config_files, { path => '/etc/ackrc' };
117             }
118              
119              
120 50 100 100     271 if ( $ENV{'ACKRC'} && -f $ENV{'ACKRC'} ) {
121 4         48 push @config_files, { path => $ENV{'ACKRC'} };
122             }
123             else {
124 46         138 push @config_files, map { +{ path => $_ } } _check_for_ackrc($ENV{'HOME'});
  24         75  
125             }
126              
127 50         417 my $cwd = Cwd::getcwd();
128 50 50       141 return () unless defined $cwd;
129              
130             # XXX This should go through some untainted cwd-fetching function, and not get untainted brute-force like this.
131 50         222 $cwd =~ /(.+)/;
132 50         138 $cwd = $1;
133 50         306 my @dirs = File::Spec->splitdir( $cwd );
134 50         123 while ( @dirs ) {
135 90         177 my $ackrc = _check_for_ackrc(@dirs);
136 86 100       182 if ( defined $ackrc ) {
137 38         124 push @config_files, { project => 1, path => $ackrc };
138 38         76 last;
139             }
140 48         103 pop @dirs;
141             }
142              
143             # We only test for existence here, so if the file is deleted out from under us, this will fail later.
144 46         113 return _remove_redundancies( @config_files );
145             }
146              
147             1;