File Coverage

blib/lib/App/Ack/ConfigLoader.pm
Criterion Covered Total %
statement 41 457 8.9
branch 0 122 0.0
condition 0 28 0.0
subroutine 14 72 19.4
pod 0 6 0.0
total 55 685 8.0


line stmt bran cond sub pod time code
1             package App::Ack::ConfigLoader;
2              
3 2     2   1559 use strict;
  2         4  
  2         48  
4 2     2   7 use warnings;
  2         4  
  2         37  
5 2     2   26 use 5.010;
  2         6  
6              
7 2     2   16 use App::Ack ();
  2         2  
  2         23  
8 2     2   7 use App::Ack::ConfigDefault ();
  2         2  
  2         20  
9 2     2   713 use App::Ack::ConfigFinder ();
  2         4  
  2         34  
10 2     2   681 use App::Ack::Filter ();
  2         5  
  2         32  
11 2     2   701 use App::Ack::Filter::Collection ();
  2         4  
  2         33  
12 2     2   739 use App::Ack::Filter::Default ();
  2         5  
  2         31  
13 2     2   678 use App::Ack::Filter::IsPath ();
  2         4  
  2         38  
14 2     2   10 use File::Spec 3.00 ();
  2         26  
  2         35  
15 2     2   7 use Getopt::Long 2.38 ();
  2         11  
  2         100  
16 2     2   9 use Text::ParseWords 3.1 ();
  2         23  
  2         2685  
