File Coverage

blib/lib/App/Ack/ConfigFinder.pm
Criterion Covered Total %
statement 18 57 31.5
branch 0 22 0.0
condition 0 3 0.0
subroutine 6 10 60.0
pod 2 2 100.0
total 26 94 27.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 2     2   14 use strict;
  2         5  
  2         57  
44 2     2   12 use warnings;
  2         4  
  2         50  
45              
46 2     2   11 use App::Ack ();
  2         3  
  2         48  
47 2     2   10 use Cwd 3.00 ();
  2         38  
  2         56  
48 2     2   11 use File::Spec 3.00 ();
  2         28  
  2         83  
49              
50 2     2   1395 use if ($^O eq 'MSWin32'), 'Win32';
  2         27  
  2         15  
51              
52             =head1 METHODS
53              
54             =head2 new
55              
56             Creates a new config finder.
57              
58             =cut
59              
60             sub new {
61 0     0 1   my ( $class ) = @_;
62              
63 0           return bless {}, $class;
64             }
65              
66              
67             sub _remove_redundancies {
68 0     0     my @configs = @_;
69              
70 0           my %seen;
71             my @uniq;
72 0           foreach my $config (@configs) {
73 0           my $path = $config->{path};
74 0 0         my $key = -e $path ? Cwd::realpath( $path ) : $path;
75 0 0         if ( not $App::Ack::is_windows ) {
76             # On Unix, uniquify on inode.
77 0           my ($dev, $inode) = (stat $key)[0, 1];
78 0 0         $key = "$dev:$inode" if defined $dev;
79             }
80 0 0         push( @uniq, $config ) unless $seen{$key}++;
81             }
82 0           return @uniq;
83             }
84              
85              
86             sub _check_for_ackrc {
87 0 0   0     return unless defined $_[0];
88              
89 0           my @files = grep { -f }
90 0           map { File::Spec->catfile(@_, $_) }
  0            
91             qw(.ackrc _ackrc);
92              
93 0 0         App::Ack::die( File::Spec->catdir(@_) . ' contains both .ackrc and _ackrc. Please remove one of those files.' )
94             if @files > 1;
95              
96 0 0         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 0     0 1   my @config_files;
108              
109 0 0         if ( $App::Ack::is_windows ) {
110 0           push @config_files, map { +{ path => File::Spec->catfile($_, 'ackrc') } } (
  0            
111             Win32::GetFolderPath(Win32::CSIDL_COMMON_APPDATA()),
112             Win32::GetFolderPath(Win32::CSIDL_APPDATA()),
113             );
114             }
115             else {
116 0           push @config_files, { path => '/etc/ackrc' };
117             }
118              
119              
120 0 0 0       if ( $ENV{'ACKRC'} && -f $ENV{'ACKRC'} ) {
121 0           push @config_files, { path => $ENV{'ACKRC'} };
122             }
123             else {
124 0           push @config_files, map { +{ path => $_ } } _check_for_ackrc($ENV{'HOME'});
  0            
125             }
126              
127 0           my $cwd = Cwd::getcwd();
128 0 0         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 0           $cwd =~ /(.+)/;
132 0           $cwd = $1;
133 0           my @dirs = File::Spec->splitdir( $cwd );
134 0           while ( @dirs ) {
135 0           my $ackrc = _check_for_ackrc(@dirs);
136 0 0         if ( defined $ackrc ) {
137 0           push @config_files, { project => 1, path => $ackrc };
138 0           last;
139             }
140 0           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 0           return _remove_redundancies( @config_files );
145             }
146              
147             1;