diff options
| author | Jesse Morgan <jesse@jesterpm.net> | 2018-04-12 16:29:32 -0700 | 
|---|---|---|
| committer | Jesse Morgan <jesse@jesterpm.net> | 2018-04-12 16:29:32 -0700 | 
| commit | af87be48b6cc85aaf142725acef73c8440bd52a6 (patch) | |
| tree | b0a0277b3ee6c4e42dcb55d0cbd0dfb3e9df3390 /ack | |
| parent | 0c6bd095dda2b8e009df0f4564ba2131450013a7 (diff) | |
Add new scripts from laptop
Diffstat (limited to 'ack')
| -rwxr-xr-x | ack | 5346 | 
1 files changed, 5346 insertions, 0 deletions
| @@ -0,0 +1,5346 @@ +#!/usr/bin/env perl +# +# This file, ack, is generated code. +# Please DO NOT EDIT or send patches for it. +# +# Please take a look at the source from +# https://github.com/beyondgrep/ack2 +# and submit patches against the individual files +# that build ack. +# + +package main; + +use strict; +use warnings; +our $VERSION = '2.22'; # Check https://beyondgrep.com/ for updates + +use 5.008008; +use Getopt::Long 2.38 (); +use Carp 1.04 (); + +use File::Spec (); + + +# XXX Don't make this so brute force +# See also: https://github.com/beyondgrep/ack2/issues/89 + +our $opt_after_context; +our $opt_before_context; +our $opt_output; +our $opt_print0; +our $opt_color; +our $opt_heading; +our $opt_show_filename; +our $opt_regex; +our $opt_break; +our $opt_count; +our $opt_v; +our $opt_m; +our $opt_g; +our $opt_f; +our $opt_lines; +our $opt_L; +our $opt_l; +our $opt_passthru; +our $opt_column; + +# Flag if we need any context tracking. +our $is_tracking_context; + +# These are all our globals. + +MAIN: { +    $App::Ack::orig_program_name = $0; +    $0 = join(' ', 'ack', $0); +    if ( $App::Ack::VERSION ne $main::VERSION ) { +        App::Ack::die( "Program/library version mismatch\n\t$0 is $main::VERSION\n\t$INC{'App/Ack.pm'} is $App::Ack::VERSION" ); +    } + +    # Do preliminary arg checking; +    my $env_is_usable = 1; +    for my $arg ( @ARGV ) { +        last if ( $arg eq '--' ); + +        # Get the --thpppt, --bar, --cathy checking out of the way. +        $arg =~ /^--th[pt]+t+$/ and App::Ack::thpppt($arg); +        $arg eq '--bar'         and App::Ack::ackbar(); +        $arg eq '--cathy'       and App::Ack::cathy(); + +        # See if we want to ignore the environment. (Don't tell Al Gore.) +        $arg eq '--env'         and $env_is_usable = 1; +        $arg eq '--noenv'       and $env_is_usable = 0; +    } + +    if ( !$env_is_usable ) { +        my @keys = ( 'ACKRC', grep { /^ACK_/ } keys %ENV ); +        delete @ENV{@keys}; +    } +    load_colors(); + +    Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); +    Getopt::Long::Configure('pass_through', 'no_auto_abbrev'); +    Getopt::Long::GetOptions( +        'help'       => sub { App::Ack::show_help(); exit; }, +        'version'    => sub { App::Ack::print_version_statement(); exit; }, +        'man'        => sub { App::Ack::show_man(); exit; }, +    ); +    Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); + +    if ( !@ARGV ) { +        App::Ack::show_help(); +        exit 1; +    } + +    main(); +} + +sub _compile_descend_filter { +    my ( $opt ) = @_; + +    my $idirs = 0; +    my $dont_ignore_dirs = 0; + +    for my $filter (@{$opt->{idirs} || []}) { +        if ($filter->is_inverted()) { +            $dont_ignore_dirs++; +        } +        else { +            $idirs++; +        } +    } + +    # If we have one or more --noignore-dir directives, we can't ignore +    # entire subdirectory hierarchies, so we return an "accept all" +    # filter and scrutinize the files more in _compile_file_filter. +    return if $dont_ignore_dirs; +    return unless $idirs; + +    $idirs = $opt->{idirs}; + +    return sub { +        my $resource = App::Ack::Resource->new($File::Next::dir); +        return !grep { $_->filter($resource) } @{$idirs}; +    }; +} + +sub _compile_file_filter { +    my ( $opt, $start ) = @_; + +    my $ifiles_filters = $opt->{ifiles}; + +    my $filters         = $opt->{'filters'} || []; +    my $direct_filters = App::Ack::Filter::Collection->new(); +    my $inverse_filters = App::Ack::Filter::Collection->new(); + +    foreach my $filter (@{$filters}) { +        if ($filter->is_inverted()) { +            # We want to check if files match the uninverted filters +            $inverse_filters->add($filter->invert()); +        } +        else { +            $direct_filters->add($filter); +        } +    } + +    my %is_member_of_starting_set = map { (get_file_id($_) => 1) } @{$start}; + +    my @ignore_dir_filter = @{$opt->{idirs} || []}; +    my @is_inverted       = map { $_->is_inverted() } @ignore_dir_filter; +    # This depends on InverseFilter->invert returning the original filter (for optimization). +    @ignore_dir_filter         = map { $_->is_inverted() ? $_->invert() : $_ } @ignore_dir_filter; +    my $dont_ignore_dir_filter = grep { $_ } @is_inverted; +    my $previous_dir = ''; +    my $previous_dir_ignore_result; + +    return sub { +        if ( $opt_g ) { +            if ( $File::Next::name =~ /$opt_regex/ && $opt_v ) { +                return 0; +            } +            if ( $File::Next::name !~ /$opt_regex/ && !$opt_v ) { +                return 0; +            } +        } +        # ack always selects files that are specified on the command +        # line, regardless of filetype.  If you want to ack a JPEG, +        # and say "ack foo whatever.jpg" it will do it for you. +        return 1 if $is_member_of_starting_set{ get_file_id($File::Next::name) }; + +        if ( $dont_ignore_dir_filter ) { +            if ( $previous_dir eq $File::Next::dir ) { +                if ( $previous_dir_ignore_result ) { +                    return 0; +                } +            } +            else { +                my @dirs = File::Spec->splitdir($File::Next::dir); + +                my $is_ignoring = 0; + +                for ( my $i = 0; $i < @dirs; $i++) { +                    my $dir_rsrc = App::Ack::Resource->new(File::Spec->catfile(@dirs[0 .. $i])); + +                    my $j = 0; +                    for my $filter (@ignore_dir_filter) { +                        if ( $filter->filter($dir_rsrc) ) { +                            $is_ignoring = !$is_inverted[$j]; +                        } +                        $j++; +                    } +                } + +                $previous_dir               = $File::Next::dir; +                $previous_dir_ignore_result = $is_ignoring; + +                if ( $is_ignoring ) { +                    return 0; +                } +            } +        } + +        # Ignore named pipes found in directory searching.  Named +        # pipes created by subprocesses get specified on the command +        # line, so the rule of "always select whatever is on the +        # command line" wins. +        return 0 if -p $File::Next::name; + +        # We can't handle unreadable filenames; report them. +        if ( not -r _ ) { +            use filetest 'access'; + +            if ( not -R $File::Next::name ) { +                if ( $App::Ack::report_bad_filenames ) { +                    App::Ack::warn( "${File::Next::name}: cannot open file for reading" ); +                } +                return 0; +            } +        } + +        my $resource = App::Ack::Resource->new($File::Next::name); + +        if ( $ifiles_filters && $ifiles_filters->filter($resource) ) { +            return 0; +        } + +        my $match_found = $direct_filters->filter($resource); + +        # Don't bother invoking inverse filters unless we consider the current resource a match +        if ( $match_found && $inverse_filters->filter( $resource ) ) { +            $match_found = 0; +        } +        return $match_found; +    }; +} + +sub show_types { +    my $resource = shift; +    my $ors      = shift; + +    my @types = filetypes( $resource ); +    my $types = join( ',', @types ); +    my $arrow = @types ? ' => ' : ' =>'; +    App::Ack::print( $resource->name, $arrow, join( ',', @types ), $ors ); + +    return; +} + +# Set default colors, load Term::ANSIColor +sub load_colors { +    eval 'use Term::ANSIColor 1.10 ()'; +    eval 'use Win32::Console::ANSI' if $App::Ack::is_windows; + +    $ENV{ACK_COLOR_MATCH}    ||= 'black on_yellow'; +    $ENV{ACK_COLOR_FILENAME} ||= 'bold green'; +    $ENV{ACK_COLOR_LINENO}   ||= 'bold yellow'; + +    return; +} + +sub filetypes { +    my ( $resource ) = @_; + +    my @matches; + +    foreach my $k (keys %App::Ack::mappings) { +        my $filters = $App::Ack::mappings{$k}; + +        foreach my $filter (@{$filters}) { +            # Clone the resource. +            my $clone = $resource->clone; +            if ( $filter->filter($clone) ) { +                push @matches, $k; +                last; +            } +        } +    } + +    # http://search.cpan.org/dist/Perl-Critic/lib/Perl/Critic/Policy/Subroutines/ProhibitReturnSort.pm +    @matches = sort @matches; +    return @matches; +} + +# Returns a (fairly) unique identifier for a file. +# Use this function to compare two files to see if they're +# equal (ie. the same file, but with a different path/links/etc). +sub get_file_id { +    my ( $filename ) = @_; + +    if ( $App::Ack::is_windows ) { +        return File::Next::reslash( $filename ); +    } +    else { +        # XXX Is this the best method? It always hits the FS. +        if( my ( $dev, $inode ) = (stat($filename))[0, 1] ) { +            return join(':', $dev, $inode); +        } +        else { +            # XXX This could be better. +            return $filename; +        } +    } +} + +# Returns a regex object based on a string and command-line options. +# Dies when the regex $str is undefined (i.e. not given on command line). + +sub build_regex { +    my $str = shift; +    my $opt = shift; + +    defined $str or App::Ack::die( 'No regular expression found.' ); + +    $str = quotemeta( $str ) if $opt->{Q}; +    if ( $opt->{w} ) { +        my $pristine_str = $str; + +        $str = "(?:$str)"; +        $str = "\\b$str" if $pristine_str =~ /^\w/; +        $str = "$str\\b" if $pristine_str =~ /\w$/; +    } + +    my $regex_is_lc = $str eq lc $str; +    if ( $opt->{i} || ($opt->{smart_case} && $regex_is_lc) ) { +        $str = "(?i)$str"; +    } + +    my $re = eval { qr/$str/m }; +    if ( !$re ) { +        die "Invalid regex '$str':\n  $@"; +    } + +    return $re; + +} + +my $match_column_number; + +{ + +# Number of context lines +my $n_before_ctx_lines; +my $n_after_ctx_lines; + +# Array to keep track of lines that might be required for a "before" context +my @before_context_buf; +# Position to insert next line in @before_context_buf +my $before_context_pos; + +# Number of "after" context lines still pending +my $after_context_pending; + +# Number of latest line that got printed +my $printed_line_no; + +my $is_iterating; + +my $is_first_match; +my $has_printed_something; + +BEGIN { +    $has_printed_something = 0; +} + +# Set up context tracking variables. +sub set_up_line_context { +    $n_before_ctx_lines = $opt_output ? 0 : ($opt_before_context || 0); +    $n_after_ctx_lines  = $opt_output ? 0 : ($opt_after_context || 0); + +    @before_context_buf = (undef) x $n_before_ctx_lines; +    $before_context_pos = 0; + +    $is_tracking_context = $n_before_ctx_lines || $n_after_ctx_lines; + +    $is_first_match = 1; + +    return; +} + +# Adjust context tracking variables when entering a new file. +sub set_up_line_context_for_file { +    $printed_line_no = 0; +    $after_context_pending = 0; +    if ( $opt_heading && !$opt_lines ) { +        $is_first_match = 1; +    } + +    return; +} + +=begin Developers + +This subroutine jumps through a number of optimization hoops to +try to be fast in the more common use cases of ack.  For one thing, +in non-context tracking searches (not using -A, -B, or -C), +conditions that normally would be checked inside the loop happen +outside, resulting in three nearly identical loops for -v, --passthru, +and normal searching.  Any changes that happen to one should propagate +to the others if they make sense.  The non-context branches also inline +does_match for performance reasons; any relevant changes that happen here +must also happen there. + +=end Developers + +=cut + +sub print_matches_in_resource { +    my ( $resource ) = @_; + +    my $max_count      = $opt_m || -1; +    my $nmatches       = 0; +    my $filename       = $resource->name; +    my $ors            = $opt_print0 ? "\0" : "\n"; + +    my $has_printed_for_this_resource = 0; + +    $is_iterating = 1; + +    my $fh = $resource->open(); +    if ( !$fh ) { +        if ( $App::Ack::report_bad_filenames ) { +            App::Ack::warn( "$filename: $!" ); +        } +        return 0; +    } + +    my $display_filename = $filename; +    if ( $opt_show_filename && $opt_heading && $opt_color ) { +        $display_filename = Term::ANSIColor::colored($display_filename, $ENV{ACK_COLOR_FILENAME}); +    } + +    # Check for context before the main loop, so we don't pay for it if we don't need it. +    if ( $is_tracking_context ) { +        $after_context_pending = 0; +        while ( <$fh> ) { +            if ( does_match( $_ ) && $max_count ) { +                if ( !$has_printed_for_this_resource ) { +                    if ( $opt_break && $has_printed_something ) { +                        App::Ack::print_blank_line(); +                    } +                    if ( $opt_show_filename && $opt_heading ) { +                        App::Ack::print_filename( $display_filename, $ors ); +                    } +                } +                print_line_with_context( $filename, $_, $. ); +                $has_printed_for_this_resource = 1; +                $nmatches++; +                $max_count--; +            } +            elsif ( $opt_passthru ) { +                chomp; # XXX Proper newline handling? +                # XXX Inline this call? +                if ( $opt_break && !$has_printed_for_this_resource && $has_printed_something ) { +                    App::Ack::print_blank_line(); +                } +                print_line_with_options( $filename, $_, $., ':' ); +                $has_printed_for_this_resource = 1; +            } +            else { +                chomp; # XXX Proper newline handling? +                print_line_if_context( $filename, $_, $., '-' ); +            } + +            last if ($max_count == 0) && ($after_context_pending == 0); +        } +    } +    else { +        if ( $opt_passthru ) { +            local $_; + +            while ( <$fh> ) { +                $match_column_number = undef; +                if ( $opt_v ? !/$opt_regex/o : /$opt_regex/o ) { +                    if ( !$opt_v ) { +                        $match_column_number = $-[0] + 1; +                    } +                    if ( !$has_printed_for_this_resource ) { +                        if ( $opt_break && $has_printed_something ) { +                            App::Ack::print_blank_line(); +                        } +                        if ( $opt_show_filename && $opt_heading ) { +                            App::Ack::print_filename( $display_filename, $ors ); +                        } +                    } +                    print_line_with_context( $filename, $_, $. ); +                    $has_printed_for_this_resource = 1; +                    $nmatches++; +                    $max_count--; +                } +                else { +                    chomp; # XXX proper newline handling? +                    if ( $opt_break && !$has_printed_for_this_resource && $has_printed_something ) { +                        App::Ack::print_blank_line(); +                    } +                    print_line_with_options( $filename, $_, $., ':' ); +                    $has_printed_for_this_resource = 1; +                } +                last unless $max_count != 0; +            } +        } +        elsif ( $opt_v ) { +            local $_; + +            $match_column_number = undef; +            while ( <$fh> ) { +                if ( !/$opt_regex/o ) { +                    if ( !$has_printed_for_this_resource ) { +                        if ( $opt_break && $has_printed_something ) { +                            App::Ack::print_blank_line(); +                        } +                        if ( $opt_show_filename && $opt_heading ) { +                            App::Ack::print_filename( $display_filename, $ors ); +                        } +                    } +                    print_line_with_context( $filename, $_, $. ); +                    $has_printed_for_this_resource = 1; +                    $nmatches++; +                    $max_count--; +                } +                last unless $max_count != 0; +            } +        } +        else { +            local $_; + +            while ( <$fh> ) { +                $match_column_number = undef; +                if ( /$opt_regex/o ) { +                    $match_column_number = $-[0] + 1; +                    if ( !$has_printed_for_this_resource ) { +                        if ( $opt_break && $has_printed_something ) { +                            App::Ack::print_blank_line(); +                        } +                        if ( $opt_show_filename && $opt_heading ) { +                            App::Ack::print_filename( $display_filename, $ors ); +                        } +                    } +                    s/[\r\n]+$//g; +                    print_line_with_options( $filename, $_, $., ':' ); +                    $has_printed_for_this_resource = 1; +                    $nmatches++; +                    $max_count--; +                } +                last unless $max_count != 0; +            } +        } + +    } + +    $is_iterating = 0; + +    return $nmatches; +} + +sub print_line_with_options { +    my ( $filename, $line, $line_no, $separator ) = @_; + +    $has_printed_something = 1; +    $printed_line_no = $line_no; + +    my $ors = $opt_print0 ? "\0" : "\n"; + +    my @line_parts; + +    if( $opt_color ) { +        $filename = Term::ANSIColor::colored($filename, +            $ENV{ACK_COLOR_FILENAME}); +        $line_no  = Term::ANSIColor::colored($line_no, +            $ENV{ACK_COLOR_LINENO}); +    } + +    if($opt_show_filename) { +        if( $opt_heading ) { +            push @line_parts, $line_no; +        } +        else { +            push @line_parts, $filename, $line_no; +        } + +        if( $opt_column ) { +            push @line_parts, get_match_column(); +        } +    } +    if( $opt_output ) { +        while ( $line =~ /$opt_regex/og ) { +            # XXX We need to stop using eval() for --output.  See https://github.com/beyondgrep/ack2/issues/421 +            my $output = eval $opt_output; +            App::Ack::print( join( $separator, @line_parts, $output ), $ors ); +        } +    } +    else { +        if ( $opt_color ) { +            # This match is redundant, but we need to perfom it in order to get if capture groups are set. +            $line =~ /$opt_regex/o; + +            if ( @+ > 1 ) { # If we have captures... +                while ( $line =~ /$opt_regex/og ) { +                    my $offset = 0; # Additional offset for when we add stuff. +                    my $previous_match_end = 0; + +                    last if $-[0] == $+[0]; + +                    for ( my $i = 1; $i < @+; $i++ ) { +                        my ( $match_start, $match_end ) = ( $-[$i], $+[$i] ); + +                        next unless defined($match_start); +                        next if $match_start < $previous_match_end; + +                        my $substring = substr( $line, +                            $offset + $match_start, $match_end - $match_start ); +                        my $substitution = Term::ANSIColor::colored( $substring, +                            $ENV{ACK_COLOR_MATCH} ); + +                        substr( $line, $offset + $match_start, +                            $match_end - $match_start, $substitution ); + +                        $previous_match_end  = $match_end; # Offsets do not need to be applied. +                        $offset             += length( $substitution ) - length( $substring ); +                    } + +                    pos($line) = $+[0] + $offset; +                } +            } +            else { +                my $matched = 0; # If matched, need to escape afterwards. + +                while ( $line =~ /$opt_regex/og ) { + +                    $matched = 1; +                    my ( $match_start, $match_end ) = ($-[0], $+[0]); +                    next unless defined($match_start); +                    last if $match_start == $match_end; + +                    my $substring = substr( $line, $match_start, +                        $match_end - $match_start ); +                    my $substitution = Term::ANSIColor::colored( $substring, +                        $ENV{ACK_COLOR_MATCH} ); + +                    substr( $line, $match_start, $match_end - $match_start, +                        $substitution ); + +                    pos($line) = $match_end + +                    (length( $substitution ) - length( $substring )); +                } +                # XXX Why do we do this? +                $line .= "\033[0m\033[K" if $matched; +            } +        } + +        push @line_parts, $line; +        App::Ack::print( join( $separator, @line_parts ), $ors ); +    } + +    return; +} + +sub iterate { +    my ( $resource, $cb ) = @_; + +    $is_iterating = 1; + +    my $fh = $resource->open(); +    if ( !$fh ) { +        if ( $App::Ack::report_bad_filenames ) { +            App::Ack::warn( $resource->name . ': ' . $! ); +        } +        return; +    } + +    # Check for context before the main loop, so we don't pay for it if we don't need it. +    if ( $is_tracking_context ) { +        $after_context_pending = 0; + +        while ( <$fh> ) { +            last unless $cb->(); +        } +    } +    else { +        local $_; + +        while ( <$fh> ) { +            last unless $cb->(); +        } +    } + +    $is_iterating = 0; + +    return; +} + +sub print_line_with_context { +    my ( $filename, $matching_line, $line_no ) = @_; + +    my $ors                 = $opt_print0 ? "\0" : "\n"; +    my $is_tracking_context = $opt_after_context || $opt_before_context; + +    $matching_line =~ s/[\r\n]+$//g; + +    # Check if we need to print context lines first. +    if ( $is_tracking_context ) { +        my $before_unprinted = $line_no - $printed_line_no - 1; +        if ( !$is_first_match && ( !$printed_line_no || $before_unprinted > $n_before_ctx_lines ) ) { +            App::Ack::print('--', $ors); +        } + +        # We want at most $n_before_ctx_lines of context. +        if ( $before_unprinted > $n_before_ctx_lines ) { +            $before_unprinted = $n_before_ctx_lines; +        } + +        while ( $before_unprinted > 0 ) { +            my $line = $before_context_buf[($before_context_pos - $before_unprinted + $n_before_ctx_lines) % $n_before_ctx_lines]; + +            chomp $line; + +            # Disable $opt->{column} since there are no matches in the context lines. +            local $opt_column = 0; + +            print_line_with_options( $filename, $line, $line_no-$before_unprinted, '-' ); +            $before_unprinted--; +        } +    } + +    print_line_with_options( $filename, $matching_line, $line_no, ':' ); + +    # We want to get the next $n_after_ctx_lines printed. +    $after_context_pending = $n_after_ctx_lines; + +    $is_first_match = 0; + +    return; +} + +# Print the line only if it's part of a context we need to display. +sub print_line_if_context { +    my ( $filename, $line, $line_no, $separator ) = @_; + +    if ( $after_context_pending ) { +        # Disable $opt_column since there are no matches in the context lines. +        local $opt_column = 0; +        print_line_with_options( $filename, $line, $line_no, $separator ); +        --$after_context_pending; +    } +    elsif ( $n_before_ctx_lines ) { +        # Save line for "before" context. +        $before_context_buf[$before_context_pos] = $_; +        $before_context_pos = ($before_context_pos+1) % $n_before_ctx_lines; +    } + +    return; +} + +} + +# does_match() MUST have an $opt_regex set. + +=begin Developers + +This subroutine is inlined a few places in print_matches_in_resource +for performance reasons, so any changes here must be copied there as +well. + +=end Developers + +=cut + +sub does_match { +    my ( $line ) = @_; + +    $match_column_number = undef; + +    if ( $opt_v ) { +        return ( $line !~ /$opt_regex/o ); +    } +    else { +        if ( $line =~ /$opt_regex/o ) { +            # @- = @LAST_MATCH_START +            # @+ = @LAST_MATCH_END +            $match_column_number = $-[0] + 1; +            return 1; +        } +        else { +            return; +        } +    } +} + +sub get_match_column { +    return $match_column_number; +} + +sub resource_has_match { +    my ( $resource ) = @_; + +    my $has_match = 0; +    my $fh = $resource->open(); +    if ( !$fh ) { +        if ( $App::Ack::report_bad_filenames ) { +            App::Ack::warn( $resource->name . ': ' . $! ); +        } +    } +    else { +        while ( <$fh> ) { +            if (/$opt_regex/o xor $opt_v) { +                $has_match = 1; +                last; +            } +        } +        close $fh; +    } + +    return $has_match; +} + +sub count_matches_in_resource { +    my ( $resource ) = @_; + +    my $nmatches = 0; +    my $fh = $resource->open(); +    if ( !$fh ) { +        if ( $App::Ack::report_bad_filenames ) { +            App::Ack::warn( $resource->name . ': ' . $! ); +        } +    } +    else { +        while ( <$fh> ) { +            ++$nmatches if (/$opt_regex/o xor $opt_v); +        } +        close $fh; +    } + +    return $nmatches; +} + +sub main { +    my @arg_sources = App::Ack::ConfigLoader::retrieve_arg_sources(); + +    my $opt = App::Ack::ConfigLoader::process_args( @arg_sources ); + +    $opt_after_context  = $opt->{after_context}; +    $opt_before_context = $opt->{before_context}; +    $opt_output         = $opt->{output}; +    $opt_print0         = $opt->{print0}; +    $opt_color          = $opt->{color}; +    $opt_heading        = $opt->{heading}; +    $opt_show_filename  = $opt->{show_filename}; +    $opt_regex          = $opt->{regex}; +    $opt_break          = $opt->{break}; +    $opt_count          = $opt->{count}; +    $opt_v              = $opt->{v}; +    $opt_m              = $opt->{m}; +    $opt_g              = $opt->{g}; +    $opt_f              = $opt->{f}; +    $opt_lines          = $opt->{lines}; +    $opt_L              = $opt->{L}; +    $opt_l              = $opt->{l}; +    $opt_passthru       = $opt->{passthru}; +    $opt_column         = $opt->{column}; + +    $App::Ack::report_bad_filenames = !$opt->{dont_report_bad_filenames}; + +    if ( $opt->{flush} ) { +        $| = 1; +    } + +    if ( !defined($opt_color) && !$opt_g ) { +        my $windows_color = 1; +        if ( $App::Ack::is_windows ) { +            $windows_color = eval { require Win32::Console::ANSI; }; +        } +        $opt_color = !App::Ack::output_to_pipe() && $windows_color; +    } +    if ( not defined $opt_heading and not defined $opt_break  ) { +        $opt_heading = $opt_break = $opt->{break} = !App::Ack::output_to_pipe(); +    } + +    if ( defined($opt->{H}) || defined($opt->{h}) ) { +        $opt_show_filename = $opt->{show_filename} = $opt->{H} && !$opt->{h}; +    } + +    if ( my $output = $opt_output ) { +        $output        =~ s{\\}{\\\\}g; +        $output        =~ s{"}{\\"}g; +        $opt_output = qq{"$output"}; +    } + +    my $resources; +    if ( $App::Ack::is_filter_mode && !$opt->{files_from} ) { # probably -x +        $resources    = App::Ack::Resources->from_stdin( $opt ); +        $opt_regex = shift @ARGV if not defined $opt_regex; +        $opt_regex = $opt->{regex} = build_regex( $opt_regex, $opt ); +    } +    else { +        if ( $opt_f || $opt_lines ) { +            if ( $opt_regex ) { +                App::Ack::warn( "regex ($opt_regex) specified with -f or --lines" ); +                App::Ack::exit_from_ack( 0 ); # XXX the 0 is misleading +            } +        } +        else { +            $opt_regex = shift @ARGV if not defined $opt_regex; +            $opt_regex = $opt->{regex} = build_regex( $opt_regex, $opt ); +        } +        if ( $opt_regex && $opt_regex =~ /\n/ ) { +            App::Ack::exit_from_ack( 0 ); +        } +        my @start; +        if ( not defined $opt->{files_from} ) { +            @start = @ARGV; +        } +        if ( !exists($opt->{show_filename}) ) { +            unless(@start == 1 && !(-d $start[0])) { +                $opt_show_filename = $opt->{show_filename} = 1; +            } +        } + +        if ( defined $opt->{files_from} ) { +            $resources = App::Ack::Resources->from_file( $opt, $opt->{files_from} ); +            exit 1 unless $resources; +        } +        else { +            @start = ('.') unless @start; +            foreach my $target (@start) { +                if ( !-e $target && $App::Ack::report_bad_filenames) { +                    App::Ack::warn( "$target: No such file or directory" ); +                } +            } + +            $opt->{file_filter}    = _compile_file_filter($opt, \@start); +            $opt->{descend_filter} = _compile_descend_filter($opt); + +            $resources = App::Ack::Resources->from_argv( $opt, \@start ); +        } +    } +    App::Ack::set_up_pager( $opt->{pager} ) if defined $opt->{pager}; + +    my $ors        = $opt_print0 ? "\0" : "\n"; +    my $only_first = $opt->{1}; + +    my $nmatches    = 0; +    my $total_count = 0; + +    set_up_line_context(); + +RESOURCES: +    while ( my $resource = $resources->next ) { +        if ($is_tracking_context) { +            set_up_line_context_for_file(); +        } + +        if ( $opt_f ) { +            if ( $opt->{show_types} ) { +                show_types( $resource, $ors ); +            } +            else { +                App::Ack::print( $resource->name, $ors ); +            } +            ++$nmatches; +            last RESOURCES if defined($opt_m) && $nmatches >= $opt_m; +        } +        elsif ( $opt_g ) { +            if ( $opt->{show_types} ) { +                show_types( $resource, $ors ); +            } +            else { +                local $opt_show_filename = 0; # XXX Why is this local? + +                print_line_with_options( '', $resource->name, 0, $ors ); +            } +            ++$nmatches; +            last RESOURCES if defined($opt_m) && $nmatches >= $opt_m; +        } +        elsif ( $opt_lines ) { +            my %line_numbers; +            foreach my $line ( @{ $opt_lines } ) { +                my @lines             = split /,/, $line; +                @lines                = map { +                    /^(\d+)-(\d+)$/ +                        ? ( $1 .. $2 ) +                        : $_ +                } @lines; +                @line_numbers{@lines} = (1) x @lines; +            } + +            my $filename = $resource->name; + +            local $opt_color = 0; + +            iterate( $resource, sub { +                chomp; + +                if ( $line_numbers{$.} ) { +                    print_line_with_context( $filename, $_, $. ); +                } +                elsif ( $opt_passthru ) { +                    print_line_with_options( $filename, $_, $., ':' ); +                } +                elsif ( $is_tracking_context ) { +                    print_line_if_context( $filename, $_, $., '-' ); +                } +                return 1; +            }); +        } +        elsif ( $opt_count ) { +            my $matches_for_this_file = count_matches_in_resource( $resource ); + +            if ( not $opt_show_filename ) { +                $total_count += $matches_for_this_file; +                next RESOURCES; +            } + +            if ( !$opt_l || $matches_for_this_file > 0) { +                if ( $opt_show_filename ) { +                    App::Ack::print( $resource->name, ':', $matches_for_this_file, $ors ); +                } +                else { +                    App::Ack::print( $matches_for_this_file, $ors ); +                } +            } +        } +        elsif ( $opt_l || $opt_L ) { +            my $is_match = resource_has_match( $resource ); + +            if ( $opt_L ? !$is_match : $is_match ) { +                App::Ack::print( $resource->name, $ors ); +                ++$nmatches; + +                last RESOURCES if $only_first; +                last RESOURCES if defined($opt_m) && $nmatches >= $opt_m; +            } +        } +        else { +            $nmatches += print_matches_in_resource( $resource, $opt ); +            if ( $nmatches && $only_first ) { +                last RESOURCES; +            } +        } +    } + +    if ( $opt_count && !$opt_show_filename ) { +        App::Ack::print( $total_count, "\n" ); +    } + +    close $App::Ack::fh; +    App::Ack::exit_from_ack( $nmatches ); +} + +=pod + +=encoding UTF-8 + +=head1 NAME + +ack - grep-like text finder + +=head1 SYNOPSIS + +    ack [options] PATTERN [FILE...] +    ack -f [options] [DIRECTORY...] + +=head1 DESCRIPTION + +ack is designed as an alternative to F<grep> for programmers. + +ack searches the named input files or directories for lines containing a +match to the given PATTERN.  By default, ack prints the matching lines. +If no FILE or DIRECTORY is given, the current directory will be searched. + +PATTERN is a Perl regular expression.  Perl regular expressions +are commonly found in other programming languages, but for the particulars +of their behavior, please consult +L<http://perldoc.perl.org/perlreref.html|perlreref>.  If you don't know +how to use regular expression but are interested in learning, you may +consult L<http://perldoc.perl.org/perlretut.html|perlretut>.  If you do not +need or want ack to use regular expressions, please see the +C<-Q>/C<--literal> option. + +Ack can also list files that would be searched, without actually +searching them, to let you take advantage of ack's file-type filtering +capabilities. + +=head1 FILE SELECTION + +If files are not specified for searching, either on the command +line or piped in with the C<-x> option, I<ack> delves into +subdirectories selecting files for searching. + +I<ack> is intelligent about the files it searches.  It knows about +certain file types, based on both the extension on the file and, +in some cases, the contents of the file.  These selections can be +made with the B<--type> option. + +With no file selection, I<ack> searches through regular files that +are not explicitly excluded by B<--ignore-dir> and B<--ignore-file> +options, either present in F<ackrc> files or on the command line. + +The default options for I<ack> ignore certain files and directories.  These +include: + +=over 4 + +=item * Backup files: Files matching F<#*#> or ending with F<~>. + +=item * Coredumps: Files matching F<core.\d+> + +=item * Version control directories like F<.svn> and F<.git>. + +=back + +Run I<ack> with the C<--dump> option to see what settings are set. + +However, I<ack> always searches the files given on the command line, +no matter what type.  If you tell I<ack> to search in a coredump, +it will search in a coredump. + +=head1 DIRECTORY SELECTION + +I<ack> descends through the directory tree of the starting directories +specified.  If no directories are specified, the current working directory is +used.  However, it will ignore the shadow directories used by +many version control systems, and the build directories used by the +Perl MakeMaker system.  You may add or remove a directory from this +list with the B<--[no]ignore-dir> option. The option may be repeated +to add/remove multiple directories from the ignore list. + +For a complete list of directories that do not get searched, run +C<ack --dump>. + +=head1 WHEN TO USE GREP + +I<ack> trumps I<grep> as an everyday tool 99% of the time, but don't +throw I<grep> away, because there are times you'll still need it. + +E.g., searching through huge files looking for regexes that can be +expressed with I<grep> syntax should be quicker with I<grep>. + +If your script or parent program uses I<grep> C<--quiet> or C<--silent> +or needs exit 2 on IO error, use I<grep>. + +=head1 OPTIONS + +=over 4 + +=item B<--ackrc> + +Specifies an ackrc file to load after all others; see L</"ACKRC LOCATION SEMANTICS">. + +=item B<-A I<NUM>>, B<--after-context=I<NUM>> + +Print I<NUM> lines of trailing context after matching lines. + +=item B<-B I<NUM>>, B<--before-context=I<NUM>> + +Print I<NUM> lines of leading context before matching lines. + +=item B<--[no]break> + +Print a break between results from different files. On by default +when used interactively. + +=item B<-C [I<NUM>]>, B<--context[=I<NUM>]> + +Print I<NUM> lines (default 2) of context around matching lines. +You can specify zero lines of context to override another context +specified in an ackrc. + +=item B<-c>, B<--count> + +Suppress normal output; instead print a count of matching lines for +each input file.  If B<-l> is in effect, it will only show the +number of lines for each file that has lines matching.  Without +B<-l>, some line counts may be zeroes. + +If combined with B<-h> (B<--no-filename>) ack outputs only one total +count. + +=item B<--[no]color>, B<--[no]colour> + +B<--color> highlights the matching text.  B<--nocolor> suppresses +the color.  This is on by default unless the output is redirected. + +On Windows, this option is off by default unless the +L<Win32::Console::ANSI> module is installed or the C<ACK_PAGER_COLOR> +environment variable is used. + +=item B<--color-filename=I<color>> + +Sets the color to be used for filenames. + +=item B<--color-match=I<color>> + +Sets the color to be used for matches. + +=item B<--color-lineno=I<color>> + +Sets the color to be used for line numbers. + +=item B<--[no]column> + +Show the column number of the first match.  This is helpful for +editors that can place your cursor at a given position. + +=item B<--create-ackrc> + +Dumps the default ack options to standard output.  This is useful for +when you want to customize the defaults. + +=item B<--dump> + +Writes the list of options loaded and where they came from to standard +output.  Handy for debugging. + +=item B<--[no]env> + +B<--noenv> disables all environment processing. No F<.ackrc> is +read and all environment variables are ignored. By default, F<ack> +considers F<.ackrc> and settings in the environment. + +=item B<--flush> + +B<--flush> flushes output immediately.  This is off by default +unless ack is running interactively (when output goes to a pipe or +file). + +=item B<-f> + +Only print the files that would be searched, without actually doing +any searching.  PATTERN must not be specified, or it will be taken +as a path to search. + +=item B<--files-from=I<FILE>> + +The list of files to be searched is specified in I<FILE>.  The list of +files are separated by newlines.  If I<FILE> is C<->, the list is loaded +from standard input. + +=item B<--[no]filter> + +Forces ack to act as if it were receiving input via a pipe. + +=item B<--[no]follow> + +Follow or don't follow symlinks, other than whatever starting files +or directories were specified on the command line. + +This is off by default. + +=item B<-g I<PATTERN>> + +Print searchable files where the relative path + filename matches +I<PATTERN>. + +Note that + +    ack -g foo + +is exactly the same as + +    ack -f | ack foo + +This means that just as ack will not search, for example, F<.jpg> +files, C<-g> will not list F<.jpg> files either.  ack is not intended +to be a general-purpose file finder. + +Note also that if you have C<-i> in your .ackrc that the filenames +to be matched will be case-insensitive as well. + +This option can be combined with B<--color> to make it easier to +spot the match. + +=item B<--[no]group> + +B<--group> groups matches by file name.  This is the default +when used interactively. + +B<--nogroup> prints one result per line, like grep.  This is the +default when output is redirected. + +=item B<-H>, B<--with-filename> + +Print the filename for each match. This is the default unless searching +a single explicitly specified file. + +=item B<-h>, B<--no-filename> + +Suppress the prefixing of filenames on output when multiple files are +searched. + +=item B<--[no]heading> + +Print a filename heading above each file's results.  This is the default +when used interactively. + +=item B<--help>, B<-?> + +Print a short help statement. + +=item B<--help-types>, B<--help=types> + +Print all known types. + +=item B<-i>, B<--ignore-case> + +Ignore case distinctions in PATTERN + +=item B<--ignore-ack-defaults> + +Tells ack to completely ignore the default definitions provided with ack. +This is useful in combination with B<--create-ackrc> if you I<really> want +to customize ack. + +=item B<--[no]ignore-dir=I<DIRNAME>>, B<--[no]ignore-directory=I<DIRNAME>> + +Ignore directory (as CVS, .svn, etc are ignored). May be used +multiple times to ignore multiple directories. For example, mason +users may wish to include B<--ignore-dir=data>. The B<--noignore-dir> +option allows users to search directories which would normally be +ignored (perhaps to research the contents of F<.svn/props> directories). + +The I<DIRNAME> must always be a simple directory name. Nested +directories like F<foo/bar> are NOT supported. You would need to +specify B<--ignore-dir=foo> and then no files from any foo directory +are taken into account by ack unless given explicitly on the command +line. + +=item B<--ignore-file=I<FILTERTYPE:FILTERARGS>> + +Ignore files matching I<FILTERTYPE:FILTERARGS>.  The filters are specified +identically to file type filters as seen in L</"Defining your own types">. + +=item B<-k>, B<--known-types> + +Limit selected files to those with types that ack knows about.  This is +equivalent to the default behavior found in ack 1. + +=item B<--lines=I<NUM>> + +Only print line I<NUM> of each file. Multiple lines can be given with multiple +B<--lines> options or as a comma separated list (B<--lines=3,5,7>). B<--lines=4-7> +also works. The lines are always output in ascending order, no matter the +order given on the command line. + +=item B<-l>, B<--files-with-matches> + +Only print the filenames of matching files, instead of the matching text. + +=item B<-L>, B<--files-without-matches> + +Only print the filenames of files that do I<NOT> match. + +=item B<--match I<PATTERN>> + +Specify the I<PATTERN> explicitly. This is helpful if you don't want to put the +regex as your first argument, e.g. when executing multiple searches over the +same set of files. + +    # search for foo and bar in given files +    ack file1 t/file* --match foo +    ack file1 t/file* --match bar + +=item B<-m=I<NUM>>, B<--max-count=I<NUM>> + +Stop reading a file after I<NUM> matches. + +=item B<--man> + +Print this manual page. + +=item B<-n>, B<--no-recurse> + +No descending into subdirectories. + +=item B<-o> + +Show only the part of each line matching PATTERN (turns off text +highlighting) + +=item B<--output=I<expr>> + +Output the evaluation of I<expr> for each line (turns off text +highlighting) +If PATTERN matches more than once then a line is output for each non-overlapping match. +For more information please see the section L</"Examples of F<--output>">. + +=item B<--pager=I<program>>, B<--nopager> + +B<--pager> directs ack's output through I<program>.  This can also be specified +via the C<ACK_PAGER> and C<ACK_PAGER_COLOR> environment variables. + +Using --pager does not suppress grouping and coloring like piping +output on the command-line does. + +B<--nopager> cancels any setting in ~/.ackrc, C<ACK_PAGER> or C<ACK_PAGER_COLOR>. +No output will be sent through a pager. + +=item B<--passthru> + +Prints all lines, whether or not they match the expression.  Highlighting +will still work, though, so it can be used to highlight matches while +still seeing the entire file, as in: + +    # Watch a log file, and highlight a certain IP address +    $ tail -f ~/access.log | ack --passthru 123.45.67.89 + +=item B<--print0> + +Only works in conjunction with -f, -g, -l or -c (filename output). The filenames +are output separated with a null byte instead of the usual newline. This is +helpful when dealing with filenames that contain whitespace, e.g. + +    # remove all files of type html +    ack -f --html --print0 | xargs -0 rm -f + +=item B<-Q>, B<--literal> + +Quote all metacharacters in PATTERN, it is treated as a literal. + +=item B<-r>, B<-R>, B<--recurse> + +Recurse into sub-directories. This is the default and just here for +compatibility with grep. You can also use it for turning B<--no-recurse> off. + +=item B<-s> + +Suppress error messages about nonexistent or unreadable files.  This is taken +from fgrep. + +=item B<--[no]smart-case>, B<--no-smart-case> + +Ignore case in the search strings if PATTERN contains no uppercase +characters. This is similar to C<smartcase> in vim. This option is +off by default, and ignored if C<-i> is specified. + +B<-i> always overrides this option. + +=item B<--sort-files> + +Sorts the found files lexicographically.  Use this if you want your file +listings to be deterministic between runs of I<ack>. + +=item B<--show-types> + +Outputs the filetypes that ack associates with each file. + +Works with B<-f> and B<-g> options. + +=item B<--type=[no]TYPE> + +Specify the types of files to include or exclude from a search. +TYPE is a filetype, like I<perl> or I<xml>.  B<--type=perl> can +also be specified as B<--perl>, and B<--type=noperl> can be done +as B<--noperl>. + +If a file is of both type "foo" and "bar", specifying --foo and +--nobar will exclude the file, because an exclusion takes precedence +over an inclusion. + +Type specifications can be repeated and are ORed together. + +See I<ack --help=types> for a list of valid types. + +=item B<--type-add I<TYPE>:I<FILTER>:I<FILTERARGS>> + +Files with the given FILTERARGS applied to the given FILTER +are recognized as being of (the existing) type TYPE. +See also L</"Defining your own types">. + + +=item B<--type-set I<TYPE>:I<FILTER>:I<FILTERARGS>> + +Files with the given FILTERARGS applied to the given FILTER are recognized as +being of type TYPE. This replaces an existing definition for type TYPE.  See +also L</"Defining your own types">. + +=item B<--type-del I<TYPE>> + +The filters associated with TYPE are removed from Ack, and are no longer considered +for searches. + +=item B<-v>, B<--invert-match> + +Invert match: select non-matching lines + +=item B<--version> + +Display version and copyright information. + +=item B<-w>, B<--word-regexp> + +=item B<-w>, B<--word-regexp> + +Turn on "words mode".  This sometimes matches a whole word, but the +semantics is quite subtle.  If the passed regexp begins with a word +character, then a word boundary is required before the match.  If the +passed regexp ends with a word character, or with a word character +followed by newline, then a word boundary is required after the match. + +Thus, for example, B<-w> with the regular expression C<ox> will not +match the strings C<box> or C<oxen>.  However, if the regular +expression is C<(ox|ass)> then it will match those strings.  Because +the regular expression's first character is C<(>, the B<-w> flag has +no effect at the start, and because the last character is C<)>, it has +no effect at the end. + +Force PATTERN to match only whole words.  The PATTERN is wrapped with +C<\b> metacharacters. + +=item B<-x> + +An abbreviation for B<--files-from=->; the list of files to search are read +from standard input, with one line per file. + +=item B<-1> + +Stops after reporting first match of any kind.  This is different +from B<--max-count=1> or B<-m1>, where only one match per file is +shown.  Also, B<-1> works with B<-f> and B<-g>, where B<-m> does +not. + +=item B<--thpppt> + +Display the all-important Bill The Cat logo.  Note that the exact +spelling of B<--thpppppt> is not important.  It's checked against +a regular expression. + +=item B<--bar> + +Check with the admiral for traps. + +=item B<--cathy> + +Chocolate, Chocolate, Chocolate! + +=back + +=head1 THE .ackrc FILE + +The F<.ackrc> file contains command-line options that are prepended +to the command line before processing.  Multiple options may live +on multiple lines.  Lines beginning with a # are ignored.  A F<.ackrc> +might look like this: + +    # Always sort the files +    --sort-files + +    # Always color, even if piping to another program +    --color + +    # Use "less -r" as my pager +    --pager=less -r + +Note that arguments with spaces in them do not need to be quoted, +as they are not interpreted by the shell. Basically, each I<line> +in the F<.ackrc> file is interpreted as one element of C<@ARGV>. + +F<ack> looks in several locations for F<.ackrc> files; the searching +process is detailed in L</"ACKRC LOCATION SEMANTICS">.  These +files are not considered if B<--noenv> is specified on the command line. + +=head1 Defining your own types + +ack allows you to define your own types in addition to the predefined +types. This is done with command line options that are best put into +an F<.ackrc> file - then you do not have to define your types over and +over again. In the following examples the options will always be shown +on one command line so that they can be easily copy & pasted. + +File types can be specified both with the the I<--type=xxx> option, +or the file type as an option itself.  For example, if you create +a filetype of "cobol", you can specify I<--type=cobol> or simply +I<--cobol>.  File types must be at least two characters long.  This +is why the C language is I<--cc> and the R language is I<--rr>. + +I<ack --perl foo> searches for foo in all perl files. I<ack --help=types> +tells you, that perl files are files ending +in .pl, .pm, .pod or .t. So what if you would like to include .xs +files as well when searching for --perl files? I<ack --type-add perl:ext:xs --perl foo> +does this for you. B<--type-add> appends +additional extensions to an existing type. + +If you want to define a new type, or completely redefine an existing +type, then use B<--type-set>. I<ack --type-set eiffel:ext:e,eiffel> defines +the type I<eiffel> to include files with +the extensions .e or .eiffel. So to search for all eiffel files +containing the word Bertrand use I<ack --type-set eiffel:ext:e,eiffel --eiffel Bertrand>. +As usual, you can also write B<--type=eiffel> +instead of B<--eiffel>. Negation also works, so B<--noeiffel> excludes +all eiffel files from a search. Redefining also works: I<ack --type-set cc:ext:c,h> +and I<.xs> files no longer belong to the type I<cc>. + +When defining your own types in the F<.ackrc> file you have to use +the following: + +  --type-set=eiffel:ext:e,eiffel + +or writing on separate lines + +  --type-set +  eiffel:ext:e,eiffel + +The following does B<NOT> work in the F<.ackrc> file: + +  --type-set eiffel:ext:e,eiffel + +In order to see all currently defined types, use I<--help-types>, e.g. +I<ack --type-set backup:ext:bak --type-add perl:ext:perl --help-types> + +In addition to filtering based on extension (like ack 1.x allowed), ack 2 +offers additional filter types.  The generic syntax is +I<--type-set TYPE:FILTER:FILTERARGS>; I<FILTERARGS> depends on the value +of I<FILTER>. + +=over 4 + +=item is:I<FILENAME> + +I<is> filters match the target filename exactly.  It takes exactly one +argument, which is the name of the file to match. + +Example: + +    --type-set make:is:Makefile + +=item ext:I<EXTENSION>[,I<EXTENSION2>[,...]] + +I<ext> filters match the extension of the target file against a list +of extensions.  No leading dot is needed for the extensions. + +Example: + +    --type-set perl:ext:pl,pm,t + +=item match:I<PATTERN> + +I<match> filters match the target filename against a regular expression. +The regular expression is made case insensitive for the search. + +Example: + +    --type-set make:match:/(gnu)?makefile/ + +=item firstlinematch:I<PATTERN> + +I<firstlinematch> matches the first line of the target file against a +regular expression.  Like I<match>, the regular expression is made +case insensitive. + +Example: + +    --type-add perl:firstlinematch:/perl/ + +=back + +More filter types may be made available in the future. + +=head1 ENVIRONMENT VARIABLES + +For commonly-used ack options, environment variables can make life +much easier.  These variables are ignored if B<--noenv> is specified +on the command line. + +=over 4 + +=item ACKRC + +Specifies the location of the user's F<.ackrc> file.  If this file doesn't +exist, F<ack> looks in the default location. + +=item ACK_OPTIONS + +This variable specifies default options to be placed in front of +any explicit options on the command line. + +=item ACK_COLOR_FILENAME + +Specifies the color of the filename when it's printed in B<--group> +mode.  By default, it's "bold green". + +The recognized attributes are clear, reset, dark, bold, underline, +underscore, blink, reverse, concealed black, red, green, yellow, +blue, magenta, on_black, on_red, on_green, on_yellow, on_blue, +on_magenta, on_cyan, and on_white.  Case is not significant. +Underline and underscore are equivalent, as are clear and reset. +The color alone sets the foreground color, and on_color sets the +background color. + +This option can also be set with B<--color-filename>. + +=item ACK_COLOR_MATCH + +Specifies the color of the matching text when printed in B<--color> +mode.  By default, it's "black on_yellow". + +This option can also be set with B<--color-match>. + +See B<ACK_COLOR_FILENAME> for the color specifications. + +=item ACK_COLOR_LINENO + +Specifies the color of the line number when printed in B<--color> +mode.  By default, it's "bold yellow". + +This option can also be set with B<--color-lineno>. + +See B<ACK_COLOR_FILENAME> for the color specifications. + +=item ACK_PAGER + +Specifies a pager program, such as C<more>, C<less> or C<most>, to which +ack will send its output. + +Using C<ACK_PAGER> does not suppress grouping and coloring like +piping output on the command-line does, except that on Windows +ack will assume that C<ACK_PAGER> does not support color. + +C<ACK_PAGER_COLOR> overrides C<ACK_PAGER> if both are specified. + +=item ACK_PAGER_COLOR + +Specifies a pager program that understands ANSI color sequences. +Using C<ACK_PAGER_COLOR> does not suppress grouping and coloring +like piping output on the command-line does. + +If you are not on Windows, you never need to use C<ACK_PAGER_COLOR>. + +=back + +=head1 AVAILABLE COLORS + +F<ack> uses the colors available in Perl's L<Term::ANSIColor> module, which +provides the following listed values. Note that case does not matter when using +these values. + +=head2 Foreground colors + +    black  red  green  yellow  blue  magenta  cyan  white + +    bright_black  bright_red      bright_green  bright_yellow +    bright_blue   bright_magenta  bright_cyan   bright_white + +=head2 Background colors + +    on_black  on_red      on_green  on_yellow +    on_blue   on_magenta  on_cyan   on_white + +    on_bright_black  on_bright_red      on_bright_green  on_bright_yellow +    on_bright_blue   on_bright_magenta  on_bright_cyan   on_bright_white + +=head1 ACK & OTHER TOOLS + +=head2 Simple vim integration + +F<ack> integrates easily with the Vim text editor. Set this in your +F<.vimrc> to use F<ack> instead of F<grep>: + +    set grepprg=ack\ -k + +That example uses C<-k> to search through only files of the types ack +knows about, but you may use other default flags. Now you can search +with F<ack> and easily step through the results in Vim: + +  :grep Dumper perllib + +=head2 Editor integration + +Many users have integrated ack into their preferred text editors. +For details and links, see L<https://beyondgrep.com/more-tools/>. + +=head2 Shell and Return Code + +For greater compatibility with I<grep>, I<ack> in normal use returns +shell return or exit code of 0 only if something is found and 1 if +no match is found. + +(Shell exit code 1 is C<$?=256> in perl with C<system> or backticks.) + +The I<grep> code 2 for errors is not used. + +If C<-f> or C<-g> are specified, then 0 is returned if at least one +file is found.  If no files are found, then 1 is returned. + +=cut + +=head1 DEBUGGING ACK PROBLEMS + +If ack gives you output you're not expecting, start with a few simple steps. + +=head2 Use B<--noenv> + +Your environment variables and F<.ackrc> may be doing things you're +not expecting, or forgotten you specified.  Use B<--noenv> to ignore +your environment and F<.ackrc>. + +=head2 Use B<-f> to see what files have been selected + +Ack's B<-f> was originally added as a debugging tool.  If ack is +not finding matches you think it should find, run F<ack -f> to see +what files have been selected.  You can also add the C<--show-types> +options to show the type of each file selected. + +=head2 Use B<--dump> + +This lists the ackrc files that are loaded and the options loaded +from them. +So for example you can find a list of directories that do not get searched or where filetypes are defined. + +=head1 TIPS + +=head2 Use the F<.ackrc> file. + +The F<.ackrc> is the place to put all your options you use most of +the time but don't want to remember.  Put all your --type-add and +--type-set definitions in it.  If you like --smart-case, set it +there, too.  I also set --sort-files there. + +=head2 Use F<-f> for working with big codesets + +Ack does more than search files.  C<ack -f --perl> will create a +list of all the Perl files in a tree, ideal for sending into F<xargs>. +For example: + +    # Change all "this" to "that" in all Perl files in a tree. +    ack -f --perl | xargs perl -p -i -e's/this/that/g' + +or if you prefer: + +    perl -p -i -e's/this/that/g' $(ack -f --perl) + +=head2 Use F<-Q> when in doubt about metacharacters + +If you're searching for something with a regular expression +metacharacter, most often a period in a filename or IP address, add +the -Q to avoid false positives without all the backslashing.  See +the following example for more... + +=head2 Use ack to watch log files + +Here's one I used the other day to find trouble spots for a website +visitor.  The user had a problem loading F<troublesome.gif>, so I +took the access log and scanned it with ack twice. + +    ack -Q aa.bb.cc.dd /path/to/access.log | ack -Q -B5 troublesome.gif + +The first ack finds only the lines in the Apache log for the given +IP.  The second finds the match on my troublesome GIF, and shows +the previous five lines from the log in each case. + +=head2 Examples of F<--output> + +Following variables are useful in the expansion string: + +=over 4 + +=item C<$&> + +The whole string matched by PATTERN. + +=item C<$1>, C<$2>, ... + +The contents of the 1st, 2nd ... bracketed group in PATTERN. + +=item C<$`> + +The string before the match. + +=item C<$'> + +The string after the match. + +=back + +For more details and other variables see +L<http://perldoc.perl.org/perlvar.html#Variables-related-to-regular-expressions|perlvar>. + +This example shows how to add text around a particular pattern +(in this case adding _ around word with "e") + +    ack2.pl "\w*e\w*" quick.txt --output="$`_$&_$'" +    _The_ quick brown fox jumps over the lazy dog +    The quick brown fox jumps _over_ the lazy dog +    The quick brown fox jumps over _the_ lazy dog + +This shows how to pick out particular parts of a match using ( ) within regular expression. + +  ack '=head(\d+)\s+(.*)' --output=' $1 : $2' +  input file contains "=head1 NAME" +  output  "1 : NAME" + +=head1 COMMUNITY + +There are ack mailing lists and a Slack channel for ack.  See +L<https://beyondgrep.com/community/> for details. + +=head1 FAQ + +=head2 Why isn't ack finding a match in (some file)? + +First, take a look and see if ack is even looking at the file.  ack is +intelligent in what files it will search and which ones it won't, but +sometimes that can be surprising. + +Use the C<-f> switch, with no regex, to see a list of files that ack +will search for you.  If your file doesn't show up in the list of files +that C<ack -f> shows, then ack never looks in it. + +NOTE: If you're using an old ack before 2.0, it's probably because it's of +a type that ack doesn't recognize.  In ack 1.x, the searching behavior is +driven by filetype.  B<If ack 1.x doesn't know what kind of file it is, +ack ignores the file.>  You can use the C<--show-types> switch to show +which type ack thinks each file is. + +=head2 Wouldn't it be great if F<ack> did search & replace? + +No, ack will always be read-only.  Perl has a perfectly good way +to do search & replace in files, using the C<-i>, C<-p> and C<-n> +switches. + +You can certainly use ack to select your files to update.  For +example, to change all "foo" to "bar" in all PHP files, you can do +this from the Unix shell: + +    $ perl -i -p -e's/foo/bar/g' $(ack -f --php) + +=head2 Can I make ack recognize F<.xyz> files? + +Yes!  Please see L</"Defining your own types">.  If you think +that F<ack> should recognize a type by default, please see +L</"ENHANCEMENTS">. + +=head2 There's already a program/package called ack. + +Yes, I know. + +=head2 Why is it called ack if it's called ack-grep? + +The name of the program is "ack".  Some packagers have called it +"ack-grep" when creating packages because there's already a package +out there called "ack" that has nothing to do with this ack. + +I suggest you make a symlink named F<ack> that points to F<ack-grep> +because one of the crucial benefits of ack is having a name that's +so short and simple to type. + +To do that, run this with F<sudo> or as root: + +   ln -s /usr/bin/ack-grep /usr/bin/ack + +Alternatively, you could use a shell alias: + +    # bash/zsh +    alias ack=ack-grep + +    # csh +    alias ack ack-grep + +=head2 What does F<ack> mean? + +Nothing.  I wanted a name that was easy to type and that you could +pronounce as a single syllable. + +=head2 Can I do multi-line regexes? + +No, ack does not support regexes that match multiple lines.  Doing +so would require reading in the entire file at a time. + +If you want to see lines near your match, use the C<--A>, C<--B> +and C<--C> switches for displaying context. + +=head2 Why is ack telling me I have an invalid option when searching for C<+foo>? + +ack treats command line options beginning with C<+> or C<-> as options; if you +would like to search for these, you may prefix your search term with C<--> or +use the C<--match> option.  (However, don't forget that C<+> is a regular +expression metacharacter!) + +=head2 Why does C<"ack '.{40000,}'"> fail?  Isn't that a valid regex? + +The Perl language limits the repetition quantifier to 32K.  You +can search for C<.{32767}> but not C<.{32768}>. + +=head2 Ack does "X" and shouldn't, should it? + +We try to remain as close to grep's behavior as possible, so when in doubt, +see what grep does!  If there's a mismatch in functionality there, please +bring it up on the ack-users mailing list. + +=head1 ACKRC LOCATION SEMANTICS + +Ack can load its configuration from many sources.  The following list +specifies the sources Ack looks for configuration files; each one +that is found is loaded in the order specified here, and +each one overrides options set in any of the sources preceding +it.  (For example, if I set --sort-files in my user ackrc, and +--nosort-files on the command line, the command line takes +precedence) + +=over 4 + +=item * + +Defaults are loaded from App::Ack::ConfigDefaults.  This can be omitted +using C<--ignore-ack-defaults>. + +=item * Global ackrc + +Options are then loaded from the global ackrc.  This is located at +C</etc/ackrc> on Unix-like systems. + +Under Windows XP and earlier, the global ackrc is at +C<C:\Documents and Settings\All Users\Application Data\ackrc> + +Under Windows Vista/7, the global ackrc is at +C<C:\ProgramData\ackrc> + +The C<--noenv> option prevents all ackrc files from being loaded. + +=item * User ackrc + +Options are then loaded from the user's ackrc.  This is located at +C<$HOME/.ackrc> on Unix-like systems. + +Under Windows XP and earlier, the user's ackrc is at +C<C:\Documents and Settings\$USER\Application Data\ackrc>. + +Under Windows Vista/7, the user's ackrc is at +C<C:\Users\$USER\AppData\Roaming\ackrc>. + +If you want to load a different user-level ackrc, it may be specified +with the C<$ACKRC> environment variable. + +The C<--noenv> option prevents all ackrc files from being loaded. + +=item * Project ackrc + +Options are then loaded from the project ackrc.  The project ackrc is +the first ackrc file with the name C<.ackrc> or C<_ackrc>, first searching +in the current directory, then the parent directory, then the grandparent +directory, etc.  This can be omitted using C<--noenv>. + +=item * --ackrc + +The C<--ackrc> option may be included on the command line to specify an +ackrc file that can override all others.  It is consulted even if C<--noenv> +is present. + +=item * ACK_OPTIONS + +Options are then loaded from the environment variable C<ACK_OPTIONS>.  This can +be omitted using C<--noenv>. + +=item * Command line + +Options are then loaded from the command line. + +=back + +=head1 DIFFERENCES BETWEEN ACK 1.X AND ACK 2.X + +A lot of changes were made for ack 2; here is a list of them. + +=head2 GENERAL CHANGES + +=over 4 + +=item * + +When no selectors are specified, ack 1.x only searches through files that +it can map to a file type.  ack 2.x, by contrast, will search through +every regular, non-binary file that is not explicitly ignored via +B<--ignore-file> or B<--ignore-dir>.  This is similar to the behavior of the +B<-a/--all> option in ack 1.x. + +=item * + +A more flexible filter system has been added, so that more powerful file types +may be created by the user.  For details, please consult +L</"Defining your own types">. + +=item * + +ack now loads multiple ackrc files; see L</"ACKRC LOCATION SEMANTICS"> for +details. + +=item * + +ack's default filter definitions aren't special; you may tell ack to +completely disregard them if you don't like them. + +=back + +=head2 REMOVED OPTIONS + +=over 4 + +=item * + +Because of the change in default search behavior, the B<-a/--all> and +B<-u/--unrestricted> options have been removed.  In addition, the +B<-k/--known-types> option was added to cause ack to behave with +the default search behavior of ack 1.x. + +=item * + +The B<-G> option has been removed.  Two regular expressions on the +command line was considered too confusing; to simulate B<-G>'s functionality, +you may use the new B<-x> option to pipe filenames from one invocation of +ack into another. + +=item * + +The B<--binary> option has been removed. + +=item * + +The B<--skipped> option has been removed. + +=item * + +The B<--text> option has been removed. + +=item * + +The B<--invert-file-match> option has been removed.  Instead, you may +use B<-v> with B<-g>. + +=back + +=head2 CHANGED OPTIONS + +=over 4 + +=item * + +The options that modify the regular expression's behavior (B<-i>, B<-w>, +B<-Q>, and B<-v>) may now be used with B<-g>. + +=back + +=head2 ADDED OPTIONS + +=over 4 + +=item * + +B<--files-from> was added so that a user may submit a list of filenames as +a list of files to search. + +=item * + +B<-x> was added to tell ack to accept a list of filenames via standard input; +this list is the list of filenames that will be used for the search. + +=item * + +B<-s> was added to tell ack to suppress error messages about non-existent or +unreadable files. + +=item * + +B<--ignore-directory> and B<--noignore-directory> were added as aliases for +B<--ignore-dir> and B<--noignore-dir> respectively. + +=item * + +B<--ignore-file> was added so that users may specify patterns of files to +ignore (ex. /.*~$/). + +=item * + +B<--dump> was added to allow users to easily find out which options are +set where. + +=item * + +B<--create-ackrc> was added so that users may create custom ackrc files based +on the default settings loaded by ack, and so that users may easily view those +defaults. + +=item * + +B<--type-del> was added to selectively remove file type definitions. + +=item * + +B<--ignore-ack-defaults> was added so that users may ignore ack's default +options in favor of their own. + +=item * + +B<--bar> was added so ack users may consult Admiral Ackbar. + +=back + +=head1 AUTHOR + +Andy Lester, C<< <andy at petdance.com> >> + +=head1 BUGS + +Please report any bugs or feature requests to the issues list at +Github: L<https://github.com/beyondgrep/ack2/issues> + +=head1 ENHANCEMENTS + +All enhancement requests MUST first be posted to the ack-users +mailing list at L<http://groups.google.com/group/ack-users>.  I +will not consider a request without it first getting seen by other +ack users.  This includes requests for new filetypes. + +There is a list of enhancements I want to make to F<ack> in the ack +issues list at Github: L<https://github.com/beyondgrep/ack2/issues> + +Patches are always welcome, but patches with tests get the most +attention. + +=head1 SUPPORT + +Support for and information about F<ack> can be found at: + +=over 4 + +=item * The ack homepage + +L<https://beyondgrep.com/> + +=item * The ack-users mailing list + +L<http://groups.google.com/group/ack-users> + +=item * The ack issues list at Github + +L<https://github.com/beyondgrep/ack2/issues> + +=item * AnnoCPAN: Annotated CPAN documentation + +L<http://annocpan.org/dist/ack> + +=item * CPAN Ratings + +L<http://cpanratings.perl.org/d/ack> + +=item * Search CPAN + +L<http://search.cpan.org/dist/ack> + +=item * MetaCPAN + +L<http://metacpan.org/release/ack> + +=item * Git source repository + +L<https://github.com/beyondgrep/ack2> + +=back + +=head1 ACKNOWLEDGEMENTS + +How appropriate to have I<ack>nowledgements! + +Thanks to everyone who has contributed to ack in any way, including +Michele Campeotto, +H.Merijn Brand, +Duke Leto, +Gerhard Poul, +Ethan Mallove, +Marek Kubica, +Ray Donnelly, +Nikolaj Schumacher, +Ed Avis, +Nick Morrott, +Austin Chamberlin, +Varadinsky, +SE<eacute>bastien FeugE<egrave>re, +Jakub Wilk, +Pete Houston, +Stephen Thirlwall, +Jonah Bishop, +Chris Rebert, +Denis Howe, +RaE<uacute>l GundE<iacute>n, +James McCoy, +Daniel Perrett, +Steven Lee, +Jonathan Perret, +Fraser Tweedale, +RaE<aacute>l GundE<aacute>n, +Steffen Jaeckel, +Stephan Hohe, +Michael Beijen, +Alexandr Ciornii, +Christian Walde, +Charles Lee, +Joe McMahon, +John Warwick, +David Steinbrunner, +Kara Martens, +Volodymyr Medvid, +Ron Savage, +Konrad Borowski, +Dale Sedivic, +Michael McClimon, +Andrew Black, +Ralph Bodenner, +Shaun Patterson, +Ryan Olson, +Shlomi Fish, +Karen Etheridge, +Olivier Mengue, +Matthew Wild, +Scott Kyle, +Nick Hooey, +Bo Borgerson, +Mark Szymanski, +Marq Schneider, +Packy Anderson, +JR Boyens, +Dan Sully, +Ryan Niebur, +Kent Fredric, +Mike Morearty, +Ingmar Vanhassel, +Eric Van Dewoestine, +Sitaram Chamarty, +Adam James, +Richard Carlsson, +Pedro Melo, +AJ Schuster, +Phil Jackson, +Michael Schwern, +Jan Dubois, +Christopher J. Madsen, +Matthew Wickline, +David Dyck, +Jason Porritt, +Jjgod Jiang, +Thomas Klausner, +Uri Guttman, +Peter Lewis, +Kevin Riggle, +Ori Avtalion, +Torsten Blix, +Nigel Metheringham, +GE<aacute>bor SzabE<oacute>, +Tod Hagan, +Michael Hendricks, +E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason, +Piers Cawley, +Stephen Steneker, +Elias Lutfallah, +Mark Leighton Fisher, +Matt Diephouse, +Christian Jaeger, +Bill Sully, +Bill Ricker, +David Golden, +Nilson Santos F. Jr, +Elliot Shank, +Merijn Broeren, +Uwe Voelker, +Rick Scott, +Ask BjE<oslash>rn Hansen, +Jerry Gay, +Will Coleda, +Mike O'Regan, +Slaven ReziE<0x107>, +Mark Stosberg, +David Alan Pisoni, +Adriano Ferreira, +James Keenan, +Leland Johnson, +Ricardo Signes, +Pete Krawczyk and +Rob Hoelz. + +=head1 COPYRIGHT & LICENSE + +Copyright 2005-2017 Andy Lester. + +This program is free software; you can redistribute it and/or modify +it under the terms of the Artistic License v2.0. + +See http://www.perlfoundation.org/artistic_license_2_0 or the LICENSE.md +file that comes with the ack distribution. + +=cut +package App::Ack; + +use warnings; +use strict; + + +our $VERSION; +our $COPYRIGHT; +BEGIN { +    $VERSION = '2.22'; +    $COPYRIGHT = 'Copyright 2005-2017 Andy Lester.'; +} + +our $fh; + +BEGIN { +    $fh = *STDOUT; +} + + +our %types; +our %type_wanted; +our %mappings; +our %ignore_dirs; + +our $is_filter_mode; +our $output_to_pipe; + +our $dir_sep_chars; +our $is_cygwin; +our $is_windows; + +use File::Spec 1.00015 (); + +BEGIN { +    # These have to be checked before any filehandle diddling. +    $output_to_pipe  = not -t *STDOUT; +    $is_filter_mode = -p STDIN; + +    $is_cygwin       = ($^O eq 'cygwin' || $^O eq 'msys'); +    $is_windows      = ($^O eq 'MSWin32'); +    $dir_sep_chars   = $is_windows ? quotemeta( '\\/' ) : quotemeta( File::Spec->catfile( '', '' ) ); +} + + + +sub remove_dir_sep { +    my $path = shift; +    $path =~ s/[$dir_sep_chars]$//; + +    return $path; +} + + + +sub warn { +    return CORE::warn( _my_program(), ': ', @_, "\n" ); +} + + +sub die { +    return CORE::die( _my_program(), ': ', @_, "\n" ); +} + +sub _my_program { +    require File::Basename; +    return File::Basename::basename( $0 ); +} + + + +sub filetypes_supported { +    return keys %mappings; +} + +sub thpppt { +    my $y = q{_   /|,\\'!.x',=(www)=,   U   }; +    $y =~ tr/,x!w/\nOo_/; + +    App::Ack::print( "$y ack $_[0]!\n" ); +    exit 0; +} + +sub ackbar { +    my $x; +    $x = <<'_BAR'; + 6?!I'7!I"?%+! + 3~!I#7#I"7#I!?!+!="+"="+!:! + 2?#I!7!I!?#I!7!I"+"=%+"=# + 1?"+!?*+!=#~"=!+#?"="+! + 0?"+!?"I"?&+!="~!=!~"=!+%="+" + /I!+!?)+!?!+!=$~!=!~!="+!="+"?!="?! + .?%I"?%+%='?!=#~$=" + ,,!?%I"?(+$=$~!=#:"~$:!~! + ,I!?!I!?"I"?!+#?"+!?!+#="~$:!~!:!~!:!,!:!,":#~! + +I!?&+!="+!?#+$=!~":!~!:!~!:!,!:#,!:!,%:" + *+!I!?!+$=!+!=!+!?$+#=!~":!~":#,$:",#:!,!:! + *I!?"+!?!+!=$+!?#+#=#~":$,!:",!:!,&:" + )I!?$=!~!=#+"?!+!=!+!=!~!="~!:!~":!,'.!,%:!~! + (=!?"+!?!=!~$?"+!?!+!=#~"=",!="~$,$.",#.!:!=! + (I"+"="~"=!+&=!~"=!~!,!~!+!=!?!+!?!=!I!?!+"=!.",!.!,":! + %I$?!+!?!=%+!~!+#~!=!~#:#=!~!+!~!=#:!,%.!,!.!:" + $I!?!=!?!I!+!?"+!=!~!=!~!?!I!?!=!+!=!~#:",!~"=!~!:"~!=!:",&:" '-/ + $?!+!I!?"+"=!+"~!,!:"+#~#:#,"=!~"=!,!~!,!.",!:".!:! */! !I!t!'!s! !a! !g!r!e!p!!! !/! + $+"=!+!?!+"~!=!:!~!:"I!+!,!~!=!:!~!,!:!,$:!~".&:"~!,# (-/ + %~!=!~!=!:!.!+"~!:!,!.!,!~!=!:$.!,":!,!.!:!~!,!:!=!.#="~!,!:" ./! + %=!~!?!+"?"+!=!~",!.!:!?!~!.!:!,!:!,#.!,!:","~!:!=!~!=!:",!~! ./! + %+"~":!~!=#~!:!~!,!.!~!:",!~!=!~!.!:!,!.",!:!,":!=":!.!,!:!7! -/! + %~",!:".#:!=!:!,!:"+!:!~!:!.!,!~!,!.#,!.!,$:"~!,":"~!=! */! + &=!~!=#+!=!~",!.!:",#:#,!.",+:!,!.",!=!+!?! + &~!=!~!=!~!:"~#:",!.!,#~!:!.!+!,!.",$.",$.#,!+!I!?! + &~!="~!:!~":!~",!~!=!~":!,!:!~!,!:!,&.$,#."+!?!I!?!I! + &~!=!~!=!+!,!:!~!:!=!,!:!~&:$,!.!,".!,".!,#."~!+!?$I! + &~!=!~!="~!=!:!~":!,!~%:#,!:",!.!,#.",#I!7"I!?!+!?"I" + &+!I!7!:#~"=!~!:!,!:"~$.!=!.!,!~!,$.#,!~!7!I#?!+!?"I"7! + %7#?!+!~!:!=!~!=!~":!,!:"~":#.!,)7#I"?"I!7& + %7#I!=":!=!~!:"~$:"~!:#,!:!,!:!~!:#,!7#I!?#7) + $7$+!,!~!=#~!:!~!:!~$:#,!.!~!:!=!,":!7#I"?#7+=!?! + $7#I!~!,!~#=!~!:"~!:!,!:!,#:!=!~",":!7$I!?#I!7*+!=!+" + "I!7$I!,":!,!.!=":$,!:!,$:$7$I!+!?"I!7+?"I!7!I!7!,! + !,!7%I!:",!."~":!,&.!,!:!~!I!7$I!+!?"I!7,?!I!7',! + !7(,!.#~":!,%.!,!7%I!7!?#I"7,+!?!7* +7+:!,!~#,"=!7'I!?#I"7/+!7+ +77I!+!7!?!7!I"71+!7, +_BAR + +    return _pic_decode($x); +} + +sub cathy { +    my $x = <<'CATHY'; + 0+!--+! + 0|! "C!H!O!C!O!L!A!T!E!!! !|! + 0|! "C!H!O!C!O!L!A!T!E!!! !|! + 0|! "C!H!O!C!O!L!A!T!E!!! !|! + 0|! $A"C!K!!! $|! + 0+!--+! + 6\! 1:!,!.! ! + 7\! /.!M!~!Z!M!~! + 8\! /~!D! "M! ! + 4.! $\! /M!~!.!8! +.!M# 4 + 0,!.! (\! .~!M!N! ,+!I!.!M!.! 3 + /?!O!.!M!:! '\! .O!.! +~!Z!=!N!.! 4 + ..! !D!Z!.!Z!.! '\! 9=!M".! 6 + /.! !.!~!M".! '\! 8~! 9 + 4M!.! /.!7!N!M!.! F + 4.! &:!M! !N"M# !M"N!M! #D!M&=! = + :M!7!M#:! !~!M!7!,!$!M!:! #.! !O!N!.!M!:!M# ; + 8Z!M"~!N!$!D!.!N!?! !I!N!.! (?!M! !M!,!D!M".! 9 + (?!Z!M!N!:! )=!M!O!8!.!M!+!M! !M!,! !O!M! +,!M!.!M!~!Z!N!M!:! &:!~! 0 + &8!7!.!~!M"D!M!,! &M!?!=!8! !M!,!O! !M!+! !+!O!.!M! $M#~! !.!8!M!Z!.!M! !O!M"Z! %:!~!M!Z!M!Z!.! + + &:!M!7!,! *M!.!Z!M! !8"M!.!M!~! !.!M!.!=! #~!8!.!M! !7!M! "N!Z#I! !D!M!,!M!.! $."M!,! !M!.! * + 2$!O! "N! !.!M!I! !7" "M! "+!O! !~!M! !d!O!.!7!I!M!.! !.!O!=!M!.! !M",!M!.! %.!$!O!D! + + 1~!O! "M!+! !8!$! "M! "?!O! %Z!8!D!M!?!8!I!O!7!M! #M!.!M! "M",!M! 4 + 07!~! ".!8! !.!M! "I!+! !.!M! &Z!D!.!7!=!M! !:!.!M! #:!8"+! !.!+!8! !8! 3 + /~!M! #N! !~!M!$! !.!M! !.!M" &~!M! "~!M!O! "D! $M! !8! "M!,!M!+!D!.! 1 + #.! #?!M!N!.! #~!O! $M!.!7!$! "?" !?!~!M! '7!8!?!M!.!+!M"O! $?"$!D! !.!O! !$!7!I!.! 0 + $,!M!:!O!?! ".! !?!=! $=!:!O! !M! "M! !M! !+!$! (.! +.!M! !M!.! !8! !+"Z!~! $:!M!$! !.! ' + #.!8!.!I!$! $7!I! %M" !=!M! !~!M!D! "7!I! .I!O! %?!=!,!D! !,!M! !D!~!8!~! %D!M! ( + #.!M"?! $=!O! %=!N! "8!.! !Z!M! #M!~! (M!:! #.!M" &O! !M!.! !?!,! !8!.!N!~! $8!N!M!,!.! % + *$!O! &M!,! "O! !.!M!.! #M! (~!M( &O!.! !7! "M! !.!M!.!M!,! #.!M! !M! & + )=!8!.! $.!M!O!.! "$!.!I!N! !I!M# (7!M(I! %D"Z!M! "=!I! "M! !M!:! #~!D! ' + )D! &8!N!:! ".!O! !M!="M! "M! (7!M) %." !M!D!."M!.! !$!=! !M!,! + + (M! &+!.!M! #Z!7!O!M!.!~!8! +,!M#D!?!M#D! #.!Z!M#,!Z!?! !~!N! "N!.! !M! + + 'D!:! %$!D! !?! #M!Z! !8!.! !M"?!7!?!7! '+!I!D! !?!O!:!M!:! ":!M!:! !M!7".!M! "8!+! !:!D! !.!M! * + %.!O!:! $.!O!+! !D!.! #M! "M!.!+!N!I!Z! "7!M!N!M!N!?!I!7!Z!=!M'D"~! #M!.!8!$! !:! !.!M! "N!?! !,!O! ) + !.!?!M!:!M!I! %8!,! "M!.! #M! "N! !M!.! !M!.! !+!~! !.!M!.! ':!M! $M! $M!Z!$! !M!.! "D! "M! "?!M! ( + !7!8! !+!I! ".! "$!=! ":!$! "+! !M!.! !O! !M!I!M".! !=!~! ",!O! '=!M! $$!,! #N!:! ":!8!.! !D!~! !,!M!.! !:!M!.! & + !:!,!.! &Z" #D! !.!8!."M!.! !8!?!Z!M!.!M! #Z!~! !?!M!Z!.! %~!O!.!8!$!N!8!O!I!:!~! !+! #M!.! !.!M!.! !+!M! ".!~!M!+! $ + !.! 'D!I! #?!M!.!M!,! !.!Z! !.!8! #M&O!I!?! (~!I!M"." !M!Z!.! !M!N!.! "+!$!.! "M!.! !M!?!.! "8!M! $ + (O!8! $M! !M!.! ".!:! !+!=! #M! #.!M! !+" *$!M":!.! !M!~! "M!7! #M! #7!Z! "M"$!M!.! !.! # + '$!Z! #.!7!+!M! $.!,! !+!:! #N! #.!M!.!+!M! +D!M! #=!N! ":!O! #=!M! #Z!D! $M!I! % + $,! ".! $.!M" %$!.! !?!~! "+!7!." !.!M!,! !M! *,!N!M!.$M!?! "D!,! #M!.! #N! + + ,M!Z! &M! "I!,! "M! %I!M! !?!=!.! (Z!8!M! $:!M!.! !,!M! $D! #.!M!.! ) + +8!O! &.!8! "I!,! !~!M! &N!M! !M!D! '?!N!O!." $?!7! "?!~! #M!.! #I!D!.! ( + 3M!,! "N!.! !D" &.!+!M!.! !M":!.":!M!7!M!D! 'M!.! "M!.! "M!,! $I! ) + 3I! #M! "M!,! !:! &.!M" ".!,! !.!$!M!I! #.! !:! !.!M!?! "N!+! ".! / + 1M!,! #.!M!8!M!=!.! +~!N"O!Z"~! *+!M!.! "M! 2 + 0.!M! &M!.! 8:! %.!M!Z! "M!=! *O!,! % + 0?!$! &N! )." .,! %."M! ":!M!.! 0 + 0N!:! %?!O! #.! ..! &,! &.!D!,! "N!I! 0 +CATHY +    return _pic_decode($x); +} + +sub _pic_decode { +    my($compressed) = @_; +    $compressed =~ s/(.)(.)/$1x(ord($2)-32)/eg; +    App::Ack::print( $compressed ); +    exit 0; +} + + +sub show_help { +    my $help_arg = shift || 0; + +    return show_help_types() if $help_arg =~ /^types?/; + +    App::Ack::print( <<"END_OF_HELP" ); +Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES] + +Search for PATTERN in each source file in the tree from the current +directory on down.  If any files or directories are specified, then +only those files and directories are checked.  ack may also search +STDIN, but only if no file or directory arguments are specified, +or if one of them is "-". + +Default switches may be specified in ACK_OPTIONS environment variable or +an .ackrc file. If you want no dependency on the environment, turn it +off with --noenv. + +Example: ack -i select + +Searching: +  -i, --ignore-case             Ignore case distinctions in PATTERN +  --[no]smart-case              Ignore case distinctions in PATTERN, +                                only if PATTERN contains no upper case. +                                Ignored if -i is specified +  -v, --invert-match            Invert match: select non-matching lines +  -w, --word-regexp             Force PATTERN to match only whole words +  -Q, --literal                 Quote all metacharacters; PATTERN is literal + +Search output: +  --lines=NUM                   Only print line(s) NUM of each file +  -l, --files-with-matches      Only print filenames containing matches +  -L, --files-without-matches   Only print filenames with no matches +  --output=expr                 Output the evaluation of expr for each line +                                (turns off text highlighting) +  -o                            Show only the part of a line matching PATTERN +                                Same as --output='\$&' +  --passthru                    Print all lines, whether matching or not +  --match PATTERN               Specify PATTERN explicitly. +  -m, --max-count=NUM           Stop searching in each file after NUM matches +  -1                            Stop searching after one match of any kind +  -H, --with-filename           Print the filename for each match (default: +                                on unless explicitly searching a single file) +  -h, --no-filename             Suppress the prefixing filename on output +  -c, --count                   Show number of lines matching per file +  --[no]column                  Show the column number of the first match + +  -A NUM, --after-context=NUM   Print NUM lines of trailing context after +                                matching lines. +  -B NUM, --before-context=NUM  Print NUM lines of leading context before +                                matching lines. +  -C [NUM], --context[=NUM]     Print NUM lines (default 2) of output context. + +  --print0                      Print null byte as separator between filenames, +                                only works with -f, -g, -l, -L or -c. + +  -s                            Suppress error messages about nonexistent or +                                unreadable files. + + +File presentation: +  --pager=COMMAND               Pipes all ack output through COMMAND.  For +                                example, --pager="less -R".  Ignored if output +                                is redirected. +  --nopager                     Do not send output through a pager.  Cancels +                                any setting in ~/.ackrc, ACK_PAGER or +                                ACK_PAGER_COLOR. +  --[no]heading                 Print a filename heading above each file's +                                results.  (default: on when used interactively) +  --[no]break                   Print a break between results from different +                                files.  (default: on when used interactively) +  --group                       Same as --heading --break +  --nogroup                     Same as --noheading --nobreak +  --[no]color                   Highlight the matching text (default: on unless +                                output is redirected, or on Windows) +  --[no]colour                  Same as --[no]color +  --color-filename=COLOR +  --color-match=COLOR +  --color-lineno=COLOR          Set the color for filenames, matches, and line +                                numbers. +  --flush                       Flush output immediately, even when ack is used +                                non-interactively (when output goes to a pipe or +                                file). + + +File finding: +  -f                            Only print the files selected, without +                                searching.  The PATTERN must not be specified. +  -g                            Same as -f, but only select files matching +                                PATTERN. +  --sort-files                  Sort the found files lexically. +  --show-types                  Show which types each file has. +  --files-from=FILE             Read the list of files to search from FILE. +  -x                            Read the list of files to search from STDIN. + +File inclusion/exclusion: +  --[no]ignore-dir=name         Add/remove directory from list of ignored dirs +  --[no]ignore-directory=name   Synonym for ignore-dir +  --ignore-file=filter          Add filter for ignoring files +  -r, -R, --recurse             Recurse into subdirectories (default: on) +  -n, --no-recurse              No descending into subdirectories +  --[no]follow                  Follow symlinks.  Default is off. +  -k, --known-types             Include only files of types that ack recognizes. + +  --type=X                      Include only X files, where X is a recognized +                                filetype. +  --type=noX                    Exclude X files. +                                See "ack --help-types" for supported filetypes. + +File type specification: +  --type-set TYPE:FILTER:FILTERARGS +                                Files with the given FILTERARGS applied to the +                                given FILTER are recognized as being of type +                                TYPE. This replaces an existing definition for +                                type TYPE. +  --type-add TYPE:FILTER:FILTERARGS +                                Files with the given FILTERARGS applied to the +                                given FILTER are recognized as being type TYPE. +  --type-del TYPE               Removes all filters associated with TYPE. + + +Miscellaneous: +  --[no]env                     Ignore environment variables and global ackrc +                                files.  --env is legal but redundant. +  --ackrc=filename              Specify an ackrc file to use +  --ignore-ack-defaults         Ignore default definitions included with ack. +  --create-ackrc                Outputs a default ackrc for your customization +                                to standard output. +  --help, -?                    This help +  --help-types                  Display all known types +  --dump                        Dump information on which options are loaded +                                from which RC files +  --[no]filter                  Force ack to treat standard input as a pipe +                                (--filter) or tty (--nofilter) +  --man                         Man page +  --version                     Display version & copyright +  --thpppt                      Bill the Cat +  --bar                         The warning admiral +  --cathy                       Chocolate! Chocolate! Chocolate! + +Exit status is 0 if match, 1 if no match. + +ack's home page is at https://beyondgrep.com/ + +The full ack manual is available by running "ack --man". + +This is version $VERSION of ack.  Run "ack --version" for full version info. +END_OF_HELP + +    return; + } + + + +sub show_help_types { +    App::Ack::print( <<'END_OF_HELP' ); +Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES] + +The following is the list of filetypes supported by ack.  You can +specify a file type with the --type=TYPE format, or the --TYPE +format.  For example, both --type=perl and --perl work. + +Note that some extensions may appear in multiple types.  For example, +.pod files are both Perl and Parrot. + +END_OF_HELP + +    my @types = filetypes_supported(); +    my $maxlen = 0; +    for ( @types ) { +        $maxlen = length if $maxlen < length; +    } +    for my $type ( sort @types ) { +        next if $type =~ /^-/; # Stuff to not show +        my $ext_list = $mappings{$type}; + +        if ( ref $ext_list ) { +            $ext_list = join( '; ', map { $_->to_string } @{$ext_list} ); +        } +        App::Ack::print( sprintf( "    --[no]%-*.*s %s\n", $maxlen, $maxlen, $type, $ext_list ) ); +    } + +    return; +} + +sub show_man { +    require Pod::Usage; + +    Pod::Usage::pod2usage({ +        -input   => $App::Ack::orig_program_name, +        -verbose => 2, +        -exitval => 0, +    }); + +    return; +} + + +sub get_version_statement { +    require Config; + +    my $copyright = get_copyright(); +    my $this_perl = $Config::Config{perlpath}; +    if ($^O ne 'VMS') { +        my $ext = $Config::Config{_exe}; +        $this_perl .= $ext unless $this_perl =~ m/$ext$/i; +    } +    my $ver = sprintf( '%vd', $^V ); + +    return <<"END_OF_VERSION"; +ack ${VERSION} +Running under Perl $ver at $this_perl + +$copyright + +This program is free software.  You may modify or distribute it +under the terms of the Artistic License v2.0. +END_OF_VERSION +} + + +sub print_version_statement { +    App::Ack::print( get_version_statement() ); + +    return; +} + + +sub get_copyright { +    return $COPYRIGHT; +} + + +sub print                   { print {$fh} @_; return; } +sub print_blank_line        { App::Ack::print( "\n" ); return; } +sub print_filename          { App::Ack::print( $_[0], $_[1] ); return; } + +sub set_up_pager { +    my $command = shift; + +    return if App::Ack::output_to_pipe(); + +    my $pager; +    if ( not open( $pager, '|-', $command ) ) { +        App::Ack::die( qq{Unable to pipe to pager "$command": $!} ); +    } +    $fh = $pager; + +    return; +} + + +sub output_to_pipe { +    return $output_to_pipe; +} + + +sub exit_from_ack { +    my $nmatches = shift; + +    my $rc = $nmatches ? 0 : 1; +    exit $rc; +} + + + +1; # End of App::Ack +package App::Ack::Resource; + + +use warnings; +use strict; +use overload +    '""' => 'name'; + + +sub new { +    my $class    = shift; +    my $filename = shift; + +    my $self = bless { +        filename => $filename, +        fh       => undef, +        opened   => 0, +    }, $class; + +    if ( $self->{filename} eq '-' ) { +        $self->{fh}     = *STDIN; +        $self->{opened} = 1; +    } + +    return $self; +} + + + +sub name { +    return $_[0]->{filename}; +} + + + +sub basename { +    my ( $self ) = @_; + +    # XXX Definedness? Pre-populate the slot with an undef? +    unless ( exists $self->{basename} ) { +        $self->{basename} = (File::Spec->splitpath($self->name))[2]; +    } + +    return $self->{basename}; +} + + + +sub open { +    my ( $self ) = @_; + +    if ( !$self->{opened} ) { +        if ( open $self->{fh}, '<', $self->{filename} ) { +            $self->{opened} = 1; +        } +        else { +            $self->{fh} = undef; +        } +    } + +    return $self->{fh}; +} + + + +sub needs_line_scan { +    my $self  = shift; +    my $opt   = shift; + +    return 1 if $opt->{v}; + +    my $size = -s $self->{fh}; +    if ( $size == 0 ) { +        return 0; +    } +    elsif ( $size > 100_000 ) { +        return 1; +    } + +    my $buffer; +    my $rc = sysread( $self->{fh}, $buffer, $size ); +    if ( !defined($rc) && $App::Ack::report_bad_filenames ) { +        App::Ack::warn( "$self->{filename}: $!" ); +        return 1; +    } +    return 0 unless $rc && ( $rc == $size ); + +    return $buffer =~ /$opt->{regex}/m; +} + + + +sub reset { +    my $self = shift; + +    # Return if we haven't opened the file yet. +    if ( !defined($self->{fh}) ) { +        return; +    } + +    if ( !seek( $self->{fh}, 0, 0 ) && $App::Ack::report_bad_filenames ) { +        App::Ack::warn( "$self->{filename}: $!" ); +    } + +    return; +} + + + +sub close { +    my $self = shift; + +    # Return if we haven't opened the file yet. +    if ( !defined($self->{fh}) ) { +        return; +    } + +    if ( !close($self->{fh}) && $App::Ack::report_bad_filenames ) { +        App::Ack::warn( $self->name() . ": $!" ); +    } + +    $self->{opened} = 0; + +    return; +} + + + +sub clone { +    my ( $self ) = @_; + +    return __PACKAGE__->new($self->name); +} + + + +sub firstliney { +    my ( $self ) = @_; + +    if ( !exists $self->{firstliney} ) { +        my $fh = $self->open(); +        if ( !$fh ) { +            if ( $App::Ack::report_bad_filenames ) { +                App::Ack::warn( $self->name . ': ' . $! ); +            } +            return ''; +        } + +        my $buffer = ''; +        my $rc     = sysread( $fh, $buffer, 250 ); +        unless($rc) { # XXX handle this better? +            $buffer = ''; +        } +        $buffer =~ s/[\r\n].*//s; +        $self->{firstliney} = $buffer; +        $self->reset; + +        $self->close; +    } + +    return $self->{firstliney}; +} + +1; +package App::Ack::Resources; + + + +use Errno qw(EACCES); + +use warnings; +use strict; + +sub _generate_error_handler { +    my $opt = shift; + +    if ( $opt->{dont_report_bad_filenames} ) { +        return sub { +            my $msg = shift; +            if ( $! == EACCES ) { +                return; +            } +            App::Ack::warn( $msg ); +        }; +    } +    else { +        return sub { +            my $msg = shift; +            App::Ack::warn( $msg ); +        }; +    } +} + + +sub from_argv { +    my $class = shift; +    my $opt   = shift; +    my $start = shift; + +    my $self = bless {}, $class; + +    my $file_filter    = undef; +    my $descend_filter = $opt->{descend_filter}; + +    if( $opt->{n} ) { +        $descend_filter = sub { +            return 0; +        }; +    } + +    $self->{iter} = +        File::Next::files( { +            file_filter     => $opt->{file_filter}, +            descend_filter  => $descend_filter, +            error_handler   => _generate_error_handler($opt), +            warning_handler => sub {}, +            sort_files      => $opt->{sort_files}, +            follow_symlinks => $opt->{follow}, +        }, @{$start} ); + +    return $self; +} + + +sub from_file { +    my $class = shift; +    my $opt   = shift; +    my $file  = shift; + +    my $iter = +        File::Next::from_file( { +            error_handler   => _generate_error_handler($opt), +            warning_handler => _generate_error_handler($opt), +            sort_files      => $opt->{sort_files}, +        }, $file ) or return undef; + +    return bless { +        iter => $iter, +    }, $class; +} + +# This is for reading input lines from STDIN, not the list of files from STDIN +sub from_stdin { +    my $class = shift; +    my $opt   = shift; + +    my $self  = bless {}, $class; + +    my $has_been_called = 0; + +    $self->{iter} = sub { +        if ( !$has_been_called ) { +            $has_been_called = 1; +            return '-'; +        } +        return; +    }; + +    return $self; +} + +sub next { +    my $self = shift; + +    my $file = $self->{iter}->() or return; + +    return App::Ack::Resource->new( $file ); +} + +1; +package App::Ack::ConfigDefault; + +use warnings; +use strict; + + + + +sub options { +    return split( /\n/, _options_block() ); +} + + +sub options_clean { +    return grep { /./ && !/^#/ } options(); +} + + +sub _options_block { +    my $lines = <<'HERE'; +# This is the default ackrc for ack version ==VERSION==. + +# There are four different ways to match +# +# is:  Match the filename exactly +# +# ext: Match the extension of the filename exactly +# +# match: Match the filename against a Perl regular expression +# +# firstlinematch: Match the first 250 characters of the first line +#   of text against a Perl regular expression.  This is only for +#   the --type-add option. + + +### Directories to ignore + +# Bazaar +# http://bazaar.canonical.com/ +--ignore-directory=is:.bzr + +# Codeville +# http://freecode.com/projects/codeville +--ignore-directory=is:.cdv + +# Interface Builder (Xcode) +# http://en.wikipedia.org/wiki/Interface_Builder +--ignore-directory=is:~.dep +--ignore-directory=is:~.dot +--ignore-directory=is:~.nib +--ignore-directory=is:~.plst + +# Git +# http://git-scm.com/ +--ignore-directory=is:.git +# When using submodules, .git is a file. +--ignore-file=is:.git + +# Mercurial +# http://mercurial.selenic.com/ +--ignore-directory=is:.hg + +# quilt +# http://directory.fsf.org/wiki/Quilt +--ignore-directory=is:.pc + +# Subversion +# http://subversion.tigris.org/ +--ignore-directory=is:.svn + +# Monotone +# http://www.monotone.ca/ +--ignore-directory=is:_MTN + +# CVS +# http://savannah.nongnu.org/projects/cvs +--ignore-directory=is:CVS + +# RCS +# http://www.gnu.org/software/rcs/ +--ignore-directory=is:RCS + +# SCCS +# http://en.wikipedia.org/wiki/Source_Code_Control_System +--ignore-directory=is:SCCS + +# darcs +# http://darcs.net/ +--ignore-directory=is:_darcs + +# Vault/Fortress +--ignore-directory=is:_sgbak + +# autoconf +# http://www.gnu.org/software/autoconf/ +--ignore-directory=is:autom4te.cache + +# Perl module building +--ignore-directory=is:blib +--ignore-directory=is:_build + +# Perl Devel::Cover module's output directory +# https://metacpan.org/release/Devel-Cover +--ignore-directory=is:cover_db + +# Node modules created by npm +--ignore-directory=is:node_modules + +# CMake cache +# http://www.cmake.org/ +--ignore-directory=is:CMakeFiles + +# Eclipse workspace folder +# http://eclipse.org/ +--ignore-directory=is:.metadata + +# Cabal (Haskell) sandboxes +# http://www.haskell.org/cabal/users-guide/installing-packages.html +--ignore-directory=is:.cabal-sandbox + +### Files to ignore + +# Backup files +--ignore-file=ext:bak +--ignore-file=match:/~$/ + +# Emacs swap files +--ignore-file=match:/^#.+#$/ + +# vi/vim swap files http://vim.org/ +--ignore-file=match:/[._].*\.swp$/ + +# core dumps +--ignore-file=match:/core\.\d+$/ + +# minified Javascript +--ignore-file=match:/[.-]min[.]js$/ +--ignore-file=match:/[.]js[.]min$/ + +# minified CSS +--ignore-file=match:/[.]min[.]css$/ +--ignore-file=match:/[.]css[.]min$/ + +# JS and CSS source maps +--ignore-file=match:/[.]js[.]map$/ +--ignore-file=match:/[.]css[.]map$/ + +# PDFs, because they pass Perl's -T detection +--ignore-file=ext:pdf + +# Common graphics, just as an optimization +--ignore-file=ext:gif,jpg,jpeg,png + + +### Filetypes defined + +# Perl +# http://perl.org/ +--type-add=perl:ext:pl,pm,pod,t,psgi +--type-add=perl:firstlinematch:/^#!.*\bperl/ + +# Perl tests +--type-add=perltest:ext:t + +# Makefiles +# http://www.gnu.org/s/make/ +--type-add=make:ext:mk +--type-add=make:ext:mak +--type-add=make:is:makefile +--type-add=make:is:Makefile +--type-add=make:is:Makefile.Debug +--type-add=make:is:Makefile.Release + +# Rakefiles +# http://rake.rubyforge.org/ +--type-add=rake:is:Rakefile + +# CMake +# http://www.cmake.org/ +--type-add=cmake:is:CMakeLists.txt +--type-add=cmake:ext:cmake + +# Actionscript +--type-add=actionscript:ext:as,mxml + +# Ada +# http://www.adaic.org/ +--type-add=ada:ext:ada,adb,ads + +# ASP +# http://msdn.microsoft.com/en-us/library/aa286483.aspx +--type-add=asp:ext:asp + +# ASP.Net +# http://www.asp.net/ +--type-add=aspx:ext:master,ascx,asmx,aspx,svc + +# Assembly +--type-add=asm:ext:asm,s + +# Batch +--type-add=batch:ext:bat,cmd + +# ColdFusion +# http://en.wikipedia.org/wiki/ColdFusion +--type-add=cfmx:ext:cfc,cfm,cfml + +# Clojure +# http://clojure.org/ +--type-add=clojure:ext:clj,cljs,edn,cljc + +# C +# .xs are Perl C files +--type-add=cc:ext:c,h,xs + +# C header files +--type-add=hh:ext:h + +# CoffeeScript +# http://coffeescript.org/ +--type-add=coffeescript:ext:coffee + +# C++ +--type-add=cpp:ext:cpp,cc,cxx,m,hpp,hh,h,hxx + +# C++ header files +--type-add=hpp:ext:hpp,hh,h,hxx + +# C# +--type-add=csharp:ext:cs + +# CSS +# http://www.w3.org/Style/CSS/ +--type-add=css:ext:css + +# Dart +# http://www.dartlang.org/ +--type-add=dart:ext:dart + +# Delphi +# http://en.wikipedia.org/wiki/Embarcadero_Delphi +--type-add=delphi:ext:pas,int,dfm,nfm,dof,dpk,dproj,groupproj,bdsgroup,bdsproj + +# Elixir +# http://elixir-lang.org/ +--type-add=elixir:ext:ex,exs + +# Emacs Lisp +# http://www.gnu.org/software/emacs +--type-add=elisp:ext:el + +# Erlang +# http://www.erlang.org/ +--type-add=erlang:ext:erl,hrl + +# Fortran +# http://en.wikipedia.org/wiki/Fortran +--type-add=fortran:ext:f,f77,f90,f95,f03,for,ftn,fpp + +# Go +# http://golang.org/ +--type-add=go:ext:go + +# Groovy +# http://groovy.codehaus.org/ +--type-add=groovy:ext:groovy,gtmpl,gpp,grunit,gradle + +# GSP +# http://groovy.codehaus.org/GSP +--type-add=gsp:ext:gsp + +# Haskell +# http://www.haskell.org/ +--type-add=haskell:ext:hs,lhs + +# HTML +--type-add=html:ext:htm,html,xhtml + +# Jade +# http://jade-lang.com/ +--type-add=jade:ext:jade + +# Java +# http://www.oracle.com/technetwork/java/index.html +--type-add=java:ext:java,properties + +# JavaScript +--type-add=js:ext:js + +# JSP +# http://www.oracle.com/technetwork/java/javaee/jsp/index.html +--type-add=jsp:ext:jsp,jspx,jspf,jhtm,jhtml + +# JSON +# http://www.json.org/ +--type-add=json:ext:json + +# Kotlin +# https://kotlinlang.org/ +--type-add=kotlin:ext:kt,kts + +# Less +# http://www.lesscss.org/ +--type-add=less:ext:less + +# Common Lisp +# http://common-lisp.net/ +--type-add=lisp:ext:lisp,lsp + +# Lua +# http://www.lua.org/ +--type-add=lua:ext:lua +--type-add=lua:firstlinematch:/^#!.*\blua(jit)?/ + +# Objective-C +--type-add=objc:ext:m,h + +# Objective-C++ +--type-add=objcpp:ext:mm,h + +# OCaml +# http://caml.inria.fr/ +--type-add=ocaml:ext:ml,mli,mll,mly + +# Matlab +# http://en.wikipedia.org/wiki/MATLAB +--type-add=matlab:ext:m + +# Parrot +# http://www.parrot.org/ +--type-add=parrot:ext:pir,pasm,pmc,ops,pod,pg,tg + +# PHP +# http://www.php.net/ +--type-add=php:ext:php,phpt,php3,php4,php5,phtml +--type-add=php:firstlinematch:/^#!.*\bphp/ + +# Plone +# http://plone.org/ +--type-add=plone:ext:pt,cpt,metadata,cpy,py + +# Python +# http://www.python.org/ +--type-add=python:ext:py +--type-add=python:firstlinematch:/^#!.*\bpython/ + +# R +# http://www.r-project.org/ +--type-add=rr:ext:R + +# reStructured Text +# http://docutils.sourceforge.net/rst.html +--type-add=rst:ext:rst + +# Ruby +# http://www.ruby-lang.org/ +--type-add=ruby:ext:rb,rhtml,rjs,rxml,erb,rake,spec +--type-add=ruby:is:Rakefile +--type-add=ruby:firstlinematch:/^#!.*\bruby/ + +# Rust +# http://www.rust-lang.org/ +--type-add=rust:ext:rs + +# Sass +# http://sass-lang.com +--type-add=sass:ext:sass,scss + +# Scala +# http://www.scala-lang.org/ +--type-add=scala:ext:scala + +# Scheme +# http://groups.csail.mit.edu/mac/projects/scheme/ +--type-add=scheme:ext:scm,ss + +# Shell +--type-add=shell:ext:sh,bash,csh,tcsh,ksh,zsh,fish +--type-add=shell:firstlinematch:/^#!.*\b(?:ba|t?c|k|z|fi)?sh\b/ + +# Smalltalk +# http://www.smalltalk.org/ +--type-add=smalltalk:ext:st + +# Smarty +# http://www.smarty.net/ +--type-add=smarty:ext:tpl + +# SQL +# http://www.iso.org/iso/catalogue_detail.htm?csnumber=45498 +--type-add=sql:ext:sql,ctl + +# Stylus +# http://learnboost.github.io/stylus/ +--type-add=stylus:ext:styl + +# Swift +# https://developer.apple.com/swift/ +--type-add=swift:ext:swift +--type-add=swift:firstlinematch:/^#!.*\bswift/ + +# Tcl +# http://www.tcl.tk/ +--type-add=tcl:ext:tcl,itcl,itk + +# LaTeX +# http://www.latex-project.org/ +--type-add=tex:ext:tex,cls,sty + +# Template Toolkit (Perl) +# http://template-toolkit.org/ +--type-add=tt:ext:tt,tt2,ttml + +# Visual Basic +--type-add=vb:ext:bas,cls,frm,ctl,vb,resx + +# Verilog +--type-add=verilog:ext:v,vh,sv + +# VHDL +# http://www.eda.org/twiki/bin/view.cgi/P1076/WebHome +--type-add=vhdl:ext:vhd,vhdl + +# Vim +# http://www.vim.org/ +--type-add=vim:ext:vim + +# XML +# http://www.w3.org/TR/REC-xml/ +--type-add=xml:ext:xml,dtd,xsd,xsl,xslt,ent,wsdl +--type-add=xml:firstlinematch:/<[?]xml/ + +# YAML +# http://yaml.org/ +--type-add=yaml:ext:yaml,yml +HERE +    $lines =~ s/==VERSION==/$App::Ack::VERSION/sm; + +    return $lines; +} + +1; +package App::Ack::ConfigFinder; + + +use strict; +use warnings; + +use Cwd 3.00 (); +use File::Spec 3.00; + +use if ($^O eq 'MSWin32'), 'Win32'; + + +sub new { +    my ( $class ) = @_; + +    return bless {}, $class; +} + + +sub _remove_redundancies { +    my @configs = @_; + +    my %seen; +    foreach my $config (@configs) { +        my $key = $config->{path}; +        if ( not $App::Ack::is_windows ) { +            # On Unix, uniquify on inode. +            my ($dev, $inode) = (stat $key)[0, 1]; +            $key = "$dev:$inode" if defined $dev; +        } +        undef $config if $seen{$key}++; +    } +    return grep { defined } @configs; +} + + +sub _check_for_ackrc { +    return unless defined $_[0]; + +    my @files = grep { -f } +                map { File::Spec->catfile(@_, $_) } +                qw(.ackrc _ackrc); + +    die File::Spec->catdir(@_) . " contains both .ackrc and _ackrc.\n" . +        "Please remove one of those files.\n" +            if @files > 1; + +    return wantarray ? @files : $files[0]; +} # end _check_for_ackrc + + + +sub find_config_files { +    my @config_files; + +    if ( $App::Ack::is_windows ) { +        push @config_files, map { +{ path => File::Spec->catfile($_, 'ackrc') } } ( +            Win32::GetFolderPath(Win32::CSIDL_COMMON_APPDATA()), +            Win32::GetFolderPath(Win32::CSIDL_APPDATA()), +        ); +    } +    else { +        push @config_files, { path => '/etc/ackrc' }; +    } + + +    if ( $ENV{'ACKRC'} && -f $ENV{'ACKRC'} ) { +        push @config_files, { path => $ENV{'ACKRC'} }; +    } +    else { +        push @config_files, map { +{ path => $_ } } _check_for_ackrc($ENV{'HOME'}); +    } + +    my $cwd = Cwd::getcwd(); +    return () unless defined $cwd; + +    # XXX This should go through some untainted cwd-fetching function, and not get untainted brute-force like this. +    $cwd =~ /(.+)/; +    $cwd = $1; +    my @dirs = File::Spec->splitdir( $cwd ); +    while(@dirs) { +        my $ackrc = _check_for_ackrc(@dirs); +        if(defined $ackrc) { +            push @config_files, { project => 1, path => $ackrc }; +            last; +        } +        pop @dirs; +    } + +    # We only test for existence here, so if the file is deleted out from under us, this will fail later. +    return _remove_redundancies( @config_files ); +} + + + +sub read_rcfile { +    my $file = shift; + +    return unless defined $file && -e $file; + +    my @lines; + +    open( my $fh, '<', $file ) or App::Ack::die( "Unable to read $file: $!" ); +    while ( my $line = <$fh> ) { +        chomp $line; +        $line =~ s/^\s+//; +        $line =~ s/\s+$//; + +        next if $line eq ''; +        next if $line =~ /^\s*#/; + +        push( @lines, $line ); +    } +    close $fh or App::Ack::die( "Unable to close $file: $!" ); + +    return @lines; +} + +1; +package App::Ack::ConfigLoader; + +use strict; +use warnings; + +use Carp 1.04 (); +use Getopt::Long 2.38 (); +use Text::ParseWords 3.1 (); + + +my @INVALID_COMBINATIONS; + +BEGIN { +    my @context  = qw( -A -B -C --after-context --before-context --context ); +    my @pretty   = qw( --heading --group --break ); +    my @filename = qw( -h -H --with-filename --no-filename ); + +    @INVALID_COMBINATIONS = ( +        # XXX normalize +        [qw(-l)]                 => [@context, @pretty, @filename, qw(-L -o --passthru --output --max-count --column -f -g --show-types)], +        [qw(-L)]                 => [@context, @pretty, @filename, qw(-l -o --passthru --output --max-count --column -f -g --show-types -c --count)], +        [qw(--line)]             => [@context, @pretty, @filename, qw(-l --files-with-matches --files-without-matches -L -o --passthru --match -m --max-count -1 -c --count --column --print0 -f -g --show-types)], +        [qw(-o)]                 => [@context, qw(--output -c --count --column --column -f --show-types)], +        [qw(--passthru)]         => [@context, qw(--output --column -m --max-count -1 -c --count -f -g)], +        [qw(--output)]           => [@context, qw(-c --count -f -g)], +        [qw(--match)]            => [qw(-f -g)], +        [qw(-m --max-count)]     => [qw(-1 -f -g -c --count)], +        [qw(-h --no-filename)]   => [qw(-H --with-filename -f -g --group --heading)], +        [qw(-H --with-filename)] => [qw(-h --no-filename -f -g)], +        [qw(-c --count)]         => [@context, @pretty, qw(--column -f -g)], +        [qw(--column)]           => [qw(-f -g)], +        [@context]               => [qw(-f -g)], +        [qw(-f)]                 => [qw(-g), @pretty], +        [qw(-g)]                 => [qw(-f), @pretty], +    ); +} + +sub _generate_ignore_dir { +    my ( $option_name, $opt ) = @_; + +    my $is_inverted = $option_name =~ /^--no/; + +    return sub { +        my ( undef, $dir ) = @_; + +        $dir = App::Ack::remove_dir_sep( $dir ); +        if ( $dir !~ /:/ ) { +            $dir = 'is:' . $dir; +        } + +        my ( $filter_type, $args ) = split /:/, $dir, 2; + +        if ( $filter_type eq 'firstlinematch' ) { +            Carp::croak( qq{Invalid filter specification "$filter_type" for option '$option_name'} ); +        } + +        my $filter = App::Ack::Filter->create_filter($filter_type, split(/,/, $args)); +        my $collection; + +        my $previous_inversion_matches = $opt->{idirs} && !($is_inverted xor $opt->{idirs}[-1]->is_inverted()); + +        if ( $previous_inversion_matches ) { +            $collection = $opt->{idirs}[-1]; + +            if ( $is_inverted ) { +                # XXX this relies on invert of an inverted filter +                #     to return the original +                $collection = $collection->invert() +            } +        } +        else { +            $collection = App::Ack::Filter::Collection->new(); + +            if ( $is_inverted ) { +                push @{ $opt->{idirs} }, $collection->invert(); +            } +            else { +                push @{ $opt->{idirs} }, $collection; +            } +        } + +        $collection->add($filter); + +        if ( $filter_type eq 'is' ) { +            $collection->add(App::Ack::Filter::IsPath->new($args)); +        } +    }; +} + +sub process_filter_spec { +    my ( $spec ) = @_; + +    if ( $spec =~ /^(\w+):(\w+):(.*)/ ) { +        my ( $type_name, $ext_type, $arguments ) = ( $1, $2, $3 ); + +        return ( $type_name, +            App::Ack::Filter->create_filter($ext_type, split(/,/, $arguments)) ); +    } +    elsif ( $spec =~ /^(\w+)=(.*)/ ) { # Check to see if we have ack1-style argument specification. +        my ( $type_name, $extensions ) = ( $1, $2 ); + +        my @extensions = split(/,/, $extensions); +        foreach my $extension ( @extensions ) { +            $extension =~ s/^[.]//; +        } + +        return ( $type_name, App::Ack::Filter->create_filter('ext', @extensions) ); +    } +    else { +        Carp::croak "invalid filter specification '$spec'"; +    } +} + + +sub uninvert_filter { +    my ( $opt, @filters ) = @_; + +    return unless defined $opt->{filters} && @filters; + +    # Loop through all the registered filters.  If we hit one that +    # matches this extension and it's inverted, we need to delete it from +    # the options. +    for ( my $i = 0; $i < @{ $opt->{filters} }; $i++ ) { +        my $opt_filter = @{ $opt->{filters} }[$i]; + +        # XXX Do a real list comparison? This just checks string equivalence. +        if ( $opt_filter->is_inverted() && "$opt_filter->{filter}" eq "@filters" ) { +            splice @{ $opt->{filters} }, $i, 1; +            $i--; +        } +    } + +    return; +} + + +sub process_filetypes { +    my ( $opt, $arg_sources ) = @_; + +    Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); # start with default options, minus some annoying ones +    Getopt::Long::Configure( +        'no_ignore_case', +        'no_auto_abbrev', +        'pass_through', +    ); +    my %additional_specs; + +    my $add_spec = sub { +        my ( undef, $spec ) = @_; + +        my ( $name, $filter ) = process_filter_spec($spec); + +        push @{ $App::Ack::mappings{$name} }, $filter; + +        $additional_specs{$name . '!'} = sub { +            my ( undef, $value ) = @_; + +            my @filters = @{ $App::Ack::mappings{$name} }; +            if ( not $value ) { +                @filters = map { $_->invert() } @filters; +            } +            else { +                uninvert_filter( $opt, @filters ); +            } + +            push @{ $opt->{'filters'} }, @filters; +        }; +    }; + +    my $set_spec = sub { +        my ( undef, $spec ) = @_; + +        my ( $name, $filter ) = process_filter_spec($spec); + +        $App::Ack::mappings{$name} = [ $filter ]; + +        $additional_specs{$name . '!'} = sub { +            my ( undef, $value ) = @_; + +            my @filters = @{ $App::Ack::mappings{$name} }; +            if ( not $value ) { +                @filters = map { $_->invert() } @filters; +            } + +            push @{ $opt->{'filters'} }, @filters; +        }; +    }; + +    my $delete_spec = sub { +        my ( undef, $name ) = @_; + +        delete $App::Ack::mappings{$name}; +        delete $additional_specs{$name . '!'}; +    }; + +    my %type_arg_specs = ( +        'type-add=s' => $add_spec, +        'type-set=s' => $set_spec, +        'type-del=s' => $delete_spec, +    ); + +    foreach my $source (@{$arg_sources}) { +        my ( $source_name, $args ) = @{$source}{qw/name contents/}; + +        if ( ref($args) ) { +            # $args are modified in place, so no need to munge $arg_sources +            local @ARGV = @{$args}; +            Getopt::Long::GetOptions(%type_arg_specs); +            @{$args} = @ARGV; +        } +        else { +            ( undef, $source->{contents} ) = +                Getopt::Long::GetOptionsFromString($args, %type_arg_specs); +        } +    } + +    $additional_specs{'k|known-types'} = sub { +        my ( undef, $value ) = @_; + +        my @filters = map { @{$_} } values(%App::Ack::mappings); + +        push @{ $opt->{'filters'} }, @filters; +    }; + +    return \%additional_specs; +} + + +sub removed_option { +    my ( $option, $explanation ) = @_; + +    $explanation ||= ''; +    return sub { +        warn "Option '$option' is not valid in ack 2.\n$explanation"; +        exit 1; +    }; +} + + +sub get_arg_spec { +    my ( $opt, $extra_specs ) = @_; + +    my $dash_a_explanation = <<'EOT'; +You don't need -a, ack 1.x users.  This is because ack 2.x has +-k/--known-types which makes it only select files of known types, rather +than any text file (which is the behavior of ack 1.x). + +If you're surprised to see this message because you didn't put -a on the +command line, you may have options in an .ackrc, or in the ACKRC_OPTIONS +environment variable.  Try using the --dump flag to help find it. +EOT + + +    sub _context_value { +        my $val = shift; + +        # Contexts default to 2. +        return (!defined($val) || ($val < 0)) ? 2 : $val; +    } + +    return { +        1                   => sub { $opt->{1} = $opt->{m} = 1 }, +        'A|after-context:-1'  => sub { shift; $opt->{after_context}  = _context_value(shift) }, +        'B|before-context:-1' => sub { shift; $opt->{before_context} = _context_value(shift) }, +        'C|context:-1'        => sub { shift; $opt->{before_context} = $opt->{after_context} = _context_value(shift) }, +        'a'                 => removed_option('-a', $dash_a_explanation), +        'all'               => removed_option('--all', $dash_a_explanation), +        'break!'            => \$opt->{break}, +        c                   => \$opt->{count}, +        'color|colour!'     => \$opt->{color}, +        'color-match=s'     => \$ENV{ACK_COLOR_MATCH}, +        'color-filename=s'  => \$ENV{ACK_COLOR_FILENAME}, +        'color-lineno=s'    => \$ENV{ACK_COLOR_LINENO}, +        'column!'           => \$opt->{column}, +        count               => \$opt->{count}, +        'create-ackrc'      => sub { print "$_\n" for ( '--ignore-ack-defaults', App::Ack::ConfigDefault::options() ); exit; }, +        'env!'              => sub { +            my ( undef, $value ) = @_; + +            if ( !$value ) { +                $opt->{noenv_seen} = 1; +            } +        }, +        f                   => \$opt->{f}, +        'files-from=s'      => \$opt->{files_from}, +        'filter!'           => \$App::Ack::is_filter_mode, +        flush               => \$opt->{flush}, +        'follow!'           => \$opt->{follow}, +        g                   => \$opt->{g}, +        G                   => removed_option('-G'), +        'group!'            => sub { shift; $opt->{heading} = $opt->{break} = shift }, +        'heading!'          => \$opt->{heading}, +        'h|no-filename'     => \$opt->{h}, +        'H|with-filename'   => \$opt->{H}, +        'i|ignore-case'     => \$opt->{i}, +        'ignore-directory|ignore-dir=s' => _generate_ignore_dir('--ignore-dir', $opt), +        'ignore-file=s'     => sub { +                                    my ( undef, $file ) = @_; + +                                    my ( $filter_type, $args ) = split /:/, $file, 2; + +                                    my $filter = App::Ack::Filter->create_filter($filter_type, split(/,/, $args)); + +                                    if ( !$opt->{ifiles} ) { +                                        $opt->{ifiles} = App::Ack::Filter::Collection->new(); +                                    } +                                    $opt->{ifiles}->add($filter); +                               }, +        'lines=s'           => sub { shift; my $val = shift; push @{$opt->{lines}}, $val }, +        'l|files-with-matches' +                            => \$opt->{l}, +        'L|files-without-matches' +                            => \$opt->{L}, +        'm|max-count=i'     => \$opt->{m}, +        'match=s'           => \$opt->{regex}, +        'n|no-recurse'      => \$opt->{n}, +        o                   => sub { $opt->{output} = '$&' }, +        'output=s'          => \$opt->{output}, +        'pager:s'           => sub { +            my ( undef, $value ) = @_; + +            $opt->{pager} = $value || $ENV{PAGER}; +        }, +        'noignore-directory|noignore-dir=s' => _generate_ignore_dir('--noignore-dir', $opt), +        'nopager'           => sub { $opt->{pager} = undef }, +        'passthru'          => \$opt->{passthru}, +        'print0'            => \$opt->{print0}, +        'Q|literal'         => \$opt->{Q}, +        'r|R|recurse'       => sub { $opt->{n} = 0 }, +        's'                 => \$opt->{dont_report_bad_filenames}, +        'show-types'        => \$opt->{show_types}, +        'smart-case!'       => \$opt->{smart_case}, +        'sort-files'        => \$opt->{sort_files}, +        'type=s'            => sub { +            my ( $getopt, $value ) = @_; + +            my $cb_value = 1; +            if ( $value =~ s/^no// ) { +                $cb_value = 0; +            } + +            my $callback = $extra_specs->{ $value . '!' }; + +            if ( $callback ) { +                $callback->( $getopt, $cb_value ); +            } +            else { +                Carp::croak( "Unknown type '$value'" ); +            } +        }, +        'u'                 => removed_option('-u'), +        'unrestricted'      => removed_option('--unrestricted'), +        'v|invert-match'    => \$opt->{v}, +        'w|word-regexp'     => \$opt->{w}, +        'x'                 => sub { $opt->{files_from} = '-' }, + +        'version'           => sub { App::Ack::print_version_statement(); exit; }, +        'help|?:s'          => sub { shift; App::Ack::show_help(@_); exit; }, +        'help-types'        => sub { App::Ack::show_help_types(); exit; }, +        'man'               => sub { App::Ack::show_man(); exit; }, +        $extra_specs ? %{$extra_specs} : (), +    }; # arg_specs +} + + +sub process_other { +    my ( $opt, $extra_specs, $arg_sources ) = @_; + +    # Start with default options, minus some annoying ones. +    Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); +    Getopt::Long::Configure( +        'bundling', +        'no_ignore_case', +    ); + +    my $argv_source; +    my $is_help_types_active; + +    foreach my $source (@{$arg_sources}) { +        my ( $source_name, $args ) = @{$source}{qw/name contents/}; + +        if ( $source_name eq 'ARGV' ) { +            $argv_source = $args; +            last; +        } +    } + +    if ( $argv_source ) { # This *should* always be true, but you never know... +        my @copy = @{$argv_source}; +        local @ARGV = @copy; + +        Getopt::Long::Configure('pass_through'); + +        Getopt::Long::GetOptions( +            'help-types' => \$is_help_types_active, +        ); + +        Getopt::Long::Configure('no_pass_through'); +    } + +    my $arg_specs = get_arg_spec($opt, $extra_specs); + +    foreach my $source (@{$arg_sources}) { +        my ( $source_name, $args ) = @{$source}{qw/name contents/}; + +        my $args_for_source = $arg_specs; + +        if ( $source->{project} ) { +            my $illegal = sub { +                die "Options --output, --pager and --match are forbidden in project .ackrc files.\n"; +            }; + +            $args_for_source = { +                %{$args_for_source}, +                'output=s' => $illegal, +                'pager:s'  => $illegal, +                'match=s'  => $illegal, +            }; +        } + +        my $ret; +        if ( ref($args) ) { +            local @ARGV = @{$args}; +            $ret = Getopt::Long::GetOptions( %{$args_for_source} ); +            @{$args} = @ARGV; +        } +        else { +            ( $ret, $source->{contents} ) = +                Getopt::Long::GetOptionsFromString( $args, %{$args_for_source} ); +        } +        if ( !$ret ) { +            if ( !$is_help_types_active ) { +                my $where = $source_name eq 'ARGV' ? 'on command line' : "in $source_name"; +                App::Ack::die( "Invalid option $where" ); +            } +        } +        if ( $opt->{noenv_seen} ) { +            App::Ack::die( "--noenv found in $source_name" ); +        } +    } + +    # XXX We need to check on a -- in the middle of a non-ARGV source + +    return; +} + + +sub should_dump_options { +    my ( $sources ) = @_; + +    foreach my $source (@{$sources}) { +        my ( $name, $options ) = @{$source}{qw/name contents/}; + +        if($name eq 'ARGV') { +            my $dump; +            local @ARGV = @{$options}; +            Getopt::Long::Configure('default', 'pass_through', 'no_auto_help', 'no_auto_version'); +            Getopt::Long::GetOptions( +                'dump' => \$dump, +            ); +            @{$options} = @ARGV; +            return $dump; +        } +    } +    return; +} + + +sub explode_sources { +    my ( $sources ) = @_; + +    my @new_sources; + +    Getopt::Long::Configure('default', 'pass_through', 'no_auto_help', 'no_auto_version'); + +    my %opt; +    my $arg_spec = get_arg_spec(\%opt); + +    my $add_type = sub { +        my ( undef, $arg ) = @_; + +        # XXX refactor? +        if ( $arg =~ /(\w+)=/) { +            $arg_spec->{$1} = sub {}; +        } +        else { +            ( $arg ) = split /:/, $arg; +            $arg_spec->{$arg} = sub {}; +        } +    }; + +    my $del_type = sub { +        my ( undef, $arg ) = @_; + +        delete $arg_spec->{$arg}; +    }; + +    foreach my $source (@{$sources}) { +        my ( $name, $options ) = @{$source}{qw/name contents/}; +        if ( ref($options) ne 'ARRAY' ) { +            $source->{contents} = $options = +                [ Text::ParseWords::shellwords($options) ]; +        } + +        for my $j ( 0 .. @{$options}-1 ) { +            next unless $options->[$j] =~ /^-/; +            my @chunk = ( $options->[$j] ); +            push @chunk, $options->[$j] while ++$j < @{$options} && $options->[$j] !~ /^-/; +            $j--; + +            my @copy = @chunk; +            local @ARGV = @chunk; +            Getopt::Long::GetOptions( +                'type-add=s' => $add_type, +                'type-set=s' => $add_type, +                'type-del=s' => $del_type, +            ); +            Getopt::Long::GetOptions( %{$arg_spec} ); + +            push @new_sources, { +                name     => $name, +                contents => \@copy, +            }; +        } +    } + +    return \@new_sources; +} + + +sub compare_opts { +    my ( $a, $b ) = @_; + +    my $first_a = $a->[0]; +    my $first_b = $b->[0]; + +    $first_a =~ s/^--?//; +    $first_b =~ s/^--?//; + +    return $first_a cmp $first_b; +} + + +sub dump_options { +    my ( $sources ) = @_; + +    $sources = explode_sources($sources); + +    my %opts_by_source; +    my @source_names; + +    foreach my $source (@{$sources}) { +        my ( $name, $contents ) = @{$source}{qw/name contents/}; +        if ( not $opts_by_source{$name} ) { +            $opts_by_source{$name} = []; +            push @source_names, $name; +        } +        push @{$opts_by_source{$name}}, $contents; +    } + +    foreach my $name (@source_names) { +        my $contents = $opts_by_source{$name}; + +        print $name, "\n"; +        print '=' x length($name), "\n"; +        print '  ', join(' ', @{$_}), "\n" foreach sort { compare_opts($a, $b) } @{$contents}; +    } + +    return; +} + + +sub remove_default_options_if_needed { +    my ( $sources ) = @_; + +    my $default_index; + +    foreach my $index ( 0 .. $#{$sources} ) { +        if ( $sources->[$index]{'name'} eq 'Defaults' ) { +            $default_index = $index; +            last; +        } +    } + +    return $sources unless defined $default_index; + +    my $should_remove = 0; + +    # Start with default options, minus some annoying ones. +    Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); +    Getopt::Long::Configure( +        'no_ignore_case', +        'no_auto_abbrev', +        'pass_through', +    ); + +    foreach my $index ( $default_index + 1 .. $#{$sources} ) { +        my ( $name, $args ) = @{$sources->[$index]}{qw/name contents/}; + +        if (ref($args)) { +            local @ARGV = @{$args}; +            Getopt::Long::GetOptions( +                'ignore-ack-defaults' => \$should_remove, +            ); +            @{$args} = @ARGV; +        } +        else { +            ( undef, $sources->[$index]{contents} ) = Getopt::Long::GetOptionsFromString($args, +                'ignore-ack-defaults' => \$should_remove, +            ); +        } +    } + +    Getopt::Long::Configure('default'); +    Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); + +    return $sources unless $should_remove; + +    my @copy = @{$sources}; +    splice @copy, $default_index, 1; +    return \@copy; +} + + +sub check_for_mutually_exclusive_options { +    my ( $arg_sources ) = @_; + +    my %mutually_exclusive_with; +    my @copy = @{$arg_sources}; + +    for(my $i = 0; $i < @INVALID_COMBINATIONS; $i += 2) { +        my ( $lhs, $rhs ) = @INVALID_COMBINATIONS[ $i, $i + 1 ]; + +        foreach my $l_opt ( @{$lhs} ) { +            foreach my $r_opt ( @{$rhs} ) { +                push @{ $mutually_exclusive_with{ $l_opt } }, $r_opt; +                push @{ $mutually_exclusive_with{ $r_opt } }, $l_opt; +            } +        } +    } + +    while( @copy ) { +        my %set_opts; + +        my $source = shift @copy; +        my ( $source_name, $args ) = @{$source}{qw/name contents/}; +        $args = ref($args) ? [ @{$args} ] : [ Text::ParseWords::shellwords($args) ]; + +        foreach my $opt ( @{$args} ) { +            next unless $opt =~ /^[-+]/; +            last if $opt eq '--'; + +            if( $opt =~ /^(.*)=/ ) { +                $opt = $1; +            } +            elsif ( $opt =~ /^(-[^-]).+/ ) { +                $opt = $1; +            } + +            $set_opts{ $opt } = 1; + +            my $mutex_opts = $mutually_exclusive_with{ $opt }; + +            next unless $mutex_opts; + +            foreach my $mutex_opt ( @{$mutex_opts} ) { +                if($set_opts{ $mutex_opt }) { +                    die "Options '$mutex_opt' and '$opt' are mutually exclusive\n"; +                } +            } +        } +    } + +    return; +} + + +sub process_args { +    my $arg_sources = \@_; + +    my %opt = ( +        pager => $ENV{ACK_PAGER_COLOR} || $ENV{ACK_PAGER}, +    ); + +    check_for_mutually_exclusive_options($arg_sources); + +    $arg_sources = remove_default_options_if_needed($arg_sources); + +    if ( should_dump_options($arg_sources) ) { +        dump_options($arg_sources); +        exit(0); +    } + +    my $type_specs = process_filetypes(\%opt, $arg_sources); +    process_other(\%opt, $type_specs, $arg_sources); +    while ( @{$arg_sources} ) { +        my $source = shift @{$arg_sources}; +        my ( $source_name, $args ) = @{$source}{qw/name contents/}; + +        # All of our sources should be transformed into an array ref +        if ( ref($args) ) { +            if ( $source_name eq 'ARGV' ) { +                @ARGV = @{$args}; +            } +            elsif (@{$args}) { +                Carp::croak "source '$source_name' has extra arguments!"; +            } +        } +        else { +            Carp::croak 'The impossible has occurred!'; +        } +    } +    my $filters = ($opt{filters} ||= []); + +    # Throw the default filter in if no others are selected. +    if ( not grep { !$_->is_inverted() } @{$filters} ) { +        push @{$filters}, App::Ack::Filter::Default->new(); +    } +    return \%opt; +} + + +sub retrieve_arg_sources { +    my @arg_sources; + +    my $noenv; +    my $ackrc; + +    Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); +    Getopt::Long::Configure('pass_through'); +    Getopt::Long::Configure('no_auto_abbrev'); + +    Getopt::Long::GetOptions( +        'noenv'   => \$noenv, +        'ackrc=s' => \$ackrc, +    ); + +    Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); + +    my @files; + +    if ( !$noenv ) { +        my $finder = App::Ack::ConfigFinder->new; +        @files  = $finder->find_config_files; +    } +    if ( $ackrc ) { +        # We explicitly use open so we get a nice error message. +        # XXX This is a potential race condition!. +        if(open my $fh, '<', $ackrc) { +            close $fh; +        } +        else { +            die "Unable to load ackrc '$ackrc': $!" +        } +        push( @files, { path => $ackrc } ); +    } + +    push @arg_sources, { +        name     => 'Defaults', +        contents => [ App::Ack::ConfigDefault::options_clean() ], +    }; + +    foreach my $file ( @files) { +        my @lines = App::Ack::ConfigFinder::read_rcfile($file->{path}); + +        if(@lines) { +            push @arg_sources, { +                name     => $file->{path}, +                contents => \@lines, +                project  => $file->{project}, +            }; +        } +    } + +    if ( $ENV{ACK_OPTIONS} && !$noenv ) { +        push @arg_sources, { +            name     => 'ACK_OPTIONS', +            contents => $ENV{ACK_OPTIONS}, +        }; +    } + +    push @arg_sources, { +        name     => 'ARGV', +        contents => [ @ARGV ], +    }; + +    return @arg_sources; +} + +1; # End of App::Ack::ConfigLoader +package App::Ack::Filter; + + +use strict; +use warnings; + +use Carp 1.04 (); + +my %filter_types; + + +sub create_filter { +    my ( undef, $type, @args ) = @_; + +    if ( my $package = $filter_types{$type} ) { +        return $package->new(@args); +    } +    Carp::croak "Unknown filter type '$type'"; +} + + +sub register_filter { +    my ( undef, $type, $package ) = @_; + +    $filter_types{$type} = $package; + +    return; +} + + +sub invert { +    my ( $self ) = @_; + +    return App::Ack::Filter::Inverse->new( $self ); +} + + +sub is_inverted { +    return 0; +} + + +sub to_string { +    my ( $self ) = @_; + +    return '(unimplemented to_string)'; +} + + +sub inspect { +    my ( $self ) = @_; + +    return ref($self); +} + +1; +package App::Ack::Filter::Extension; + + +use strict; +use warnings; +BEGIN { +    our @ISA = 'App::Ack::Filter'; +} + + +sub new { +    my ( $class, @extensions ) = @_; + +    my $exts = join('|', map { "\Q$_\E"} @extensions); +    my $re   = qr/[.](?:$exts)$/i; + +    return bless { +        extensions => \@extensions, +        regex      => $re, +        groupname  => 'ExtensionGroup', +    }, $class; +} + +sub create_group { +    return App::Ack::Filter::ExtensionGroup->new(); +} + +sub filter { +    my ( $self, $resource ) = @_; + +    my $re = $self->{'regex'}; + +    return $resource->name =~ /$re/; +} + +sub inspect { +    my ( $self ) = @_; + +    my $re = $self->{'regex'}; + +    return ref($self) . " - $re"; +} + +sub to_string { +    my ( $self ) = @_; + +    my $exts = $self->{'extensions'}; + +    return join(' ', map { ".$_" } @{$exts}); +} + +BEGIN { +    App::Ack::Filter->register_filter(ext => __PACKAGE__); +} + +1; +package App::Ack::Filter::FirstLineMatch; + + + +use strict; +use warnings; +BEGIN { +    our @ISA = 'App::Ack::Filter'; +} + +sub new { +    my ( $class, $re ) = @_; + +    $re =~ s{^/|/$}{}g; # XXX validate? +    $re = qr{$re}i; + +    return bless { +        regex => $re, +    }, $class; +} + +# This test reads the first 250 characters of a file, then just uses the +# first line found in that. This prevents reading something  like an entire +# .min.js file (which might be only one "line" long) into memory. + +sub filter { +    my ( $self, $resource ) = @_; + +    my $re = $self->{'regex'}; + +    my $line = $resource->firstliney; + +    return $line =~ /$re/; +} + +sub inspect { +    my ( $self ) = @_; + +    my $re = $self->{'regex'}; + +    return ref($self) . " - $re"; +} + +sub to_string { +    my ( $self ) = @_; + +    (my $re = $self->{regex}) =~ s{\([^:]*:(.*)\)$}{$1}; + +    return "first line matches /$re/"; +} + +BEGIN { +    App::Ack::Filter->register_filter(firstlinematch => __PACKAGE__); +} + +1; +package App::Ack::Filter::Is; + + +use strict; +use warnings; +BEGIN { +    our @ISA = 'App::Ack::Filter'; +} + +use File::Spec 3.00 (); + +sub new { +    my ( $class, $filename ) = @_; + +    return bless { +        filename => $filename, +        groupname => 'IsGroup', +    }, $class; +} + +sub create_group { +    return App::Ack::Filter::IsGroup->new(); +} + +sub filter { +    my ( $self, $resource ) = @_; + +    my $filename = $self->{'filename'}; +    my $base     = (File::Spec->splitpath($resource->name))[2]; + +    return $base eq $filename; +} + +sub inspect { +    my ( $self ) = @_; + +    my $filename = $self->{'filename'}; + +    return ref($self) . " - $filename"; +} + +sub to_string { +    my ( $self ) = @_; + +    my $filename = $self->{'filename'}; + +    return $filename; +} + +BEGIN { +    App::Ack::Filter->register_filter(is => __PACKAGE__); +} + +1; +package App::Ack::Filter::Match; + +use strict; +use warnings; +BEGIN { +    our @ISA = 'App::Ack::Filter'; +} + +use File::Spec 3.00; + + +sub new { +    my ( $class, $re ) = @_; + +    $re =~ s{^/|/$}{}g; # XXX validate? +    $re = qr/$re/i; + +    return bless { +        regex => $re, +        groupname => 'MatchGroup', +    }, $class; +} + +sub create_group { +    return App::Ack::Filter::MatchGroup->new; +} + +sub filter { +    my ( $self, $resource ) = @_; + +    my $re = $self->{'regex'}; + +    return $resource->basename =~ /$re/; +} + +sub inspect { +    my ( $self ) = @_; + +    my $re = $self->{'regex'}; + +    print ref($self) . " - $re"; + +    return; +} + +sub to_string { +    my ( $self ) = @_; + +    my $re = $self->{'regex'}; + +    return "filename matches $re"; +} + +BEGIN { +    App::Ack::Filter->register_filter(match => __PACKAGE__); +} + +1; +package App::Ack::Filter::Default; + + +use strict; +use warnings; +BEGIN { +    our @ISA = 'App::Ack::Filter'; +} + +sub new { +    my ( $class ) = @_; + +    return bless {}, $class; +} + +sub filter { +    my ( $self, $resource ) = @_; + +    return -T $resource->name; +} + +1; +package App::Ack::Filter::Inverse; + + + +use strict; +use warnings; +BEGIN { +    our @ISA = 'App::Ack::Filter'; +} + +sub new { +    my ( $class, $filter ) = @_; + +    return bless { +        filter => $filter, +    }, $class; +} + +sub filter { +    my ( $self, $resource ) = @_; + +    my $filter = $self->{'filter'}; +    return !$filter->filter( $resource ); +} + +sub invert { +    my $self = shift; + +    return $self->{'filter'}; +} + +sub is_inverted { +    return 1; +} + +sub inspect { +    my ( $self ) = @_; + +    my $filter = $self->{'filter'}; + +    return "!$filter"; +} + +1; +package App::Ack::Filter::Collection; + + +use strict; +use warnings; +BEGIN { +    our @ISA = 'App::Ack::Filter'; +} + +sub new { +    my ( $class ) = @_; + +    return bless { +        groups => {}, +        ungrouped => [], +    }, $class; +} + +sub filter { +    my ( $self, $resource ) = @_; + +    for my $group (values %{$self->{'groups'}}) { +        if ($group->filter($resource)) { +            return 1; +        } +    } + +    for my $filter (@{$self->{'ungrouped'}}) { +        if ($filter->filter($resource)) { +            return 1; +        } +    } + +    return 0; +} + +sub add { +    my ( $self, $filter ) = @_; + +    if (exists $filter->{'groupname'}) { +        my $group = ($self->{groups}->{$filter->{groupname}} ||= $filter->create_group()); +        $group->add($filter); +    } +    else { +        push @{$self->{'ungrouped'}}, $filter; +    } + +    return; +} + +sub inspect { +    my ( $self ) = @_; + +    return ref($self) . " - $self"; +} + +sub to_string { +    my ( $self ) = @_; + +    my $ungrouped = $self->{'ungrouped'}; + +    return join(', ', map { "($_)" } @{$ungrouped}); +} + +1; +package App::Ack::Filter::IsGroup; + + +use strict; +use warnings; +BEGIN { +    our @ISA = 'App::Ack::Filter'; +} + +use File::Spec 3.00 (); + +sub new { +    my ( $class ) = @_; + +    return bless { +        data => {}, +    }, $class; +} + +sub add { +    my ( $self, $filter ) = @_; + +    $self->{data}->{ $filter->{filename} } = 1; + +    return; +} + +sub filter { +    my ( $self, $resource ) = @_; + +    my $data = $self->{'data'}; +    my $base = $resource->basename; + +    return exists $data->{$base}; +} + +sub inspect { +    my ( $self ) = @_; + +    return ref($self) . " - $self"; +} + +sub to_string { +    my ( $self ) = @_; + +    return join(' ', keys %{$self->{data}}); +} + +1; +package App::Ack::Filter::ExtensionGroup; + + +use strict; +use warnings; +BEGIN { +    our @ISA = 'App::Ack::Filter'; +} + +sub new { +    my ( $class ) = @_; + +    return bless { +        data => {}, +    }, $class; +} + +sub add { +    my ( $self, $filter ) = @_; + +    foreach my $ext (@{$filter->{extensions}}) { +        $self->{data}->{lc $ext} = 1; +    } + +    return; +} + +sub filter { +    my ( $self, $resource ) = @_; + +    if ($resource->name =~ /[.]([^.]*)$/) { +        return exists $self->{'data'}->{lc $1}; +    } + +    return 0; +} + +sub inspect { +    my ( $self ) = @_; + +    return ref($self) . " - $self"; +} + +sub to_string { +    my ( $self ) = @_; + +    return join(' ', map { ".$_" } sort keys %{$self->{data}}); +} + +1; +package App::Ack::Filter::MatchGroup; + + +use strict; +use warnings; +BEGIN { +    our @ISA = 'App::Ack::Filter'; +} + +sub new { +    my ( $class ) = @_; + +    return bless { +        matches => [], +        big_re  => undef, +    }, $class; +} + +sub add { +    my ( $self, $filter ) = @_; + +    push @{ $self->{matches} }, $filter->{regex}; + +    my $re = join('|', map { "(?:$_)" } @{ $self->{matches} }); +    $self->{big_re} = qr/$re/; + +    return; +} + +sub filter { +    my ( $self, $resource ) = @_; + +    my $re = $self->{big_re}; + +    return $resource->basename =~ /$re/; +} + +sub inspect { +    my ( $self ) = @_; + +    # XXX Needs an explicit return. +} + +sub to_string { +    my ( $self ) = @_; + +    # XXX Needs an explicit return. +} + +1; +package App::Ack::Filter::IsPath; + + +use strict; +use warnings; +BEGIN { +    our @ISA = 'App::Ack::Filter'; +} + + +sub new { +    my ( $class, $filename ) = @_; + +    return bless { +        filename => $filename, +        groupname => 'IsPathGroup', +    }, $class; +} + +sub create_group { +    return App::Ack::Filter::IsPathGroup->new(); +} + +sub filter { +    my ( $self, $resource ) = @_; + +    return $resource->name eq $self->{'filename'}; +} + +sub inspect { +    my ( $self ) = @_; + +    my $filename = $self->{'filename'}; + +    return ref($self) . " - $filename"; +} + +sub to_string { +    my ( $self ) = @_; + +    my $filename = $self->{'filename'}; + +    return $filename; +} + +1; +package App::Ack::Filter::IsPathGroup; + + + + +use strict; +use warnings; +BEGIN { +    our @ISA = 'App::Ack::Filter'; +} + +sub new { +    my ( $class ) = @_; + +    return bless { +        data => {}, +    }, $class; +} + +sub add { +    my ( $self, $filter ) = @_; + +    $self->{data}->{ $filter->{filename} } = 1; + +    return; +} + +sub filter { +    my ( $self, $resource ) = @_; + +    my $data = $self->{'data'}; + +    return exists $data->{$resource->name}; +} + +sub inspect { +    my ( $self ) = @_; + +    return ref($self) . " - $self"; +} + +sub to_string { +    my ( $self ) = @_; + +    return join(' ', keys %{$self->{data}}); +} + +1; +package File::Next; + +use strict; +use warnings; + + +our $VERSION = '1.16'; + + + +use File::Spec (); + +our $name; # name of the current file +our $dir;  # dir of the current file + +our %files_defaults; +our %skip_dirs; + +BEGIN { +    %files_defaults = ( +        file_filter     => undef, +        descend_filter  => undef, +        error_handler   => sub { CORE::die $_[0] }, +        warning_handler => sub { CORE::warn @_ }, +        sort_files      => undef, +        follow_symlinks => 1, +        nul_separated   => 0, +    ); +    %skip_dirs = map {($_,1)} (File::Spec->curdir, File::Spec->updir); +} + + +sub files { +    die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__); + +    my ($parms,@queue) = _setup( \%files_defaults, @_ ); + +    return sub { +        my $filter = $parms->{file_filter}; +        while (@queue) { +            my ($dirname,$file,$fullpath) = splice( @queue, 0, 3 ); +            if ( -f $fullpath || -p _ || $fullpath =~ m{^/dev/fd} ) { +                if ( $filter ) { +                    local $_ = $file; +                    local $File::Next::dir = $dirname; +                    local $File::Next::name = $fullpath; +                    next if not $filter->(); +                } +                return wantarray ? ($dirname,$file,$fullpath) : $fullpath; +            } +            if ( -d _ ) { +                unshift( @queue, _candidate_files( $parms, $fullpath ) ); +            } +        } # while + +        return; +    }; # iterator +} + + + + + + +sub from_file { +    die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__); + +    my ($parms,@queue) = _setup( \%files_defaults, @_ ); +    my $err  = $parms->{error_handler}; +    my $warn = $parms->{warning_handler}; + +    my $filename = $queue[1]; + +    if ( !defined($filename) ) { +        $err->( 'Must pass a filename to from_file()' ); +        return undef; +    } + +    my $fh; +    if ( $filename eq '-' ) { +        $fh = \*STDIN; +    } +    else { +        if ( !open( $fh, '<', $filename ) ) { +            $err->( "Unable to open $filename: $!", $! + 0 ); +            return undef; +        } +    } + +    return sub { +        my $filter = $parms->{file_filter}; +        local $/ = $parms->{nul_separated} ? "\x00" : $/; +        while ( my $fullpath = <$fh> ) { +            chomp $fullpath; +            next unless $fullpath =~ /./; +            if ( not ( -f $fullpath || -p _ ) ) { +                $warn->( "$fullpath: No such file" ); +                next; +            } + +            my ($volume,$dirname,$file) = File::Spec->splitpath( $fullpath ); +            if ( $filter ) { +                local $_ = $file; +                local $File::Next::dir  = $dirname; +                local $File::Next::name = $fullpath; +                next if not $filter->(); +            } +            return wantarray ? ($dirname,$file,$fullpath) : $fullpath; +        } # while +        close $fh; + +        return; +    }; # iterator +} + +sub _bad_invocation { +    my $good = (caller(1))[3]; +    my $bad  = $good; +    $bad =~ s/(.+)::/$1->/; +    return "$good must not be invoked as $bad"; +} + +sub sort_standard($$)   { return $_[0]->[1] cmp $_[1]->[1] } +sub sort_reverse($$)    { return $_[1]->[1] cmp $_[0]->[1] } + +sub reslash { +    my $path = shift; + +    my @parts = split( /\//, $path ); + +    return $path if @parts < 2; + +    return File::Spec->catfile( @parts ); +} + + + +sub _setup { +    my $defaults = shift; +    my $passed_parms = ref $_[0] eq 'HASH' ? {%{+shift}} : {}; # copy parm hash + +    my %passed_parms = %{$passed_parms}; + +    my $parms = {}; +    for my $key ( keys %{$defaults} ) { +        $parms->{$key} = +            exists $passed_parms{$key} +                ? delete $passed_parms{$key} +                : $defaults->{$key}; +    } + +    # Any leftover keys are bogus +    for my $badkey ( keys %passed_parms ) { +        my $sub = (caller(1))[3]; +        $parms->{error_handler}->( "Invalid option passed to $sub(): $badkey" ); +    } + +    # If it's not a code ref, assume standard sort +    if ( $parms->{sort_files} && ( ref($parms->{sort_files}) ne 'CODE' ) ) { +        $parms->{sort_files} = \&sort_standard; +    } +    my @queue; + +    for ( @_ ) { +        my $start = reslash( $_ ); +        if (-d $start) { +            push @queue, ($start,undef,$start); +        } +        else { +            push @queue, (undef,$start,$start); +        } +    } + +    return ($parms,@queue); +} + + +sub _candidate_files { +    my $parms   = shift; +    my $dirname = shift; + +    my $dh; +    if ( !opendir $dh, $dirname ) { +        $parms->{error_handler}->( "$dirname: $!", $! + 0 ); +        return; +    } + +    my @newfiles; +    my $descend_filter = $parms->{descend_filter}; +    my $follow_symlinks = $parms->{follow_symlinks}; +    my $sort_sub = $parms->{sort_files}; + +    for my $file ( grep { !exists $skip_dirs{$_} } readdir $dh ) { +        my $has_stat; + +        my $fullpath = File::Spec->catdir( $dirname, $file ); +        if ( !$follow_symlinks ) { +            next if -l $fullpath; +            $has_stat = 1; +        } + +        # Only do directory checking if we have a descend_filter +        if ( $descend_filter ) { +            if ( $has_stat ? (-d _) : (-d $fullpath) ) { +                local $File::Next::dir = $fullpath; +                local $_ = $file; +                next if not $descend_filter->(); +            } +        } +        if ( $sort_sub ) { +            push( @newfiles, [ $dirname, $file, $fullpath ] ); +        } +        else { +            push( @newfiles, $dirname, $file, $fullpath ); +        } +    } +    closedir $dh; + +    if ( $sort_sub ) { +        return map { @{$_} } sort $sort_sub @newfiles; +    } + +    return @newfiles; +} + + +1; # End of File::Next | 