17              
18             sub configure_parser {
19 0     0 0   my @opts = @_;
20              
21 0           my @standard = qw(
22             default
23             bundling
24             no_auto_help
25             no_auto_version
26             no_ignore_case
27             );
28 0           Getopt::Long::Configure( @standard, @opts );
29              
30 0           return;
31             }
32              
33              
34             sub _generate_ignore_dir {
35 0     0     my ( $option_name, $opt ) = @_;
36              
37 0           my $is_inverted = $option_name =~ /^--no/;
38              
39             return sub {
40 0     0     my ( undef, $dir ) = @_;
41              
42 0           $dir = _remove_directory_separator( $dir );
43 0 0         if ( $dir !~ /:/ ) {
44 0           $dir = 'is:' . $dir;
45             }
46              
47 0           my ( $filter_type, $args ) = split /:/, $dir, 2;
48              
49 0 0         if ( $filter_type eq 'firstlinematch' ) {
50 0           App::Ack::die( qq{Invalid filter specification "$filter_type" for option '$option_name'} );
51             }
52              
53 0           my $filter = App::Ack::Filter->create_filter($filter_type, split(/,/, $args));
54 0           my $collection;
55              
56 0   0       my $previous_inversion_matches = $opt->{idirs} && !($is_inverted xor $opt->{idirs}[-1]->is_inverted());
57              
58 0 0         if ( $previous_inversion_matches ) {
59 0           $collection = $opt->{idirs}[-1];
60              
61 0 0         if ( $is_inverted ) {
62             # This relies on invert of an inverted filter to return the original.
63 0           $collection = $collection->invert();
64             }
65             }
66             else {
67 0           $collection = App::Ack::Filter::Collection->new();
68 0 0         push @{ $opt->{idirs} }, $is_inverted ? $collection->invert() : $collection;
  0            
69             }
70              
71 0           $collection->add($filter);
72              
73 0 0         if ( $filter_type eq 'is' ) {
74 0           $collection->add(App::Ack::Filter::IsPath->new($args));
75             }
76 0           };
77             }
78              
79              
80             sub _remove_directory_separator {
81 0     0     my $path = shift;
82              
83 0 0         state $dir_sep_chars = $App::Ack::is_windows ? quotemeta( '\\/' ) : quotemeta( File::Spec->catfile( '', '' ) );
84              
85 0           $path =~ s/[$dir_sep_chars]$//;
86              
87 0           return $path;
88             }
89              
90              
91             sub _process_filter_spec {
92 0     0     my ( $spec ) = @_;
93              
94 0 0         if ( $spec =~ /^(\w+):(\w+):(.*)/ ) {
    0          
95 0           my ( $type_name, $ext_type, $arguments ) = ( $1, $2, $3 );
96              
97 0           return ( $type_name,
98             App::Ack::Filter->create_filter($ext_type, split(/,/, $arguments)) );
99             }
100             elsif ( $spec =~ /^(\w+)=(.*)/ ) { # Check to see if we have ack1-style argument specification.
101 0           my ( $type_name, $extensions ) = ( $1, $2 );
102              
103 0           my @extensions = split(/,/, $extensions);
104 0           foreach my $extension ( @extensions ) {
105 0           $extension =~ s/^[.]//;
106             }
107              
108 0           return ( $type_name, App::Ack::Filter->create_filter('ext', @extensions) );
109             }
110             else {
111 0           App::Ack::die( "Invalid filter specification '$spec'" );
112             }
113             }
114              
115              
116             sub _uninvert_filter {
117 0     0     my ( $opt, @filters ) = @_;
118              
119 0 0 0       return unless defined $opt->{filters} && @filters;
120              
121             # Loop through all the registered filters. If we hit one that
122             # matches this extension and it's inverted, we need to delete it from
123             # the options.
124 0           for ( my $i = 0; $i < @{ $opt->{filters} }; $i++ ) {
  0            
125 0           my $opt_filter = @{ $opt->{filters} }[$i];
  0            
126              
127             # XXX Do a real list comparison? This just checks string equivalence.
128 0 0 0       if ( $opt_filter->is_inverted() && "$opt_filter->{filter}" eq "@filters" ) {
129 0           splice @{ $opt->{filters} }, $i, 1;
  0            
130 0           $i--;
131             }
132             }
133              
134 0           return;
135             }
136              
137              
138             sub _process_filetypes {
139 0     0     my ( $opt, $arg_sources ) = @_;
140              
141 0           my %additional_specs;
142              
143             my $add_spec = sub {
144 0     0     my ( undef, $spec ) = @_;
145              
146 0           my ( $name, $filter ) = _process_filter_spec($spec);
147              
148 0           push @{ $App::Ack::mappings{$name} }, $filter;
  0            
149              
150             $additional_specs{$name . '!'} = sub {
151 0           my ( undef, $value ) = @_;
152              
153 0           my @filters = @{ $App::Ack::mappings{$name} };
  0            
154 0 0         if ( not $value ) {
155 0           @filters = map { $_->invert() } @filters;
  0            
156             }
157             else {
158 0           _uninvert_filter( $opt, @filters );
159             }
160              
161 0           push @{ $opt->{'filters'} }, @filters;
  0            
162 0           };
163 0           };
164              
165             my $set_spec = sub {
166 0     0     my ( undef, $spec ) = @_;
167              
168 0           my ( $name, $filter ) = _process_filter_spec($spec);
169              
170 0           $App::Ack::mappings{$name} = [ $filter ];
171              
172             $additional_specs{$name . '!'} = sub {
173 0           my ( undef, $value ) = @_;
174              
175 0           my @filters = @{ $App::Ack::mappings{$name} };
  0            
176 0 0         if ( not $value ) {
177 0           @filters = map { $_->invert() } @filters;
  0            
178             }
179              
180 0           push @{ $opt->{'filters'} }, @filters;
  0            
181 0           };
182 0           };
183              
184             my $delete_spec = sub {
185 0     0     my ( undef, $name ) = @_;
186              
187 0           delete $App::Ack::mappings{$name};
188 0           delete $additional_specs{$name . '!'};
189 0           };
190              
191 0           my %type_arg_specs = (
192             'type-add=s' => $add_spec,
193             'type-set=s' => $set_spec,
194             'type-del=s' => $delete_spec,
195             );
196              
197 0           configure_parser( 'no_auto_abbrev', 'pass_through' );
198 0           foreach my $source (@{$arg_sources}) {
  0            
199 0           my $args = $source->{contents};
200              
201 0 0         if ( ref($args) ) {
202             # $args are modified in place, so no need to munge $arg_sources
203 0           Getopt::Long::GetOptionsFromArray( $args, %type_arg_specs );
204             }
205             else {
206 0           ( undef, $source->{contents} ) =
207             Getopt::Long::GetOptionsFromString( $args, %type_arg_specs );
208             }
209             }
210              
211             $additional_specs{'k|known-types'} = sub {
212 0     0     my @filters = map { @{$_} } values(%App::Ack::mappings);
  0            
  0            
213              
214 0           push @{ $opt->{'filters'} }, @filters;
  0            
215 0           };
216              
217 0           return \%additional_specs;
218             }
219              
220              
221             sub get_arg_spec {
222 0     0 0   my ( $opt, $extra_specs ) = @_;
223              
224             =begin Adding-Options
225              
226             *** IF YOU ARE MODIFYING ACK PLEASE READ THIS ***
227              
228             If you plan to add a new option to ack, please make sure of
229             the following:
230              
231             * Your new option has a test underneath the t/ directory.
232             * Your new option is explained when a user invokes ack --help.
233             (See App::Ack::show_help)
234             * Your new option is explained when a user invokes ack --man.
235             (See the POD at the end of ./ack)
236             * Add your option to t/config-loader.t
237             * Add your option to t/Util.pm#get_expected_options
238             * Add your option's description and aliases to dev/generate-completion-scripts.pl
239             * Go through the list of options already available, and consider
240             whether your new option can be considered mutex with another option.
241              
242             =end Adding-Options
243              
244             =cut
245              
246             sub _type_handler {
247 0     0     my ( $getopt, $value ) = @_;
248              
249 0           my $cb_value = 1;
250 0 0         if ( $value =~ s/^no// ) {
251 0           $cb_value = 0;
252             }
253              
254 0           my $callback;
255             {
256 2     2   13 no warnings;
  2         4  
  2         7728  
  0            
257 0           $callback = $extra_specs->{ $value . '!' };
258             }
259              
260 0 0         if ( $callback ) {
261 0           $callback->( $getopt, $cb_value );
262             }
263             else {
264 0           App::Ack::die( "Unknown type '$value'" );
265             }
266              
267 0           return;
268             }
269              
270             return {
271 0     0     1 => sub { $opt->{1} = $opt->{m} = 1 },
272 0     0     'A|after-context:-1' => sub { shift; $opt->{A} = _context_value(shift) },
  0            
273 0     0     'B|before-context:-1' => sub { shift; $opt->{B} = _context_value(shift) },
  0            
274 0     0     'C|context:-1' => sub { shift; $opt->{B} = $opt->{A} = _context_value(shift) },
  0            
275             'break!' => \$opt->{break},
276             'c|count' => \$opt->{c},
277             'color|colour!' => \$opt->{color},
278             'color-match=s' => \$ENV{ACK_COLOR_MATCH},
279             'color-filename=s' => \$ENV{ACK_COLOR_FILENAME},
280             'color-colno=s' => \$ENV{ACK_COLOR_COLNO},
281             'color-lineno=s' => \$ENV{ACK_COLOR_LINENO},
282             'column!' => \$opt->{column},
283 0     0     'create-ackrc' => sub { say for ( '--ignore-ack-defaults', App::Ack::ConfigDefault::options() ); exit; },
  0            
284             'debug' => \$opt->{debug},
285             'env!' => sub {
286 0     0     my ( undef, $value ) = @_;
287              
288 0 0         if ( !$value ) {
289 0           $opt->{noenv_seen} = 1;
290             }
291             },
292             f => \$opt->{f},
293             'files-from=s' => \$opt->{files_from},
294             'filter!' => \$App::Ack::is_filter_mode,
295 0     0     flush => sub { $| = 1 },
296             'follow!' => \$opt->{follow},
297             g => \$opt->{g},
298 0     0     'group!' => sub { shift; $opt->{heading} = $opt->{break} = shift },
  0            
299             'heading!' => \$opt->{heading},
300             'h|no-filename' => \$opt->{h},
301             'H|with-filename' => \$opt->{H},
302 0     0     'i|ignore-case' => sub { $opt->{i} = 1; $opt->{S} = 0; },
  0            
303 0     0     'I|no-ignore-case' => sub { $opt->{i} = 0; $opt->{S} = 0; },
  0            
304             'ignore-directory|ignore-dir=s' => _generate_ignore_dir('--ignore-dir', $opt),
305             'ignore-file=s' => sub {
306 0     0     my ( undef, $file ) = @_;
307              
308 0           my ( $filter_type, $args ) = split /:/, $file, 2;
309              
310 0   0       my $filter = App::Ack::Filter->create_filter($filter_type, split(/,/, $args//''));
311              
312 0 0         if ( !$opt->{ifiles} ) {
313 0           $opt->{ifiles} = App::Ack::Filter::Collection->new();
314             }
315 0           $opt->{ifiles}->add($filter);
316             },
317             'l|files-with-matches'
318             => \$opt->{l},
319             'L|files-without-matches'
320             => \$opt->{L},
321             'm|max-count=i' => \$opt->{m},
322             'match=s' => \$opt->{regex},
323             'n|no-recurse' => \$opt->{n},
324 0     0     o => sub { $opt->{output} = '$&' },
325             'output=s' => \$opt->{output},
326             'pager:s' => sub {
327 0     0     my ( undef, $value ) = @_;
328              
329 0   0       $opt->{pager} = $value || $ENV{PAGER};
330             },
331             'noignore-directory|noignore-dir=s' => _generate_ignore_dir('--noignore-dir', $opt),
332 0     0     'nopager' => sub { $opt->{pager} = undef },
333             'passthru' => \$opt->{passthru},
334             'print0' => \$opt->{print0},
335             'p|proximate:1' => \$opt->{p},
336 0     0     'P' => sub { $opt->{p} = 0 },
337             'Q|literal' => \$opt->{Q},
338 0     0     'r|R|recurse' => sub { $opt->{n} = 0 },
339             'range-start=s' => \$opt->{range_start},
340             'range-end=s' => \$opt->{range_end},
341             'range-invert!' => \$opt->{range_invert},
342             's' => \$opt->{s},
343             'show-types' => \$opt->{show_types},
344 0 0   0     'S|smart-case!' => sub { my (undef,$value) = @_; $opt->{S} = $value; $opt->{i} = 0 if $value; },
  0            
  0            
345             'sort-files' => \$opt->{sort_files},
346             't|type=s' => \&_type_handler,
347 0     0     'T=s' => sub { my ($getopt,$value) = @_; $value="no$value"; _type_handler($getopt,$value); },
  0            
  0            
348             'underline!' => \$opt->{underline},
349             'v|invert-match' => \$opt->{v},
350             'w|word-regexp' => \$opt->{w},
351 0     0     'x' => sub { $opt->{files_from} = '-' },
352              
353 0     0     'help' => sub { App::Ack::show_help(); exit; },
  0            
354 0     0     'help-types' => sub { App::Ack::show_help_types(); exit; },
  0            
355 0     0     'help-colors' => sub { App::Ack::show_help_colors(); exit; },
  0            
356 0     0     'help-rgb-colors' => sub { App::Ack::show_help_rgb(); exit; },
  0            
357 0 0         $extra_specs ? %{$extra_specs} : (),
  0            
358             }; # arg_specs
359             }
360              
361              
362             sub _context_value {
363 0     0     my $val = shift;
364              
365             # Contexts default to 2.
366 0 0 0       return (!defined($val) || ($val < 0)) ? 2 : $val;
367             }
368              
369              
370             sub _process_other {
371 0     0     my ( $opt, $extra_specs, $arg_sources ) = @_;
372              
373 0           my $argv_source;
374             my $is_help_types_active;
375              
376 0           foreach my $source (@{$arg_sources}) {
  0            
377 0 0         if ( $source->{name} eq 'ARGV' ) {
378 0           $argv_source = $source->{contents};
379 0           last;
380             }
381             }
382              
383 0 0         if ( $argv_source ) { # This *should* always be true, but you never know...
384 0           configure_parser( 'pass_through' );
385 0           Getopt::Long::GetOptionsFromArray( [ @{$argv_source} ],
  0            
386             'help-types' => \$is_help_types_active,
387             );
388             }
389              
390 0           my $arg_specs = get_arg_spec( $opt, $extra_specs );
391              
392 0           configure_parser();
393 0           foreach my $source (@{$arg_sources}) {
  0            
394 0           my ( $source_name, $args ) = @{$source}{qw/name contents/};
  0            
395              
396 0           my $args_for_source = { %{$arg_specs} };
  0            
397              
398 0 0         if ( $source->{is_ackrc} ) {
399             my $illegal = sub {
400 0     0     my $name = shift;
401 0           App::Ack::die( "Option --$name is forbidden in .ackrc files." );
402 0           };
403              
404             $args_for_source = {
405 0           %{$args_for_source},
  0            
406             'output=s' => $illegal,
407             'match=s' => $illegal,
408             };
409             }
410 0 0         if ( $source->{project} ) {
411             my $illegal = sub {
412 0     0     my $name = shift;
413 0           App::Ack::die( "Option --$name is forbidden in project .ackrc files." );
414 0           };
415              
416             $args_for_source = {
417 0           %{$args_for_source},
  0            
418             'pager:s' => $illegal,
419             };
420             }
421              
422 0           my $ret;
423 0 0         if ( ref($args) ) {
424 0           $ret = Getopt::Long::GetOptionsFromArray( $args, %{$args_for_source} );
  0            
425             }
426             else {
427             ( $ret, $source->{contents} ) =
428 0           Getopt::Long::GetOptionsFromString( $args, %{$args_for_source} );
  0            
429             }
430 0 0         if ( !$ret ) {
431 0 0         if ( !$is_help_types_active ) {
432 0 0         my $where = $source_name eq 'ARGV' ? 'on command line' : "in $source_name";
433 0           App::Ack::die( "Invalid option $where" );
434             }
435             }
436 0 0         if ( $opt->{noenv_seen} ) {
437 0           App::Ack::die( "--noenv found in $source_name" );
438             }
439             }
440              
441             # XXX We need to check on a -- in the middle of a non-ARGV source
442              
443 0           return;
444             }
445              
446              
447             sub _explode_sources {
448 0     0     my ( $sources ) = @_;
449              
450 0           my @new_sources;
451              
452             my %opt;
453 0           my $arg_spec = get_arg_spec( \%opt, {} );
454              
455 0     0     my $dummy_sub = sub {};
456             my $add_type = sub {
457 0     0     my ( undef, $arg ) = @_;
458              
459 0 0         if ( $arg =~ /(\w+)=/) {
460 0           $arg_spec->{$1} = $dummy_sub;
461             }
462             else {
463 0           ( $arg ) = split /:/, $arg;
464 0           $arg_spec->{$arg} = $dummy_sub;
465             }
466 0           };
467              
468             my $del_type = sub {
469 0     0     my ( undef, $arg ) = @_;
470              
471 0           delete $arg_spec->{$arg};
472 0           };
473              
474 0           configure_parser( 'pass_through' );
475 0           foreach my $source (@{$sources}) {
  0            
476 0           my ( $name, $options ) = @{$source}{qw/name contents/};
  0            
477 0 0         if ( ref($options) ne 'ARRAY' ) {
478 0           $source->{contents} = $options =
479             [ Text::ParseWords::shellwords($options) ];
480             }
481              
482 0           for my $j ( 0 .. @{$options}-1 ) {
  0            
483 0 0         next unless $options->[$j] =~ /^-/;
484 0           my @chunk = ( $options->[$j] );
485 0   0       push @chunk, $options->[$j] while ++$j < @{$options} && $options->[$j] !~ /^-/;
  0            
486 0           $j--;
487              
488 0           my @copy = @chunk;
489             Getopt::Long::GetOptionsFromArray( [@chunk],
490             'type-add=s' => $add_type,
491             'type-set=s' => $add_type,
492             'type-del=s' => $del_type,
493 0           %{$arg_spec}
  0            
494             );
495              
496 0           push @new_sources, {
497             name => $name,
498             contents => \@copy,
499             };
500             }
501             }
502              
503 0           return \@new_sources;
504             }
505              
506              
507             sub _compare_opts {
508 0     0     my ( $a, $b ) = @_;
509              
510 0           my $first_a = $a->[0];
511 0           my $first_b = $b->[0];
512              
513 0           $first_a =~ s/^--?//;
514 0           $first_b =~ s/^--?//;
515              
516 0           return $first_a cmp $first_b;
517             }
518              
519              
520             sub _dump_options {
521 0     0     my ( $sources ) = @_;
522              
523 0           $sources = _explode_sources($sources);
524              
525 0           my %opts_by_source;
526             my @source_names;
527              
528 0           foreach my $source (@{$sources}) {
  0            
529 0           my $name = $source->{name};
530 0 0         if ( not $opts_by_source{$name} ) {
531 0           $opts_by_source{$name} = [];
532 0           push @source_names, $name;
533             }
534 0           push @{$opts_by_source{$name}}, $source->{contents};
  0            
535             }
536              
537 0           foreach my $name (@source_names) {
538 0           my $contents = $opts_by_source{$name};
539              
540 0           say $name;
541 0           say '=' x length($name);
542 0           say ' ', join(' ', @{$_}) for sort { _compare_opts($a, $b) } @{$contents};
  0            
  0            
  0            
543             }
544              
545 0           return;
546             }
547              
548              
549             sub _remove_default_options_if_needed {
550 0     0     my ( $sources ) = @_;
551              
552 0           my $default_index;
553              
554 0           foreach my $index ( 0 .. $#{$sources} ) {
  0            
555 0 0         if ( $sources->[$index]{'name'} eq 'Defaults' ) {
556 0           $default_index = $index;
557 0           last;
558             }
559             }
560              
561 0 0         return $sources unless defined $default_index;
562              
563 0           my $should_remove = 0;
564              
565 0           configure_parser( 'no_auto_abbrev', 'pass_through' );
566              
567 0           foreach my $index ( $default_index + 1 .. $#{$sources} ) {
  0            
568 0           my $args = $sources->[$index]->{contents};
569              
570 0 0         if (ref($args)) {
571 0           Getopt::Long::GetOptionsFromArray( $args,
572             'ignore-ack-defaults' => \$should_remove,
573             );
574             }
575             else {
576 0           ( undef, $sources->[$index]{contents} ) = Getopt::Long::GetOptionsFromString( $args,
577             'ignore-ack-defaults' => \$should_remove,
578             );
579             }
580             }
581              
582 0 0         return $sources unless $should_remove;
583              
584 0           my @copy = @{$sources};
  0            
585 0           splice @copy, $default_index, 1;
586 0           return \@copy;
587             }
588              
589              
590             sub process_args {
591 0     0 0   my $arg_sources = \@_;
592              
593             my %opt = (
594             pager => $ENV{ACK_PAGER_COLOR} || $ENV{ACK_PAGER},
595 0   0       );
596              
597 0           $arg_sources = _remove_default_options_if_needed($arg_sources);
598              
599             # Check for --dump early.
600 0           foreach my $source (@{$arg_sources}) {
  0            
601 0 0         if ( $source->{name} eq 'ARGV' ) {
602 0           my $dump;
603 0           configure_parser( 'pass_through' );
604             Getopt::Long::GetOptionsFromArray( $source->{contents},
605 0           'dump' => \$dump,
606             );
607 0 0         if ( $dump ) {
608 0           _dump_options($arg_sources);
609 0           exit(0);
610             }
611             }
612             }
613              
614 0           my $type_specs = _process_filetypes(\%opt, $arg_sources);
615              
616 0           _check_for_mutex_options( $type_specs );
617              
618 0           _process_other(\%opt, $type_specs, $arg_sources);
619 0           while ( @{$arg_sources} ) {
  0            
620 0           my $source = shift @{$arg_sources};
  0            
621 0           my $args = $source->{contents};
622              
623             # All of our sources should be transformed into an array ref
624 0 0         if ( ref($args) ) {
625 0           my $source_name = $source->{name};
626 0 0         if ( $source_name eq 'ARGV' ) {
    0          
627 0           @ARGV = @{$args};
  0            
628             }
629 0           elsif (@{$args}) {
630 0           App::Ack::die( "Source '$source_name' has extra arguments!" );
631             }
632             }
633             else {
634 0           App::Ack::die( 'The impossible has occurred!' );
635             }
636             }
637 0   0       my $filters = ($opt{filters} ||= []);
638              
639             # Throw the default filter in if no others are selected.
640 0 0         if ( not grep { !$_->is_inverted() } @{$filters} ) {
  0            
  0            
641 0           push @{$filters}, App::Ack::Filter::Default->new();
  0            
642             }
643 0           return \%opt;
644             }
645              
646              
647             sub retrieve_arg_sources {
648 0     0 0   my @arg_sources;
649              
650             my $noenv;
651 0           my $ackrc;
652              
653 0           configure_parser( 'no_auto_abbrev', 'pass_through' );
654 0           Getopt::Long::GetOptions(
655             'noenv' => \$noenv,
656             'ackrc=s' => \$ackrc,
657             );
658              
659 0           my @files;
660              
661 0 0         if ( !$noenv ) {
662 0           my $finder = App::Ack::ConfigFinder->new;
663 0           @files = $finder->find_config_files;
664             }
665 0 0         if ( $ackrc ) {
666             # We explicitly use open so we get a nice error message.
667             # XXX This is a potential race condition!.
668 0 0         if ( open my $fh, '<', $ackrc ) {
669 0           close $fh;
670             }
671             else {
672 0           App::Ack::die( "Unable to load ackrc '$ackrc': $!" );
673             }
674 0           push( @files, { path => $ackrc } );
675             }
676              
677 0           push @arg_sources, {
678             name => 'Defaults',
679             contents => [ App::Ack::ConfigDefault::options_clean() ],
680             };
681              
682 0           foreach my $file ( @files) {
683 0           my @lines = read_rcfile($file->{path});
684 0 0         if ( @lines ) {
685             push @arg_sources, {
686             name => $file->{path},
687             contents => \@lines,
688             project => $file->{project},
689 0           is_ackrc => 1,
690             };
691             }
692             }
693              
694 0           push @arg_sources, {
695             name => 'ARGV',
696             contents => [ @ARGV ],
697             };
698              
699 0           return @arg_sources;
700             }
701              
702              
703             sub read_rcfile {
704 0     0 0   my $file = shift;
705              
706 0 0 0       return unless defined $file && -e $file;
707              
708 0           my @lines;
709              
710 0 0         open( my $fh, '<', $file ) or App::Ack::die( "Unable to read $file: $!" );
711 0           while ( defined( my $line = <$fh> ) ) {
712 0           chomp $line;
713 0           $line =~ s/^\s+//;
714 0           $line =~ s/\s+$//;
715              
716 0 0         next if $line eq '';
717 0 0         next if $line =~ /^\s*#/;
718              
719 0           push( @lines, $line );
720             }
721 0 0         close $fh or App::Ack::die( "Unable to close $file: $!" );
722              
723 0           return @lines;
724             }
725              
726              
727             # Verifies no mutex options were passed. Dies if they were.
728             sub _check_for_mutex_options {
729 0     0     my $type_specs = shift;
730              
731 0           my $mutex = mutex_options();
732              
733 0           my ($raw,$used) = _options_used( $type_specs );
734              
735 0           my @used = sort { lc $a cmp lc $b } keys %{$used};
  0            
  0            
736              
737 0           for my $i ( @used ) {
738 0           for my $j ( @used ) {
739 0 0         next if $i eq $j;
740 0 0         if ( $mutex->{$i}{$j} ) {
741 0           my $x = $raw->[ $used->{$i} ];
742 0           my $y = $raw->[ $used->{$j} ];
743 0           App::Ack::die( "Options '$x' and '$y' can't be used together." );
744             }
745             }
746             }
747              
748 0           return;
749             }
750              
751              
752             # Processes the command line option and returns a hash of the options that were
753             # used on the command line, using their full name. "--prox" shows up in the hash as "--proximate".
754             sub _options_used {
755 0     0     my $type_specs = shift;
756              
757 0           my %dummy_opt;
758 0           my $real_spec = get_arg_spec( \%dummy_opt, $type_specs );
759              
760             # The real argument parsing doesn't check for --type-add, --type-del or --type-set because
761             # they get removed by the argument processing. We have to account for them here.
762 0     0     my $sub_dummy = sub {};
763             $real_spec = {
764 0           %{$real_spec},
  0            
765             'type-add=s' => $sub_dummy,
766             'type-del=s' => $sub_dummy,
767             'type-set=s' => $sub_dummy,
768             'ignore-ack-defaults' => $sub_dummy,
769             };
770              
771 0           my %parsed;
772             my @raw;
773 0           my %spec_capture_parsed;
774 0           my %spec_capture_raw;
775              
776             =pod
777              
778             We have to build two argument specs.
779              
780             To populate the C<%parsed> hash: Capture the arguments that the user has
781             passed in, as parsed by the Getopt::Long::GetOptions function. Aliases are converted
782             down to their short options. If a user passes "--proximate", Getopt::Long
783             converts that to "-p" and we store it as "-p".
784              
785             To populate the C<@raw> array: Capture the arguments raw, without having
786             been converted to their short options. If a user passes "--proximate",
787             we store it in C<@raw> as "--proximate".
788              
789             =cut
790              
791             # Capture the %parsed hash.
792             CAPTURE_PARSED: {
793 0           my $parsed_pos = 0;
  0            
794             my $sub_count = sub {
795 0     0     my $arg = shift;
796 0           $arg = "$arg";
797 0           $parsed{$arg} = $parsed_pos++;
798 0           };
799             %spec_capture_parsed = (
800 0     0     '<>' => sub { $parsed_pos++ }, # Bump forward one pos for non-options.
801 0           map { $_ => $sub_count } keys %{$real_spec}
  0            
  0            
802             );
803             }
804              
805             # Capture the @raw array.
806             CAPTURE_RAW: {
807 0           my $raw_pos = 0;
  0            
808             %spec_capture_raw = (
809 0     0     '<>' => sub { $raw_pos++ }, # Bump forward one pos for non-options.
810 0           );
811              
812             my $sub_count = sub {
813 0     0     my $arg = shift;
814              
815 0           $arg = "$arg";
816 0 0         $raw[$raw_pos] = length($arg) == 1 ? "-$arg" : "--$arg";
817 0           $raw_pos++;
818 0           };
819              
820 0           for my $opt_spec ( keys %{$real_spec} ) {
  0            
821 0           my $negatable;
822             my $type;
823 0           my $default;
824              
825 0           $negatable = ($opt_spec =~ s/!$//);
826              
827 0 0         if ( $opt_spec =~ s/(=[si])$// ) {
828 0           $type = $1;
829             }
830 0 0         if ( $opt_spec =~ s/(:.+)$// ) {
831 0           $default = $1;
832             }
833              
834 0           my @aliases = split( /\|/, $opt_spec );
835 0           for my $alias ( @aliases ) {
836 0 0         $alias .= $type if defined $type;
837 0 0         $alias .= $default if defined $default;
838 0 0         $alias .= '!' if $negatable;
839              
840 0           $spec_capture_raw{$alias} = $sub_count;
841             }
842             }
843             }
844              
845             # Parse @ARGV twice, once with each capture spec.
846 0           configure_parser( 'pass_through' ); # Ignore invalid options.
847 0           Getopt::Long::GetOptionsFromArray( [@ARGV], %spec_capture_raw );
848 0           Getopt::Long::GetOptionsFromArray( [@ARGV], %spec_capture_parsed );
849              
850 0           return (\@raw,\%parsed);
851             }
852              
853              
854             sub mutex_options {
855             # This list is machine-generated by dev/crank-mutex. Do not modify it by hand.
856              
857             return {
858 0     0 0   1 => {
859             m => 1,
860             passthru => 1,
861             },
862             A => {
863             L => 1,
864             c => 1,
865             f => 1,
866             g => 1,
867             l => 1,
868             o => 1,
869             output => 1,
870             p => 1,
871             passthru => 1,
872             },
873             B => {
874             L => 1,
875             c => 1,
876             f => 1,
877             g => 1,
878             l => 1,
879             o => 1,
880             output => 1,
881             p => 1,
882             passthru => 1,
883             },
884             C => {
885             L => 1,
886             c => 1,
887             f => 1,
888             g => 1,
889             l => 1,
890             o => 1,
891             output => 1,
892             p => 1,
893             passthru => 1,
894             },
895             H => {
896             L => 1,
897             f => 1,
898             g => 1,
899             l => 1,
900             },
901             I => {
902             f => 1,
903             },
904             L => {
905             A => 1,
906             B => 1,
907             C => 1,
908             H => 1,
909             L => 1,
910             break => 1,
911             c => 1,
912             column => 1,
913             f => 1,
914             g => 1,
915             group => 1,
916             h => 1,
917             heading => 1,
918             l => 1,
919             'no-filename' => 1,
920             o => 1,
921             output => 1,
922             p => 1,
923             passthru => 1,
924             'show-types' => 1,
925             v => 1,
926             'with-filename' => 1,
927             },
928             break => {
929             L => 1,
930             c => 1,
931             f => 1,
932             g => 1,
933             l => 1,
934             },
935             c => {
936             A => 1,
937             B => 1,
938             C => 1,
939             L => 1,
940             break => 1,
941             column => 1,
942             f => 1,
943             g => 1,
944             group => 1,
945             heading => 1,
946             m => 1,
947             o => 1,
948             output => 1,
949             p => 1,
950             passthru => 1,
951             },
952             column => {
953             L => 1,
954             c => 1,
955             f => 1,
956             g => 1,
957             l => 1,
958             o => 1,
959             output => 1,
960             passthru => 1,
961             v => 1,
962             },
963             f => {
964             A => 1,
965             B => 1,
966             C => 1,
967             H => 1,
968             I => 1,
969             L => 1,
970             break => 1,
971             c => 1,
972             column => 1,
973             f => 1,
974             'files-from' => 1,
975             g => 1,
976             group => 1,
977             h => 1,
978             heading => 1,
979             i => 1,
980             l => 1,
981             m => 1,
982             match => 1,
983             o => 1,
984             output => 1,
985             p => 1,
986             passthru => 1,
987             'smart-case' => 1,
988             u => 1,
989             v => 1,
990             x => 1,
991             },
992             'files-from' => {
993             f => 1,
994             g => 1,
995             x => 1,
996             },
997             g => {
998             A => 1,
999             B => 1,
1000             C => 1,
1001             H => 1,
1002             L => 1,
1003             break => 1,
1004             c => 1,
1005             column => 1,
1006             f => 1,
1007             'files-from' => 1,
1008             g => 1,
1009             group => 1,
1010             h => 1,
1011             heading => 1,
1012             l => 1,
1013             m => 1,
1014             match => 1,
1015             o => 1,
1016             output => 1,
1017             p => 1,
1018             passthru => 1,
1019             u => 1,
1020             x => 1,
1021             },
1022             group => {
1023             L => 1,
1024             c => 1,
1025             f => 1,
1026             g => 1,
1027             l => 1,
1028             },
1029             h => {
1030             L => 1,
1031             f => 1,
1032             g => 1,
1033             l => 1,
1034             },
1035             heading => {
1036             L => 1,
1037             c => 1,
1038             f => 1,
1039             g => 1,
1040             l => 1,
1041             },
1042             i => {
1043             f => 1,
1044             },
1045             l => {
1046             A => 1,
1047             B => 1,
1048             C => 1,
1049             H => 1,
1050             L => 1,
1051             break => 1,
1052             column => 1,
1053             f => 1,
1054             g => 1,
1055             group => 1,
1056             h => 1,
1057             heading => 1,
1058             l => 1,
1059             'no-filename' => 1,
1060             o => 1,
1061             output => 1,
1062             p => 1,
1063             passthru => 1,
1064             'show-types' => 1,
1065             'with-filename' => 1,
1066             },
1067             m => {
1068             1 => 1,
1069             c => 1,
1070             f => 1,
1071             g => 1,
1072             passthru => 1,
1073             },
1074             match => {
1075             f => 1,
1076             g => 1,
1077             },
1078             'no-filename' => {
1079             L => 1,
1080             l => 1,
1081             },
1082             o => {
1083             A => 1,
1084             B => 1,
1085             C => 1,
1086             L => 1,
1087             c => 1,
1088             column => 1,
1089             f => 1,
1090             g => 1,
1091             l => 1,
1092             o => 1,
1093             output => 1,
1094             p => 1,
1095             passthru => 1,
1096             'show-types' => 1,
1097             v => 1,
1098             },
1099             output => {
1100             A => 1,
1101             B => 1,
1102             C => 1,
1103             L => 1,
1104             c => 1,
1105             column => 1,
1106             f => 1,
1107             g => 1,
1108             l => 1,
1109             o => 1,
1110             output => 1,
1111             p => 1,
1112             passthru => 1,
1113             'show-types' => 1,
1114             u => 1,
1115             v => 1,
1116             },
1117             p => {
1118             A => 1,
1119             B => 1,
1120             C => 1,
1121             L => 1,
1122             c => 1,
1123             f => 1,
1124             g => 1,
1125             l => 1,
1126             o => 1,
1127             output => 1,
1128             p => 1,
1129             passthru => 1,
1130             },
1131             passthru => {
1132             1 => 1,
1133             A => 1,
1134             B => 1,
1135             C => 1,
1136             L => 1,
1137             c => 1,
1138             column => 1,
1139             f => 1,
1140             g => 1,
1141             l => 1,
1142             m => 1,
1143             o => 1,
1144             output => 1,
1145             p => 1,
1146             v => 1,
1147             },
1148             'show-types' => {
1149             L => 1,
1150             l => 1,
1151             o => 1,
1152             output => 1,
1153             },
1154             'smart-case' => {
1155             f => 1,
1156             },
1157             u => {
1158             f => 1,
1159             g => 1,
1160             output => 1,
1161             },
1162             v => {
1163             L => 1,
1164             column => 1,
1165             f => 1,
1166             o => 1,
1167             output => 1,
1168             passthru => 1,
1169             },
1170             'with-filename' => {
1171             L => 1,
1172             l => 1,
1173             },
1174             x => {
1175             f => 1,
1176             'files-from' => 1,
1177             g => 1,
1178             },
1179             };
1180              
1181             } # End of mutex_options()
1182              
1183              
1184             1; # End of App::Ack::ConfigLoader