From 120f8512d5de638f79df98e6c0411bcf0e57943f Mon Sep 17 00:00:00 2001 From: Tony Duckles Date: Sat, 27 Apr 2013 12:07:42 -0500 Subject: [PATCH] ack v2.04 --- bin/ack | 4144 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 2151 insertions(+), 1993 deletions(-) diff --git a/bin/ack b/bin/ack index eaf3e33..2aa5941 100755 --- a/bin/ack +++ b/bin/ack @@ -12,17 +12,17 @@ use strict; use warnings; -use 5.008; +use 5.008008; # XXX Don't make this so brute force # See also: https://github.com/petdance/ack2/issues/89 -use Getopt::Long 2.36 (); +use Getopt::Long 2.35 (); use Carp 1.04 (); -our $VERSION = '2.02'; +our $VERSION = '2.04'; # Check http://beyondgrep.com/ for updates # These are all our globals. @@ -52,7 +52,7 @@ MAIN: { my @keys = ( 'ACKRC', grep { /^ACK_/ } keys %ENV ); delete @ENV{@keys}; } - App::Ack::load_colors(); + load_colors(); Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); Getopt::Long::Configure('pass_through', 'no_auto_abbrev'); @@ -127,7 +127,7 @@ sub _compile_file_filter { my $inverse_filters = [ grep { $_->is_inverted() } @{$filters} ]; @{$filters} = grep { !$_->is_inverted() } @{$filters}; - my %is_member_of_starting_set = map { (App::Ack::get_file_id($_) => 1) } @{$start}; + my %is_member_of_starting_set = map { (get_file_id($_) => 1) } @{$start}; my $ignore_dir_list = $opt->{idirs}; my $dont_ignore_dir_list = $opt->{no_ignore_dirs}; @@ -162,7 +162,7 @@ sub _compile_file_filter { # 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{ App::Ack::get_file_id($File::Next::name) }; + return 1 if $is_member_of_starting_set{ get_file_id($File::Next::name) }; if ( $dont_ignore_dir_list ) { my ( undef, $dirname ) = File::Spec->splitpath($File::Next::name); @@ -189,17 +189,24 @@ sub _compile_file_filter { # command line" wins. return 0 if -p $File::Next::name; + # we can't handle unreadable filenames; report them + unless ( -r _ ) { + if ( $App::Ack::report_bad_filenames ) { + App::Ack::warn( "${File::Next::name}: cannot open file for reading" ); + } + return 0; + } + + my $resource = App::Ack::Resource::Basic->new($File::Next::name); + return 0 if ! $resource; foreach my $filter (@ifiles_filters) { - my $resource = App::Ack::Resource::Basic->new($File::Next::name); - return 0 if ! $resource || $filter->filter($resource); + return 0 if $filter->filter($resource); } my $match_found = 1; if ( @{$filters} ) { $match_found = 0; foreach my $filter (@{$filters}) { - my $resource = App::Ack::Resource::Basic->new($File::Next::name); - return 0 if ! $resource; if ($filter->filter($resource)) { $match_found = 1; last; @@ -209,8 +216,6 @@ sub _compile_file_filter { # Don't bother invoking inverse filters unless we consider the current resource a match if ( $match_found && @{$inverse_filters} ) { foreach my $filter ( @{$inverse_filters} ) { - my $resource = App::Ack::Resource::Basic->new($File::Next::name); - return 0 if ! $resource; if ( not $filter->filter( $resource ) ) { $match_found = 0; last; @@ -225,7 +230,7 @@ sub show_types { my $resource = shift; my $ors = shift; - my @types = App::Ack::filetypes( $resource ); + my @types = filetypes( $resource ); my $types = join( ',', @types ); my $arrow = @types ? ' => ' : ' =>'; App::Ack::print( $resource->name, $arrow, join( ',', @types ), $ors ); @@ -233,2649 +238,2660 @@ sub show_types { return; } -sub main { - my @arg_sources = App::Ack::retrieve_arg_sources(); +# Set default colors, load Term::ANSIColor +sub load_colors { + eval 'use Term::ANSIColor 1.10 ()'; - my $opt = App::Ack::ConfigLoader::process_args( @arg_sources ); + $ENV{ACK_COLOR_MATCH} ||= 'black on_yellow'; + $ENV{ACK_COLOR_FILENAME} ||= 'bold green'; + $ENV{ACK_COLOR_LINENO} ||= 'bold yellow'; - $App::Ack::report_bad_filenames = !$opt->{dont_report_bad_filenames}; + return; +} - if ( $opt->{flush} ) { - $| = 1; - } +# inefficient, but functional +sub filetypes { + my ( $resource ) = @_; - if ( not defined $opt->{color} ) { - $opt->{color} = !App::Ack::output_to_pipe() && !$App::Ack::is_windows; - } - if ( not defined $opt->{heading} and not defined $opt->{break} ) { - $opt->{heading} = $opt->{break} = !App::Ack::output_to_pipe(); - } + my @matches; - if ( defined($opt->{H}) || defined($opt->{h}) ) { - $opt->{show_filename}= $opt->{H} && !$opt->{h}; - } + foreach my $k (keys %App::Ack::mappings) { + my $filters = $App::Ack::mappings{$k}; - if ( my $output = $opt->{output} ) { - $output =~ s{\\}{\\\\}g; - $output =~ s{"}{\\"}g; - $opt->{output} = qq{"$output"}; + foreach my $filter (@{$filters}) { + # clone the resource + my $clone = $resource->clone; + if ( $filter->filter($clone) ) { + push @matches, $k; + last; + } + } } - my $resources; - if ( $App::Ack::is_filter_mode && !$opt->{files_from} ) { # probably -x - $resources = App::Ack::Resources->from_stdin( $opt ); - my $regex = $opt->{regex}; - $regex = shift @ARGV if not defined $regex; - $opt->{regex} = App::Ack::build_regex( $regex, $opt ); + return sort @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 { - 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 - } + # XXX is this the best method? it always hits the FS + if( my ( $dev, $inode ) = (stat($filename))[0, 1] ) { + return join(':', $dev, $inode); } else { - my $regex = $opt->{regex}; - $regex = shift @ARGV if not defined $regex; - $opt->{regex} = App::Ack::build_regex( $regex, $opt ); - } - my @start; - if ( not defined $opt->{files_from} ) { - @start = @ARGV; - } - if ( !exists($opt->{show_filename}) ) { - unless(@start == 1 && !(-d $start[0])) { - $opt->{show_filename} = 1; - } + # XXX this could be better + return $filename; } + } +} - 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" ); - } - } +# Returns a regex object based on a string and command-line options. +# Dies when the regex $str is undefinied (i.e. not given on command line). - $opt->{file_filter} = _compile_file_filter($opt, \@start); - $opt->{descend_filter} = _compile_descend_filter($opt); +sub build_regex { + my $str = shift; + my $opt = shift; - $resources = App::Ack::Resources->from_argv( $opt, \@start ); - } + defined $str or App::Ack::die( 'No regular expression found.' ); + + $str = quotemeta( $str ) if $opt->{Q}; + if ( $opt->{w} ) { + $str = "\\b$str" if $str =~ /^\w/; + $str = "$str\\b" if $str =~ /\w$/; } - App::Ack::set_up_pager( $opt->{pager} ) if defined $opt->{pager}; - my $print_filenames = $opt->{show_filename}; - my $max_count = $opt->{m}; - my $ors = $opt->{print0} ? "\0" : "\n"; - my $only_first = $opt->{1}; + my $regex_is_lc = $str eq lc $str; + if ( $opt->{i} || ($opt->{smart_case} && $regex_is_lc) ) { + $str = "(?i)$str"; + } - my $nmatches = 0; - my $total_count = 0; -RESOURCES: - while ( my $resource = $resources->next ) { - # XXX this variable name combined with what we're trying - # to do makes no sense. + my $re = eval { qr/$str/ }; + if ( !$re ) { + die "Invalid regex '$str':\n $@"; + } - # XXX Combine the -f and -g functions - if ( $opt->{f} ) { - # XXX printing should probably happen inside of App::Ack - if ( $opt->{show_types} ) { - show_types( $resource, $ors ); - } - else { - App::Ack::print( $resource->name, $ors ); - } - ++$nmatches; - last RESOURCES if defined($max_count) && $nmatches >= $max_count; - } - elsif ( $opt->{g} ) { - my $is_match = ( $resource->name =~ /$opt->{regex}/o ); - if ( $opt->{v} ? !$is_match : $is_match ) { - if ( $opt->{show_types} ) { - show_types( $resource, $ors ); - } - else { - App::Ack::print( $resource->name, $ors ); - } - ++$nmatches; - last RESOURCES if defined($max_count) && $nmatches >= $max_count; - } - } - elsif ( $opt->{lines} ) { - my $print_filename = $opt->{show_filename}; - my $passthru = $opt->{passthru}; + return $re; - 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; +my @before_ctx_lines; +my @after_ctx_lines; +my $is_iterating; - App::Ack::iterate($resource, $opt, sub { - chomp; +my $has_printed_something; - if ( $line_numbers{$.} ) { - App::Ack::print_line_with_context($opt, $filename, $_, $.); - } - elsif ( $passthru ) { - App::Ack::print_line_with_options($opt, $filename, $_, $., ':'); - } - return 1; - }); +BEGIN { + $has_printed_something = 0; +} + +sub print_matches_in_resource { + my ( $resource, $opt ) = @_; + + my $passthru = $opt->{passthru}; + my $max_count = $opt->{m} || -1; + my $nmatches = 0; + my $filename = $resource->name; + my $break = $opt->{break}; + my $heading = $opt->{heading}; + my $ors = $opt->{print0} ? "\0" : "\n"; + my $color = $opt->{color}; + my $print_filename = $opt->{show_filename}; + + my $has_printed_for_this_resource = 0; + + $is_iterating = 1; + + local $opt->{before_context} = $opt->{output} ? 0 : $opt->{before_context}; + local $opt->{after_context} = $opt->{output} ? 0 : $opt->{after_context}; + + my $n_before_ctx_lines = $opt->{before_context} || 0; + my $n_after_ctx_lines = $opt->{after_context} || 0; + + @after_ctx_lines = @before_ctx_lines = (); + + my $fh = $resource->open(); + if ( !$fh ) { + if ( $App::Ack::report_bad_filenames ) { + App::Ack::warn( "$filename: $!" ); } - elsif ( $opt->{count} ) { - my $matches_for_this_file = App::Ack::count_matches_in_resource( $resource, $opt ); + return 0; + } - unless ( $opt->{show_filename} ) { - $total_count += $matches_for_this_file; - next RESOURCES; + my $display_filename = $filename; + if ( $print_filename && $heading && $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 ( $n_before_ctx_lines || $n_after_ctx_lines ) { + my $current_line = <$fh>; # prime the first line of input + + while ( defined $current_line ) { + while ( (@after_ctx_lines < $n_after_ctx_lines) && defined($_ = <$fh>) ) { + push @after_ctx_lines, $_; } - if ( !$opt->{l} || $matches_for_this_file > 0) { - if ( $print_filenames ) { - App::Ack::print( $resource->name, ':', $matches_for_this_file, $ors ); + local $_ = $current_line; + my $former_dot_period = $.; + $. -= @after_ctx_lines; + + if ( does_match($opt, $_) ) { + if ( !$has_printed_for_this_resource ) { + if ( $break && $has_printed_something ) { + App::Ack::print_blank_line(); + } + if ( $print_filename && $heading ) { + App::Ack::print_filename( $display_filename, $ors ); + } } - else { - App::Ack::print( $matches_for_this_file, $ors ); + print_line_with_context($opt, $filename, $_, $.); + $has_printed_for_this_resource = 1; + $nmatches++; + $max_count--; + } + elsif ( $passthru ) { + chomp; # XXX proper newline handling? + # XXX inline this call? + if ( $break && !$has_printed_for_this_resource && $has_printed_something ) { + App::Ack::print_blank_line(); } + print_line_with_options($opt, $filename, $_, $., ':'); + $has_printed_for_this_resource = 1; + } + last unless $max_count != 0; + + # I tried doing this with local(), but for some reason, + # $. continued to have its new value after the exit of the + # enclosing block. I'm guessing that $. has some extra + # magic associated with it or something. If someone can + # tell me why this happened, I would love to know! + $. = $former_dot_period; # XXX this won't happen on an exception + + if ( $n_before_ctx_lines ) { + push @before_ctx_lines, $current_line; + shift @before_ctx_lines while @before_ctx_lines > $n_before_ctx_lines; + } + if ( $n_after_ctx_lines ) { + $current_line = shift @after_ctx_lines; + } + else { + $current_line = <$fh>; } } - elsif ( $opt->{l} || $opt->{L} ) { - my $is_match = App::Ack::resource_has_match( $resource, $opt ); - - if ( $opt->{L} ? !$is_match : $is_match ) { - App::Ack::print( $resource->name, $ors ); - ++$nmatches; + } + else { + local $_; - last RESOURCES if $only_first; - last RESOURCES if defined($max_count) && $nmatches >= $max_count; + while ( <$fh> ) { + if ( does_match($opt, $_) ) { + if ( !$has_printed_for_this_resource ) { + if ( $break && $has_printed_something ) { + App::Ack::print_blank_line(); + } + if ( $print_filename && $heading ) { + App::Ack::print_filename( $display_filename, $ors ); + } + } + print_line_with_context($opt, $filename, $_, $.); + $has_printed_for_this_resource = 1; + $nmatches++; + $max_count--; } - } - else { - $nmatches += App::Ack::print_matches_in_resource( $resource, $opt ); - if ( $nmatches && $only_first ) { - last RESOURCES; + elsif ( $passthru ) { + chomp; # XXX proper newline handling? + if ( $break && !$has_printed_for_this_resource && $has_printed_something ) { + App::Ack::print_blank_line(); + } + print_line_with_options($opt, $filename, $_, $., ':'); + $has_printed_for_this_resource = 1; } + last unless $max_count != 0; } } - if ( $opt->{count} && !$opt->{show_filename} ) { - App::Ack::print( $total_count, "\n" ); - } + $is_iterating = 0; # XXX this won't happen on an exception + # then again, do we care? ack doesn't really + # handle exceptions anyway. - close $App::Ack::fh; - App::Ack::exit_from_ack( $nmatches ); + return $nmatches; } +sub print_line_with_options { + my ( $opt, $filename, $line, $line_no, $separator ) = @_; -=head1 NAME + $has_printed_something = 1; -ack - grep-like text finder + my $print_filename = $opt->{show_filename}; + my $print_column = $opt->{column}; + my $ors = $opt->{print0} ? "\0" : "\n"; + my $heading = $opt->{heading}; + my $output_expr = $opt->{output}; + my $color = $opt->{color}; -=head1 SYNOPSIS + my @line_parts; - ack [options] PATTERN [FILE...] - ack -f [options] [DIRECTORY...] + if( $color ) { + $filename = Term::ANSIColor::colored($filename, + $ENV{ACK_COLOR_FILENAME}); + $line_no = Term::ANSIColor::colored($line_no, + $ENV{ACK_COLOR_LINENO}); + } -=head1 DESCRIPTION + if($print_filename) { + if( $heading ) { + push @line_parts, $line_no; + } + else { + push @line_parts, $filename, $line_no; + } -Ack is designed as a replacement for 99% of the uses of F. + if( $print_column ) { + push @line_parts, get_match_column(); + } + } + if( $output_expr ) { + while ( $line =~ /$opt->{regex}/og ) { + my $output = eval $output_expr; + App::Ack::print( join( $separator, @line_parts, $output ), $ors ); + } + } + else { + if ( $color ) { + my @capture_indices = get_capture_indices(); + if( @capture_indices ) { + my $offset = 0; # additional offset for when we add stuff -Ack searches the named input FILEs (or standard input if no files -are named, or the file name - is given) for lines containing a match -to the given PATTERN. By default, ack prints the matching lines. + foreach my $index_pair ( @capture_indices ) { + my ( $match_start, $match_end ) = @{$index_pair}; -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. If you don't know -how to use regular expression but are interested in learning, you may -consult L. If you do not -need or want ack to use regular expressions, please see the -C<-Q>/C<--literal> option. + my $substring = substr( $line, + $offset + $match_start, $match_end - $match_start ); + my $substitution = Term::ANSIColor::colored( $substring, + $ENV{ACK_COLOR_MATCH} ); -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. + substr( $line, $offset + $match_start, + $match_end - $match_start, $substitution ); -=head1 FILE SELECTION + $offset += length( $substitution ) - length( $substring ); + } + } + else { + my $matched = 0; # flag; if matched, need to escape afterwards -If files are not specified for searching, either on the command -line or piped in with the C<-x> option, I delves into -subdirectories selecting files for searching. + while ( $line =~ /$opt->{regex}/og ) { -I 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. + $matched = 1; + my ( $match_start, $match_end ) = ($-[0], $+[0]); -With no file selection, I searches through regular files that -are not explicitly excluded by B<--ignore-dir> and B<--ignore-file> -options, either present in F files or on the command line. + my $substring = substr( $line, $match_start, + $match_end - $match_start ); + my $substitution = Term::ANSIColor::colored( $substring, + $ENV{ACK_COLOR_MATCH} ); -The default options for I ignore certain files and directories. These -include: + substr( $line, $match_start, $match_end - $match_start, + $substitution ); -=over 4 + pos($line) = $match_end + + (length( $substitution ) - length( $substring )); + } + $line .= "\033[0m\033[K" if $matched; + } + } -=item * Backup files: Files matching F<#*#> or ending with F<~>. + push @line_parts, $line; + App::Ack::print( join( $separator, @line_parts ), $ors ); + } -=item * Coredumps: Files matching F + return; +} -=item * Version control directories like F<.svn> and F<.git>. +sub iterate { + my ( $resource, $opt, $cb ) = @_; -=back + $is_iterating = 1; -Run I with the C<--dump> option to see what settings are set. + local $opt->{before_context} = $opt->{output} ? 0 : $opt->{before_context}; + local $opt->{after_context} = $opt->{output} ? 0 : $opt->{after_context}; -However, I always searches the files given on the command line, -no matter what type. If you tell I to search in a coredump, -it will search in a coredump. + my $n_before_ctx_lines = $opt->{before_context} || 0; + my $n_after_ctx_lines = $opt->{after_context} || 0; -=head1 DIRECTORY SELECTION + @after_ctx_lines = @before_ctx_lines = (); -I 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. + my $fh = $resource->open(); + if ( !$fh ) { + if ( $App::Ack::report_bad_filenames ) { + # XXX direct access to filename + App::Ack::warn( "$resource->{filename}: $!" ); + } + return; + } -For a complete list of directories that do not get searched, run -C. + # check for context before the main loop, so we don't + # pay for it if we don't need it + if ( $n_before_ctx_lines || $n_after_ctx_lines ) { + my $current_line = <$fh>; # prime the first line of input -=head1 WHEN TO USE GREP + while ( defined $current_line ) { + while ( (@after_ctx_lines < $n_after_ctx_lines) && defined($_ = <$fh>) ) { + push @after_ctx_lines, $_; + } -I trumps I as an everyday tool 99% of the time, but don't -throw I away, because there are times you'll still need it. + local $_ = $current_line; + my $former_dot_period = $.; + $. -= @after_ctx_lines; -E.g., searching through huge files looking for regexes that can be -expressed with I syntax should be quicker with I. + last unless $cb->(); -If your script or parent program uses I C<--quiet> or C<--silent> -or needs exit 2 on IO error, use I. + # I tried doing this with local(), but for some reason, + # $. continued to have its new value after the exit of the + # enclosing block. I'm guessing that $. has some extra + # magic associated with it or something. If someone can + # tell me why this happened, I would love to know! + $. = $former_dot_period; # XXX this won't happen on an exception -=head1 OPTIONS + if ( $n_before_ctx_lines ) { + push @before_ctx_lines, $current_line; + shift @before_ctx_lines while @before_ctx_lines > $n_before_ctx_lines; + } + if ( $n_after_ctx_lines ) { + $current_line = shift @after_ctx_lines; + } + else { + $current_line = <$fh>; + } + } + } + else { + local $_; -=over 4 + while ( <$fh> ) { + last unless $cb->(); + } + } -=item B<-A I>, B<--after-context=I> + $is_iterating = 0; # XXX this won't happen on an exception + # then again, do we care? ack doesn't really + # handle exceptions anyway. -Print I lines of trailing context after matching lines. + return; +} -=item B<-B I>, B<--before-context=I> +sub get_context { + if ( not $is_iterating ) { + Carp::croak( 'get_context() called outside of iterate()' ); + } -Print I lines of leading context before matching lines. + return ( + scalar(@before_ctx_lines) ? \@before_ctx_lines : undef, + scalar(@after_ctx_lines) ? \@after_ctx_lines : undef, + ); +} -=item B<--[no]break> +} -Print a break between results from different files. On by default -when used interactively. +{ -=item B<-C [I]>, B<--context[=I]> +my $is_first_match; +my $previous_file_processed; +my $previous_line_printed; -Print I lines (default 2) of context around matching lines. +BEGIN { + $is_first_match = 1; + $previous_line_printed = -1; +} -=item B<-c>, B<--count> +sub print_line_with_context { + my ( $opt, $filename, $matching_line, $line_no ) = @_; -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. + my $heading = $opt->{heading}; -If combined with B<-h> (B<--no-filename>) ack outputs only one total -count. + if( !defined($previous_file_processed) || + $previous_file_processed ne $filename ) { + $previous_file_processed = $filename; + $previous_line_printed = -1; -=item B<--[no]color>, B<--[no]colour> + if( $heading ) { + $is_first_match = 1; + } + } -B<--color> highlights the matching text. B<--nocolor> supresses -the color. This is on by default unless the output is redirected. + my $ors = $opt->{print0} ? "\0" : "\n"; + my $match_word = $opt->{w}; + my $is_tracking_context = $opt->{after_context} || $opt->{before_context}; + my $output_expr = $opt->{output}; -On Windows, this option is off by default unless the -L module is installed or the C -environment variable is used. + $matching_line =~ s/[\r\n]+$//g; -=item B<--color-filename=I> + my ( $before_context, $after_context ) = get_context(); -Sets the color to be used for filenames. + if ( $before_context ) { + my $first_line = $. - @{$before_context}; -=item B<--color-match=I> + if ( $first_line <= $previous_line_printed ) { + splice @{$before_context}, 0, $previous_line_printed - $first_line + 1; + $first_line = $. - @{$before_context}; + } + if ( @{$before_context} ) { + my $offset = @{$before_context}; -Sets the color to be used for matches. + if( !$is_first_match && $previous_line_printed != $first_line - 1 ) { + App::Ack::print('--', $ors); + } + foreach my $line (@{$before_context}) { + my $context_line_no = $. - $offset; + if ( $context_line_no <= $previous_line_printed ) { + next; + } -=item B<--color-lineno=I> + chomp $line; + print_line_with_options($opt, $filename, $line, $context_line_no, '-'); + $previous_line_printed = $context_line_no; + $offset--; + } + } + } -Sets the color to be used for line numbers. + if ( $. > $previous_line_printed ) { + if( $is_tracking_context && !$is_first_match && $previous_line_printed != $. - 1 ) { + App::Ack::print('--', $ors); + } -=item B<--[no]column> + print_line_with_options($opt, $filename, $matching_line, $line_no, ':'); + $previous_line_printed = $.; + } -Show the column number of the first match. This is helpful for -editors that can place your cursor at a given position. + if($after_context) { + my $offset = 1; + foreach my $line (@{$after_context}) { + # XXX improve this! + if ( $previous_line_printed >= $. + $offset ) { + $offset++; + next; + } + chomp $line; + my $separator = ($opt->{regex} && does_match( $opt, $line )) ? ':' : '-'; + print_line_with_options($opt, $filename, $line, $. + $offset, $separator); + $previous_line_printed = $. + $offset; + $offset++; + } + } -=item B<--create-ackrc> + $is_first_match = 0; -Dumps the default ack options to standard output. This is useful for -when you want to customize the defaults. + return; +} -=item B<--dump> +} -Writes the list of options loaded and where they came from to standard -output. Handy for debugging. +{ -=item B<--[no]env> +my @capture_indices; +my $match_column_number; -B<--noenv> disables all environment processing. No F<.ackrc> is -read and all environment variables are ignored. By default, F -considers F<.ackrc> and settings in the environment. +# does_match() MUST have an $opt->{regex} set. -=item B<--flush> +sub does_match { + my ( $opt, $line ) = @_; -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> + $match_column_number = undef; + @capture_indices = (); -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. + 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; -=item B<--files-from=I> + if ( @- > 1 ) { + @capture_indices = map { + [ $-[$_], $+[$_] ] + } ( 1 .. $#- ); + } + return 1; + } + else { + return; + } + } +} -The list of files to be searched is specified in I. The list of -files are seperated by newlines. If I is C<->, the list is loaded -from standard input. +sub get_capture_indices { + return @capture_indices; +} -=item B<--[no]filter> +sub get_match_column { + return $match_column_number; +} -Forces ack to act as if it were recieving input via a pipe. +} -=item B<--[no]follow> +sub resource_has_match { + my ( $resource, $opt ) = @_; -Follow or don't follow symlinks, other than whatever starting files -or directories were specified on the command line. + my $has_match = 0; + my $fh = $resource->open(); + if ( !$fh ) { + if ( $App::Ack::report_bad_filenames ) { + # XXX direct access to filename + App::Ack::warn( "$resource->{filename}: $!" ); + } + } + else { + my $opt_v = $opt->{v}; + my $re = $opt->{regex}; + while ( <$fh> ) { + if (/$re/o xor $opt_v) { + $has_match = 1; + last; + } + } + close $fh; + } -This is off by default. + return $has_match; +} -=item B<-g I> +sub count_matches_in_resource { + my ( $resource, $opt ) = @_; -Print files where the relative path + filename matches I. + my $nmatches = 0; + my $fh = $resource->open(); + if ( !$fh ) { + if ( $App::Ack::report_bad_filenames ) { + # XXX direct access to filename + App::Ack::warn( "$resource->{filename}: $!" ); + } + } + else { + my $opt_v = $opt->{v}; + my $re = $opt->{regex}; + while ( <$fh> ) { + ++$nmatches if (/$re/o xor $opt_v); + } + close $fh; + } -=item B<--[no]group> + return $nmatches; +} -B<--group> groups matches by file name. This is the default -when used interactively. +sub main { + my @arg_sources = App::Ack::ConfigLoader::retrieve_arg_sources(); -B<--nogroup> prints one result per line, like grep. This is the -default when output is redirected. + my $opt = App::Ack::ConfigLoader::process_args( @arg_sources ); -=item B<-H>, B<--with-filename> + $App::Ack::report_bad_filenames = !$opt->{dont_report_bad_filenames}; -Print the filename for each match. This is the default unless searching -a single explicitly specified file. + if ( $opt->{flush} ) { + $| = 1; + } -=item B<-h>, B<--no-filename> + if ( not defined $opt->{color} ) { + $opt->{color} = !App::Ack::output_to_pipe() && !$App::Ack::is_windows; + } + if ( not defined $opt->{heading} and not defined $opt->{break} ) { + $opt->{heading} = $opt->{break} = !App::Ack::output_to_pipe(); + } -Suppress the prefixing of filenames on output when multiple files are -searched. + if ( defined($opt->{H}) || defined($opt->{h}) ) { + $opt->{show_filename}= $opt->{H} && !$opt->{h}; + } -=item B<--[no]heading> + if ( my $output = $opt->{output} ) { + $output =~ s{\\}{\\\\}g; + $output =~ s{"}{\\"}g; + $opt->{output} = qq{"$output"}; + } -Print a filename heading above each file's results. This is the default -when used interactively. + my $resources; + if ( $App::Ack::is_filter_mode && !$opt->{files_from} ) { # probably -x + $resources = App::Ack::Resources->from_stdin( $opt ); + my $regex = $opt->{regex}; + $regex = shift @ARGV if not defined $regex; + $opt->{regex} = build_regex( $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 { + my $regex = $opt->{regex}; + $regex = shift @ARGV if not defined $regex; + $opt->{regex} = build_regex( $regex, $opt ); + } + my @start; + if ( not defined $opt->{files_from} ) { + @start = @ARGV; + } + if ( !exists($opt->{show_filename}) ) { + unless(@start == 1 && !(-d $start[0])) { + $opt->{show_filename} = 1; + } + } -=item B<--help>, B<-?> + 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" ); + } + } -Print a short help statement. + $opt->{file_filter} = _compile_file_filter($opt, \@start); + $opt->{descend_filter} = _compile_descend_filter($opt); -=item B<--help-types>, B<--help=types> + $resources = App::Ack::Resources->from_argv( $opt, \@start ); + } + } + App::Ack::set_up_pager( $opt->{pager} ) if defined $opt->{pager}; -Print all known types. + my $print_filenames = $opt->{show_filename}; + my $max_count = $opt->{m}; + my $ors = $opt->{print0} ? "\0" : "\n"; + my $only_first = $opt->{1}; -=item B<-i>, B<--ignore-case> + my $nmatches = 0; + my $total_count = 0; +RESOURCES: + while ( my $resource = $resources->next ) { + # XXX this variable name combined with what we're trying + # to do makes no sense. -Ignore case distinctions in PATTERN + # XXX Combine the -f and -g functions + if ( $opt->{f} ) { + # XXX printing should probably happen inside of App::Ack + if ( $opt->{show_types} ) { + show_types( $resource, $ors ); + } + else { + App::Ack::print( $resource->name, $ors ); + } + ++$nmatches; + last RESOURCES if defined($max_count) && $nmatches >= $max_count; + } + elsif ( $opt->{g} ) { + my $is_match = ( $resource->name =~ /$opt->{regex}/o ); + if ( $opt->{v} ? !$is_match : $is_match ) { + if ( $opt->{show_types} ) { + show_types( $resource, $ors ); + } + else { + App::Ack::print( $resource->name, $ors ); + } + ++$nmatches; + last RESOURCES if defined($max_count) && $nmatches >= $max_count; + } + } + elsif ( $opt->{lines} ) { + my $print_filename = $opt->{show_filename}; + my $passthru = $opt->{passthru}; -=item B<--ignore-ack-defaults> + 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; + } -Tells ack to completely ignore the default definitions provided with ack. -This is useful in combination with B<--create-ackrc> if you I want -to customize ack. + my $filename = $resource->name; -=item B<--[no]ignore-dir=I>, B<--[no]ignore-directory=I> + local $opt->{color} = 0; -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). + iterate($resource, $opt, sub { + chomp; -The I must always be a simple directory name. Nested -directories like F 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. + if ( $line_numbers{$.} ) { + print_line_with_context($opt, $filename, $_, $.); + } + elsif ( $passthru ) { + print_line_with_options($opt, $filename, $_, $., ':'); + } + return 1; + }); + } + elsif ( $opt->{count} ) { + my $matches_for_this_file = count_matches_in_resource( $resource, $opt ); -=item B<--ignore-file=I> + unless ( $opt->{show_filename} ) { + $total_count += $matches_for_this_file; + next RESOURCES; + } -Ignore files matching I. The filters are specified -identically to file type filters as seen in L. + if ( !$opt->{l} || $matches_for_this_file > 0) { + if ( $print_filenames ) { + 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, $opt ); -=item B<-k>, B<--known-types> + if ( $opt->{L} ? !$is_match : $is_match ) { + App::Ack::print( $resource->name, $ors ); + ++$nmatches; -Limit selected files to those with types that ack knows about. This is -equivalent to the default behavior found in ack 1. + last RESOURCES if $only_first; + last RESOURCES if defined($max_count) && $nmatches >= $max_count; + } + } + else { + $nmatches += print_matches_in_resource( $resource, $opt ); + if ( $nmatches && $only_first ) { + last RESOURCES; + } + } + } -=item B<--lines=I> + if ( $opt->{count} && !$opt->{show_filename} ) { + App::Ack::print( $total_count, "\n" ); + } -Only print line I 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. + close $App::Ack::fh; + App::Ack::exit_from_ack( $nmatches ); +} -=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> +=head1 NAME -Only print the filenames of files that do I match. +ack - grep-like text finder -=item B<--match I> +=head1 SYNOPSIS -Specify the I 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. + ack [options] PATTERN [FILE...] + ack -f [options] [DIRECTORY...] - # search for foo and bar in given files - ack file1 t/file* --match foo - ack file1 t/file* --match bar +=head1 DESCRIPTION -=item B<-m=I>, B<--max-count=I> +Ack is designed as a replacement for 99% of the uses of F. -Stop reading a file after I matches. +Ack searches the named input FILEs (or standard input if no files +are named, or the file name - is given) for lines containing a match +to the given PATTERN. By default, ack prints the matching lines. -=item B<--man> +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. If you don't know +how to use regular expression but are interested in learning, you may +consult L. If you do not +need or want ack to use regular expressions, please see the +C<-Q>/C<--literal> option. -Print this manual page. +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. -=item B<-n>, B<--no-recurse> +=head1 FILE SELECTION -No descending into subdirectories. +If files are not specified for searching, either on the command +line or piped in with the C<-x> option, I delves into +subdirectories selecting files for searching. -=item B<-o> +I 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. -Show only the part of each line matching PATTERN (turns off text -highlighting) +With no file selection, I searches through regular files that +are not explicitly excluded by B<--ignore-dir> and B<--ignore-file> +options, either present in F files or on the command line. -=item B<--output=I> +The default options for I ignore certain files and directories. These +include: -Output the evaluation of I 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">. +=over 4 -=item B<--pager=I>, B<--nopager> +=item * Backup files: Files matching F<#*#> or ending with F<~>. -B<--pager> directs ack's output through I. This can also be specified -via the C and C environment variables. +=item * Coredumps: Files matching F -Using --pager does not suppress grouping and coloring like piping -output on the command-line does. +=item * Version control directories like F<.svn> and F<.git>. -B<--nopager> cancels any setting in ~/.ackrc, C or C. -No output will be sent through a pager. +=back -=item B<--passthru> +Run I with the C<--dump> option to see what settings are set. -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: +However, I always searches the files given on the command line, +no matter what type. If you tell I to search in a coredump, +it will search in a coredump. - # Watch a log file, and highlight a certain IP address - $ tail -f ~/access.log | ack --passthru 123.45.67.89 +=head1 DIRECTORY SELECTION -=item B<--print0> +I 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. -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. +For a complete list of directories that do not get searched, run +C. - # remove all files of type html - ack -f --html --print0 | xargs -0 rm -f +=head1 WHEN TO USE GREP -=item B<-Q>, B<--literal> +I trumps I as an everyday tool 99% of the time, but don't +throw I away, because there are times you'll still need it. -Quote all metacharacters in PATTERN, it is treated as a literal. +E.g., searching through huge files looking for regexes that can be +expressed with I syntax should be quicker with I. -=item B<-r>, B<-R>, B<--recurse> +If your script or parent program uses I C<--quiet> or C<--silent> +or needs exit 2 on IO error, use I. -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. +=head1 OPTIONS -=item B<-s> +=over 4 -Suppress error messages about nonexistent or unreadable files. This is taken -from fgrep. +=item B<-A I>, B<--after-context=I> -=item B<--[no]smart-case>, B<--no-smart-case> +Print I lines of trailing context after matching lines. -Ignore case in the search strings if PATTERN contains no uppercase -characters. This is similar to C in vim. This option is -off by default, and ignored if C<-i> is specified. +=item B<-B I>, B<--before-context=I> -B<-i> always overrides this option. +Print I lines of leading context before matching lines. -=item B<--sort-files> +=item B<--[no]break> -Sorts the found files lexicographically. Use this if you want your file -listings to be deterministic between runs of I. +Print a break between results from different files. On by default +when used interactively. -=item B<--show-types> +=item B<-C [I]>, B<--context[=I]> -Outputs the filetypes that ack associates with each file. +Print I lines (default 2) of context around matching lines. -Works with B<-f> and B<-g> options. +=item B<-c>, B<--count> -=item B<--type=[no]TYPE> +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. -Specify the types of files to include or exclude from a search. -TYPE is a filetype, like I or I. B<--type=perl> can -also be specified as B<--perl>, and B<--type=noperl> can be done -as B<--noperl>. +If combined with B<-h> (B<--no-filename>) ack outputs only one total +count. -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. +=item B<--[no]color>, B<--[no]colour> -Type specifications can be repeated and are ORed together. +B<--color> highlights the matching text. B<--nocolor> supresses +the color. This is on by default unless the output is redirected. -See I for a list of valid types. +On Windows, this option is off by default unless the +L module is installed or the C +environment variable is used. -=item B<--type-add I:I:I> +=item B<--color-filename=I> -Files with the given FILTERARGS applied to the given FILTER -are recognized as being of (the existing) type TYPE. -See also L. +Sets the color to be used for filenames. +=item B<--color-match=I> -=item B<--type-set I:I:I> +Sets the color to be used for matches. -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. +=item B<--color-lineno=I> -=item B<--type-del I> +Sets the color to be used for line numbers. -The filters associated with TYPE are removed from Ack, and are no longer considered -for searches. +=item B<--[no]column> -=item B<-v>, B<--invert-match> +Show the column number of the first match. This is helpful for +editors that can place your cursor at a given position. -Invert match: select non-matching lines +=item B<--create-ackrc> -=item B<--version> +Dumps the default ack options to standard output. This is useful for +when you want to customize the defaults. -Display version and copyright information. +=item B<--dump> -=item B<-w>, B<--word-regexp> +Writes the list of options loaded and where they came from to standard +output. Handy for debugging. -Force PATTERN to match only whole words. The PATTERN is wrapped with -C<\b> metacharacters. +=item B<--[no]env> -=item B<-x> +B<--noenv> disables all environment processing. No F<.ackrc> is +read and all environment variables are ignored. By default, F +considers F<.ackrc> and settings in the environment. -An abbreviation for B<--files-from=->; the list of files to search are read -from standard input, with one line per file. +=item B<--flush> -=item B<-1> +B<--flush> flushes output immediately. This is off by default +unless ack is running interactively (when output goes to a pipe or +file). -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<-f> -=item B<--thpppt> +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. -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<--files-from=I> -=item B<--bar> +The list of files to be searched is specified in I. The list of +files are seperated by newlines. If I is C<->, the list is loaded +from standard input. -Check with the admiral for traps. +=item B<--[no]filter> -=back +Forces ack to act as if it were recieving input via a pipe. -=head1 THE .ackrc FILE +=item B<--[no]follow> -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: +Follow or don't follow symlinks, other than whatever starting files +or directories were specified on the command line. - # Always sort the files - --sort-files +This is off by default. - # Always color, even if piping to a another program - --color +=item B<-g I> - # Use "less -r" as my pager - --pager=less -r +Print files where the relative path + filename matches I. -Note that arguments with spaces in them do not need to be quoted, -as they are not interpreted by the shell. Basically, each I -in the F<.ackrc> file is interpreted as one element of C<@ARGV>. +=item B<--[no]group> -F looks in several locations for F<.ackrc> files; the searching -process is detailed in L. These -files are not considered if B<--noenv> is specified on the command line. +B<--group> groups matches by file name. This is the default +when used interactively. -=head1 Defining your own types +B<--nogroup> prints one result per line, like grep. This is the +default when output is redirected. -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. +=item B<-H>, B<--with-filename> -I searches for foo in all perl files. I -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 -does this for you. B<--type-add> appends -additional extensions to an existing type. +Print the filename for each match. This is the default unless searching +a single explicitly specified file. -If you want to define a new type, or completely redefine an existing -type, then use B<--type-set>. I defines -the type I to include files with -the extensions .e or .eiffel. So to search for all eiffel files -containing the word Bertrand use I. -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 -and I<.xs> files no longer belong to the type I. +=item B<-h>, B<--no-filename> -When defining your own types in the F<.ackrc> file you have to use -the following: +Suppress the prefixing of filenames on output when multiple files are +searched. - --type-set=eiffel:ext:e,eiffel +=item B<--[no]heading> -or writing on separate lines +Print a filename heading above each file's results. This is the default +when used interactively. - --type-set - eiffel:ext:e,eiffel +=item B<--help>, B<-?> -The following does B work in the F<.ackrc> file: +Print a short help statement. - --type-set eiffel:ext:e,eiffel +=item B<--help-types>, B<--help=types> +Print all known types. -In order to see all currently defined types, use I<--help-types>, e.g. -I +=item B<-i>, B<--ignore-case> -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 depends on the value -of I. +Ignore case distinctions in PATTERN -=over 4 +=item B<--ignore-ack-defaults> -=item is:I +Tells ack to completely ignore the default definitions provided with ack. +This is useful in combination with B<--create-ackrc> if you I want +to customize ack. -I filters match the target filename exactly. It takes exactly one -argument, which is the name of the file to match. +=item B<--[no]ignore-dir=I>, B<--[no]ignore-directory=I> -Example: +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). - --type-set make:is:Makefile +The I must always be a simple directory name. Nested +directories like F 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 ext:I[,I[,...]] +=item B<--ignore-file=I> -I filters match the extension of the target file against a list -of extensions. No leading dot is needed for the extensions. +Ignore files matching I. The filters are specified +identically to file type filters as seen in L. -Example: +=item B<-k>, B<--known-types> - --type-set perl:ext:pl,pm,t +Limit selected files to those with types that ack knows about. This is +equivalent to the default behavior found in ack 1. -=item match:I +=item B<--lines=I> -I filters match the target filename against a regular expression. -The regular expression is made case insensitive for the search. +Only print line I 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. -Example: +=item B<-l>, B<--files-with-matches> - --type-set make:match:/(gnu)?makefile/ +Only print the filenames of matching files, instead of the matching text. -=item firstlinematch:I +=item B<-L>, B<--files-without-matches> -I matches the first line of the target file against a -regular expression. Like I, the regular expression is made -case insensitive. +Only print the filenames of files that do I match. -Example: +=item B<--match I> - --type-add perl:firstlinematch:/perl/ +Specify the I 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. -=back + # search for foo and bar in given files + ack file1 t/file* --match foo + ack file1 t/file* --match bar -More filter types may be made available in the future. +=item B<-m=I>, B<--max-count=I> -=head1 ENVIRONMENT VARIABLES +Stop reading a file after I matches. -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. +=item B<--man> -=over 4 +Print this manual page. -=item ACKRC +=item B<-n>, B<--no-recurse> -Specifies the location of the user's F<.ackrc> file. If this file doesn't -exist, F looks in the default location. +No descending into subdirectories. -=item ACK_OPTIONS +=item B<-o> -This variable specifies default options to be placed in front of -any explicit options on the command line. +Show only the part of each line matching PATTERN (turns off text +highlighting) -=item ACK_COLOR_FILENAME +=item B<--output=I> -Specifies the color of the filename when it's printed in B<--group> -mode. By default, it's "bold green". +Output the evaluation of I 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">. -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. +=item B<--pager=I>, B<--nopager> -This option can also be set with B<--color-filename>. +B<--pager> directs ack's output through I. This can also be specified +via the C and C environment variables. -=item ACK_COLOR_MATCH +Using --pager does not suppress grouping and coloring like piping +output on the command-line does. -Specifies the color of the matching text when printed in B<--color> -mode. By default, it's "black on_yellow". +B<--nopager> cancels any setting in ~/.ackrc, C or C. +No output will be sent through a pager. -This option can also be set with B<--color-match>. +=item B<--passthru> -See B for the color specifications. +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: -=item ACK_COLOR_LINENO + # Watch a log file, and highlight a certain IP address + $ tail -f ~/access.log | ack --passthru 123.45.67.89 -Specifies the color of the line number when printed in B<--color> -mode. By default, it's "bold yellow". +=item B<--print0> -This option can also be set with B<--color-lineno>. +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. -See B for the color specifications. + # remove all files of type html + ack -f --html --print0 | xargs -0 rm -f -=item ACK_PAGER +=item B<-Q>, B<--literal> -Specifies a pager program, such as C, C or C, to which -ack will send its output. +Quote all metacharacters in PATTERN, it is treated as a literal. -Using C does not suppress grouping and coloring like -piping output on the command-line does, except that on Windows -ack will assume that C does not support color. +=item B<-r>, B<-R>, B<--recurse> -C overrides C if both are specified. +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 ACK_PAGER_COLOR +=item B<-s> -Specifies a pager program that understands ANSI color sequences. -Using C does not suppress grouping and coloring -like piping output on the command-line does. +Suppress error messages about nonexistent or unreadable files. This is taken +from fgrep. -If you are not on Windows, you never need to use C. +=item B<--[no]smart-case>, B<--no-smart-case> -=back +Ignore case in the search strings if PATTERN contains no uppercase +characters. This is similar to C in vim. This option is +off by default, and ignored if C<-i> is specified. -=head1 ACK & OTHER TOOLS +B<-i> always overrides this option. -=head2 Vim integration +=item B<--sort-files> -F integrates easily with the Vim text editor. Set this in your -F<.vimrc> to use F instead of F: +Sorts the found files lexicographically. Use this if you want your file +listings to be deterministic between runs of I. - set grepprg=ack\ -k +=item B<--show-types> -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 and easily step through the results in Vim: +Outputs the filetypes that ack associates with each file. - :grep Dumper perllib +Works with B<-f> and B<-g> options. -Miles Sterrett has written a Vim plugin for F which allows you to use -C<:Ack> instead of C<:grep>, as well as several other advanced features. +=item B<--type=[no]TYPE> -L +Specify the types of files to include or exclude from a search. +TYPE is a filetype, like I or I. B<--type=perl> can +also be specified as B<--perl>, and B<--type=noperl> can be done +as B<--noperl>. -=head2 Emacs integration +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. -Phil Jackson put together an F extension that "provides a -simple compilation mode ... has the ability to guess what files you -want to search for based on the major-mode." +Type specifications can be repeated and are ORed together. -L +See I for a list of valid types. -=head2 TextMate integration +=item B<--type-add I:I:I> -Pedro Melo is a TextMate user who writes "I spend my day mostly -inside TextMate, and the built-in find-in-project sucks with large -projects. So I hacked a TextMate command that was using find + -grep to use ack. The result is the Search in Project with ack, and -you can find it here: -L" +Files with the given FILTERARGS applied to the given FILTER +are recognized as being of (the existing) type TYPE. +See also L. -=head2 Shell and Return Code -For greater compatibility with I, I in normal use returns -shell return or exit code of 0 only if something is found and 1 if -no match is found. +=item B<--type-set I:I:I> -(Shell exit code 1 is C<$?=256> in perl with C or backticks.) +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. -The I code 2 for errors is not used. +=item B<--type-del I> -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. +The filters associated with TYPE are removed from Ack, and are no longer considered +for searches. -=cut +=item B<-v>, B<--invert-match> -=head1 DEBUGGING ACK PROBLEMS +Invert match: select non-matching lines -If ack gives you output you're not expecting, start with a few simple steps. +=item B<--version> -=head2 Use B<--noenv> +Display version and copyright information. -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>. +=item B<-w>, B<--word-regexp> -=head2 Use B<-f> to see what files have been selected +Force PATTERN to match only whole words. The PATTERN is wrapped with +C<\b> metacharacters. -Ack's B<-f> was originally added as a debugging tool. If ack is -not finding matches you think it should find, run 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. +=item B<-x> -=head2 Use B<--dump> +An abbreviation for B<--files-from=->; the list of files to search are read +from standard input, with one line per file. -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. +=item B<-1> -=head1 TIPS +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. -=head2 Use the F<.ackrc> file. +=item B<--thpppt> -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. +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. -=head2 Use F<-f> for working with big codesets +=item B<--bar> -Ack does more than search files. C will create a -list of all the Perl files in a tree, ideal for sending into F. -For example: +Check with the admiral for traps. - # Change all "this" to "that" in all Perl files in a tree. - ack -f --perl | xargs perl -p -i -e's/this/that/g' +=back -or if you prefer: +=head1 THE .ackrc FILE - perl -p -i -e's/this/that/g' $(ack -f --perl) +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: -=head2 Use F<-Q> when in doubt about metacharacters + # Always sort the files + --sort-files -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... + # Always color, even if piping to a another program + --color -=head2 Use ack to watch log files + # Use "less -r" as my pager + --pager=less -r -Here's one I used the other day to find trouble spots for a website -visitor. The user had a problem loading F, so I -took the access log and scanned it with ack twice. +Note that arguments with spaces in them do not need to be quoted, +as they are not interpreted by the shell. Basically, each I +in the F<.ackrc> file is interpreted as one element of C<@ARGV>. - ack -Q aa.bb.cc.dd /path/to/access.log | ack -Q -B5 troublesome.gif +F looks in several locations for F<.ackrc> files; the searching +process is detailed in L. These +files are not considered if B<--noenv> is specified on the command line. -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. +=head1 Defining your own types -=head2 Examples of F<--output> +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. -Following variables are useful in the expansion string: +I searches for foo in all perl files. I +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 +does this for you. B<--type-add> appends +additional extensions to an existing type. -=over 4 +If you want to define a new type, or completely redefine an existing +type, then use B<--type-set>. I defines +the type I to include files with +the extensions .e or .eiffel. So to search for all eiffel files +containing the word Bertrand use I. +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 +and I<.xs> files no longer belong to the type I. -=item C<$&> +When defining your own types in the F<.ackrc> file you have to use +the following: -The whole string matched by PATTERN. + --type-set=eiffel:ext:e,eiffel -=item C<$1>, C<$2>, ... +or writing on separate lines -The contents of the 1st, 2nd ... bracketed group in PATTERN. + --type-set + eiffel:ext:e,eiffel -=item C<$`> +The following does B work in the F<.ackrc> file: -The string before the match. + --type-set eiffel:ext:e,eiffel -=item C<$'> -The string after the match. +In order to see all currently defined types, use I<--help-types>, e.g. +I -=back +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 depends on the value +of I. -For more details and other variables see -L. +=over 4 -This example shows how to add text around a particular pattern -(in this case adding _ around word with "e") +=item is:I - 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 +I filters match the target filename exactly. It takes exactly one +argument, which is the name of the file to match. -This shows how to pick out particular parts of a match using ( ) within regular expression. +Example: - ack '=head(\d+)\s+(.*)' --output=' $1 : $2' - input file contains "=head1 NAME" - output "1 : NAME" + --type-set make:is:Makefile -=head2 Share your knowledge +=item ext:I[,I[,...]] -Join the ack-users mailing list. Send me your tips and I may add -them here. +I filters match the extension of the target file against a list +of extensions. No leading dot is needed for the extensions. -=head1 FAQ +Example: -=head2 Why isn't ack finding a match in (some file)? + --type-set perl:ext:pl,pm,t -Probably because it's of a type that ack doesn't recognize. ack's -searching behavior is driven by filetype. B +=item match:I -Use the C<-f> switch to see a list of files that ack will search -for you. +I filters match the target filename against a regular expression. +The regular expression is made case insensitive for the search. -If you want ack to search files that it doesn't recognize, use the -C<-a> switch. +Example: -If you want ack to search every file, even ones that it always -ignores like coredumps and backup files, use the C<-u> switch. + --type-set make:match:/(gnu)?makefile/ -=head2 Why does ack ignore unknown files by default? +=item firstlinematch:I -ack is designed by a programmer, for programmers, for searching -large trees of code. Most codebases have a lot files in them which -aren't source files (like compiled object files, source control -metadata, etc), and grep wastes a lot of time searching through all -of those as well and returning matches from those files. +I matches the first line of the target file against a +regular expression. Like I, the regular expression is made +case insensitive. -That's why ack's behavior of not searching things it doesn't recognize -is one of its greatest strengths: the speed you get from only -searching the things that you want to be looking at. +Example: -=head2 Wouldn't it be great if F did search & replace? + --type-add perl:firstlinematch:/perl/ -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. +=back -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: +More filter types may be made available in the future. - $ perl -i -p -e's/foo/bar/g' $(ack -f --php) +=head1 ENVIRONMENT VARIABLES -=head2 Can you make ack recognize F<.xyz> files? +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. -Yes! Please see L. If you think -that F should recognize a type by default, please see -L. +=over 4 -=head2 There's already a program/package called ack. +=item ACKRC -Yes, I know. +Specifies the location of the user's F<.ackrc> file. If this file doesn't +exist, F looks in the default location. -=head2 Why is it called ack if it's called ack-grep? +=item ACK_OPTIONS -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. +This variable specifies default options to be placed in front of +any explicit options on the command line. -I suggest you make a symlink named F that points to F -because one of the crucial benefits of ack is having a name that's -so short and simple to type. +=item ACK_COLOR_FILENAME -To do that, run this with F or as root: +Specifies the color of the filename when it's printed in B<--group> +mode. By default, it's "bold green". - ln -s /usr/bin/ack-grep /usr/bin/ack +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. -Alternatively, you could use a shell alias: +This option can also be set with B<--color-filename>. - # bash/zsh - alias ack=ack-grep +=item ACK_COLOR_MATCH - # csh - alias ack ack-grep +Specifies the color of the matching text when printed in B<--color> +mode. By default, it's "black on_yellow". -=head2 What does F mean? +This option can also be set with B<--color-match>. -Nothing. I wanted a name that was easy to type and that you could -pronounce as a single syllable. +See B for the color specifications. -=head2 Can I do multi-line regexes? +=item ACK_COLOR_LINENO -No, ack does not support regexes that match multiple lines. Doing -so would require reading in the entire file at a time. +Specifies the color of the line number when printed in B<--color> +mode. By default, it's "bold yellow". -If you want to see lines near your match, use the C<--A>, C<--B> -and C<--C> switches for displaying context. +This option can also be set with B<--color-lineno>. -=head2 Why is ack telling me I have an invalid option when searching for C<+foo>? +See B for the color specifications. -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!) +=item ACK_PAGER -=head1 ACKRC LOCATION SEMANTICS +Specifies a pager program, such as C, C or C, to which +ack will send its output. -Ack can load its configuration from many sources. This list -specifies the sources Ack looks for configuration; 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) +Using C does not suppress grouping and coloring like +piping output on the command-line does, except that on Windows +ack will assume that C does not support color. -=over 4 +C overrides C if both are specified. -=item * +=item ACK_PAGER_COLOR -Defaults are loaded from App::Ack::ConfigDefaults. This can be omitted -using C<--ignore-ack-defaults>. +Specifies a pager program that understands ANSI color sequences. +Using C does not suppress grouping and coloring +like piping output on the command-line does. -=item * Global ackrc +If you are not on Windows, you never need to use C. -Options are then loaded from the global ackrc. This is located at -C on Unix-like systems, and -C on Windows. -This can be omitted using C<--noenv>. +=back -=item * User ackrc +=head1 ACK & OTHER TOOLS -Options are then loaded from the user's ackrc. This is located at -C<$HOME/.ackrc> on Unix-like systems, and -C. If a different -ackrc is desired, it may be overriden with the C<$ACKRC> environment -variable. -This can be omitted using C<--noenv>. +=head2 Vim integration -=item * Project ackrc +F integrates easily with the Vim text editor. Set this in your +F<.vimrc> to use F instead of F: -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>. + set grepprg=ack\ -k -=item * ACK_OPTIONS +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 and easily step through the results in Vim: -Options are then loaded from the enviroment variable C. This can -be omitted using C<--noenv>. + :grep Dumper perllib -=item * Command line +Miles Sterrett has written a Vim plugin for F which allows you to use +C<:Ack> instead of C<:grep>, as well as several other advanced features. -Options are then loaded from the command line. +L -=back +=head2 Emacs integration -=head1 DIFFERENCES BETWEEN ACK 1.X AND ACK 2.X +Phil Jackson put together an F extension that "provides a +simple compilation mode ... has the ability to guess what files you +want to search for based on the major-mode." -A lot of changes were made for ack 2; here is a list of them. +L -=head2 GENERAL CHANGES +=head2 TextMate integration -=over 4 +Pedro Melo is a TextMate user who writes "I spend my day mostly +inside TextMate, and the built-in find-in-project sucks with large +projects. So I hacked a TextMate command that was using find + +grep to use ack. The result is the Search in Project with ack, and +you can find it here: +L" -=item * +=head2 Shell and Return Code -When no selectors are specified, ack 1.x only searches through files that -it can map to a file type. ack 2.x, by constrast, 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. +For greater compatibility with I, I in normal use returns +shell return or exit code of 0 only if something is found and 1 if +no match is found. -=item * +(Shell exit code 1 is C<$?=256> in perl with C or backticks.) -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. +The I code 2 for errors is not used. -=item * +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. -ack now loads multiple ackrc files; see L for -details. +=cut -=item * +=head1 DEBUGGING ACK PROBLEMS -ack's default filter definitions aren't special; you may tell ack to -completely disregard them if you don't like them. +If ack gives you output you're not expecting, start with a few simple steps. -=back +=head2 Use B<--noenv> -=head2 REMOVED OPTIONS +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>. -=over 4 +=head2 Use B<-f> to see what files have been selected -=item * +Ack's B<-f> was originally added as a debugging tool. If ack is +not finding matches you think it should find, run 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. -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. +=head2 Use B<--dump> -=item * +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. -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. +=head1 TIPS -=item * +=head2 Use the F<.ackrc> file. -The B<--binary> option has been removed. +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. -=item * +=head2 Use F<-f> for working with big codesets -The B<--skipped> option has been removed. +Ack does more than search files. C will create a +list of all the Perl files in a tree, ideal for sending into F. +For example: -=item * + # Change all "this" to "that" in all Perl files in a tree. + ack -f --perl | xargs perl -p -i -e's/this/that/g' -The B<--text> option has been removed. +or if you prefer: -=item * + perl -p -i -e's/this/that/g' $(ack -f --perl) -The B<--invert-file-match> option has been removed. Instead, you may -use B<-v> with B<-g>. +=head2 Use F<-Q> when in doubt about metacharacters -=back +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 CHANGED OPTIONS +=head2 Use ack to watch log files -=over 4 +Here's one I used the other day to find trouble spots for a website +visitor. The user had a problem loading F, so I +took the access log and scanned it with ack twice. -=item * + ack -Q aa.bb.cc.dd /path/to/access.log | ack -Q -B5 troublesome.gif -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>. +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. -=back +=head2 Examples of F<--output> -=head2 ADDED OPTIONS +Following variables are useful in the expansion string: =over 4 -=item * +=item C<$&> -B<--files-from> was added so that a user may submit a list of filenames as -a list of files to search. +The whole string matched by PATTERN. -=item * +=item C<$1>, C<$2>, ... -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. +The contents of the 1st, 2nd ... bracketed group in PATTERN. -=item * +=item C<$`> -B<-s> was added to tell ack to suppress error messages about non-existent or -unreadable files. +The string before the match. -=item * +=item C<$'> -B<--ignore-directory> and B<--noignore-directory> were added as aliases for -B<--ignore-dir> and B<--noignore-dir> respectively. +The string after the match. -=item * +=back -B<--ignore-file> was added so that users may specify patterns of files to -ignore (ex. /.*~$/). +For more details and other variables see +L. -=item * +This example shows how to add text around a particular pattern +(in this case adding _ around word with "e") -B<--dump> was added to allow users to easily find out which options are -set where. + 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 -=item * +This shows how to pick out particular parts of a match using ( ) within regular expression. -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. + ack '=head(\d+)\s+(.*)' --output=' $1 : $2' + input file contains "=head1 NAME" + output "1 : NAME" -=item * +=head2 Share your knowledge -B<--type-del> was added to selectively remove file type definitions. +Join the ack-users mailing list. Send me your tips and I may add +them here. -=item * +=head1 FAQ -B<--ignore-ack-defaults> was added so that users may ignore ack's default -options in favor of their own. +=head2 Why isn't ack finding a match in (some file)? -=item * +Probably because it's of a type that ack doesn't recognize. ack's +searching behavior is driven by filetype. B -B<--bar> was added so ack users may consult Admiral Ackbar. +Use the C<-f> switch to see a list of files that ack will search +for you. -=back +If you want ack to search files that it doesn't recognize, use the +C<-a> switch. -=head1 AUTHOR +If you want ack to search every file, even ones that it always +ignores like coredumps and backup files, use the C<-u> switch. -Andy Lester, C<< >> - -=head1 BUGS - -Please report any bugs or feature requests to the issues list at -Github: L +=head2 Why does ack ignore unknown files by default? -=head1 ENHANCEMENTS +ack is designed by a programmer, for programmers, for searching +large trees of code. Most codebases have a lot files in them which +aren't source files (like compiled object files, source control +metadata, etc), and grep wastes a lot of time searching through all +of those as well and returning matches from those files. -All enhancement requests MUST first be posted to the ack-users -mailing list at L. I -will not consider a request without it first getting seen by other -ack users. This includes requests for new filetypes. +That's why ack's behavior of not searching things it doesn't recognize +is one of its greatest strengths: the speed you get from only +searching the things that you want to be looking at. -There is a list of enhancements I want to make to F in the ack -issues list at Github: L +=head2 Wouldn't it be great if F did search & replace? -Patches are always welcome, but patches with tests get the most -attention. +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. -=head1 SUPPORT +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: -Support for and information about F can be found at: + $ perl -i -p -e's/foo/bar/g' $(ack -f --php) -=over 4 +=head2 Can you make ack recognize F<.xyz> files? -=item * The ack homepage +Yes! Please see L. If you think +that F should recognize a type by default, please see +L. -L +=head2 There's already a program/package called ack. -=item * The ack-users mailing list +Yes, I know. -L +=head2 Why is it called ack if it's called ack-grep? -=item * The ack issues list at Github +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. -L +I suggest you make a symlink named F that points to F +because one of the crucial benefits of ack is having a name that's +so short and simple to type. -=item * AnnoCPAN: Annotated CPAN documentation +To do that, run this with F or as root: -L + ln -s /usr/bin/ack-grep /usr/bin/ack -=item * CPAN Ratings +Alternatively, you could use a shell alias: -L + # bash/zsh + alias ack=ack-grep -=item * Search CPAN + # csh + alias ack ack-grep -L +=head2 What does F mean? -=item * Git source repository +Nothing. I wanted a name that was easy to type and that you could +pronounce as a single syllable. -L +=head2 Can I do multi-line regexes? -=back +No, ack does not support regexes that match multiple lines. Doing +so would require reading in the entire file at a time. -=head1 ACKNOWLEDGEMENTS +If you want to see lines near your match, use the C<--A>, C<--B> +and C<--C> switches for displaying context. -How appropriate to have Inowledgements! +=head2 Why is ack telling me I have an invalid option when searching for C<+foo>? -Thanks to everyone who has contributed to ack in any way, including -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, -GEbor SzabE, -Tod Hagan, -Michael Hendricks, -Evar ArnfjErE 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 BjErn 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. +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!) -=head1 COPYRIGHT & LICENSE +=head1 ACKRC LOCATION SEMANTICS -Copyright 2005-2013 Andy Lester. +Ack can load its configuration from many sources. This list +specifies the sources Ack looks for configuration; 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) -This program is free software; you can redistribute it and/or modify -it under the terms of the Artistic License v2.0. +=over 4 -See http://www.perlfoundation.org/artistic_license_2_0 or the LICENSE.md -file that comes with the ack distribution. +=item * -=cut -package File::Next; +Defaults are loaded from App::Ack::ConfigDefaults. This can be omitted +using C<--ignore-ack-defaults>. -use strict; -use warnings; +=item * Global ackrc +Options are then loaded from the global ackrc. This is located at +C on Unix-like systems, and +C on Windows. +This can be omitted using C<--noenv>. -our $VERSION = '1.12'; +=item * User ackrc +Options are then loaded from the user's ackrc. This is located at +C<$HOME/.ackrc> on Unix-like systems, and +C. If a different +ackrc is desired, it may be overriden with the C<$ACKRC> environment +variable. +This can be omitted using C<--noenv>. +=item * Project ackrc -use File::Spec (); +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>. -our $name; # name of the current file -our $dir; # dir of the current file +=item * ACK_OPTIONS -our %files_defaults; -our %skip_dirs; +Options are then loaded from the enviroment variable C. This can +be omitted using C<--noenv>. -BEGIN { - %files_defaults = ( - file_filter => undef, - descend_filter => undef, - error_handler => sub { CORE::die @_ }, - warning_handler => sub { CORE::warn @_ }, - sort_files => undef, - follow_symlinks => 1, - nul_separated => 0, - ); - %skip_dirs = map {($_,1)} (File::Spec->curdir, File::Spec->updir); -} +=item * Command line +Options are then loaded from the command line. -sub files { - die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__); +=back - my ($parms,@queue) = _setup( \%files_defaults, @_ ); - my $filter = $parms->{file_filter}; +=head1 DIFFERENCES BETWEEN ACK 1.X AND ACK 2.X - return sub { - while (@queue) { - my ($dirname,$file,$fullpath) = splice( @queue, 0, 3 ); - if ( -f $fullpath || -p $fullpath || $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; - } - elsif ( -d _ ) { - unshift( @queue, _candidate_files( $parms, $fullpath ) ); - } - } # while +A lot of changes were made for ack 2; here is a list of them. - return; - }; # iterator -} +=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 constrast, 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. -sub from_file { - die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__); +=item * - my ($parms,@queue) = _setup( \%files_defaults, @_ ); - my $err = $parms->{error_handler}; - my $warn = $parms->{error_handler}; +ack now loads multiple ackrc files; see L for +details. - my $filename = $queue[1]; +=item * - if ( !defined($filename) ) { - $err->( 'Must pass a filename to from_file()' ); - return undef; - } +ack's default filter definitions aren't special; you may tell ack to +completely disregard them if you don't like them. - my $fh; - if ( $filename eq '-' ) { - $fh = \*STDIN; - } - else { - if ( !open( $fh, '<', $filename ) ) { - $err->( "Unable to open $filename: $!" ); - return undef; - } - } - my $filter = $parms->{file_filter}; +=back - return sub { - 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; - } +=head2 REMOVED OPTIONS - 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; +=over 4 - return; - }; # iterator -} +=item * -sub _bad_invocation { - my $good = (caller(1))[3]; - my $bad = $good; - $bad =~ s/(.+)::/$1->/; - return "$good must not be invoked as $bad"; -} +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. -sub sort_standard($$) { return $_[0]->[1] cmp $_[1]->[1] } -sub sort_reverse($$) { return $_[1]->[1] cmp $_[0]->[1] } +=item * -sub reslash { - my $path = shift; +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. - my @parts = split( /\//, $path ); +=item * - return $path if @parts < 2; +The B<--binary> option has been removed. - return File::Spec->catfile( @parts ); -} +=item * +The B<--skipped> option has been removed. +=item * -sub _setup { - my $defaults = shift; - my $passed_parms = ref $_[0] eq 'HASH' ? {%{+shift}} : {}; # copy parm hash +The B<--text> option has been removed. - my %passed_parms = %{$passed_parms}; +=item * - my $parms = {}; - for my $key ( keys %{$defaults} ) { - $parms->{$key} = - exists $passed_parms{$key} - ? delete $passed_parms{$key} - : $defaults->{$key}; - } +The B<--invert-file-match> option has been removed. Instead, you may +use B<-v> with B<-g>. - # 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" ); - } +=back - # 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; +=head2 CHANGED OPTIONS - for ( @_ ) { - my $start = reslash( $_ ); - if (-d $start) { - push @queue, ($start,undef,$start); - } - else { - push @queue, (undef,$start,$start); - } - } +=over 4 - return ($parms,@queue); -} +=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>. -sub _candidate_files { - my $parms = shift; - my $dirname = shift; +=back - my $dh; - if ( !opendir $dh, $dirname ) { - $parms->{error_handler}->( "$dirname: $!" ); - return; - } +=head2 ADDED OPTIONS - my @newfiles; - my $descend_filter = $parms->{descend_filter}; - my $follow_symlinks = $parms->{follow_symlinks}; - my $sort_sub = $parms->{sort_files}; +=over 4 - for my $file ( grep { !exists $skip_dirs{$_} } readdir $dh ) { - my $has_stat; +=item * - # Only do directory checking if we have a descend_filter - my $fullpath = File::Spec->catdir( $dirname, $file ); - if ( !$follow_symlinks ) { - next if -l $fullpath; - $has_stat = 1; - } +B<--files-from> was added so that a user may submit a list of filenames as +a list of files to search. - 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; +=item * - if ( $sort_sub ) { - return map { @{$_} } sort $sort_sub @newfiles; - } +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. - return @newfiles; -} +=item * +B<-s> was added to tell ack to suppress error messages about non-existent or +unreadable files. -1; # End of File::Next -package App::Ack; +=item * -use warnings; -use strict; +B<--ignore-directory> and B<--noignore-directory> were added as aliases for +B<--ignore-dir> and B<--noignore-dir> respectively. -use Getopt::Long 2.36 (); +=item * +B<--ignore-file> was added so that users may specify patterns of files to +ignore (ex. /.*~$/). -our $VERSION; -our $GIT_REVISION; -our $COPYRIGHT; -BEGIN { - $VERSION = '2.02'; - $COPYRIGHT = 'Copyright 2005-2013 Andy Lester.'; - $GIT_REVISION = q{f3c8827}; -} +=item * -our $fh; +B<--dump> was added to allow users to easily find out which options are +set where. -BEGIN { - $fh = *STDOUT; -} +=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. -our %types; -our %type_wanted; -our %mappings; -our %ignore_dirs; +=item * -our $is_filter_mode; -our $output_to_pipe; +B<--type-del> was added to selectively remove file type definitions. -our $dir_sep_chars; -our $is_cygwin; -our $is_windows; +=item * -use File::Spec 1.00015 (); -use File::Glob 1.00015 ':glob'; +B<--ignore-ack-defaults> was added so that users may ignore ack's default +options in favor of their own. -BEGIN { - # These have to be checked before any filehandle diddling. - $output_to_pipe = not -t *STDOUT; - $is_filter_mode = -p STDIN; +=item * - $is_cygwin = ($^O eq 'cygwin'); - $is_windows = ($^O =~ /MSWin32/); - $dir_sep_chars = $is_windows ? quotemeta( '\\/' ) : quotemeta( File::Spec->catfile( '', '' ) ); -} +B<--bar> was added so ack users may consult Admiral Ackbar. +=back -sub retrieve_arg_sources { - my @arg_sources; +=head1 AUTHOR - my $noenv; - my $ackrc; +Andy Lester, C<< >> - Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); - Getopt::Long::Configure('pass_through'); - Getopt::Long::Configure('no_auto_abbrev'); +=head1 BUGS - Getopt::Long::GetOptions( - 'noenv' => \$noenv, - 'ackrc=s' => \$ackrc, - ); +Please report any bugs or feature requests to the issues list at +Github: L - 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, $ackrc ); - } - - push @arg_sources, Defaults => [ App::Ack::ConfigDefault::options() ]; - - foreach my $file ( @files) { - my @lines = read_rcfile($file); - push ( @arg_sources, $file, \@lines ) if @lines; - } - - if ( $ENV{ACK_OPTIONS} && !$noenv ) { - push( @arg_sources, 'ACK_OPTIONS' => $ENV{ACK_OPTIONS} ); - } - - push( @arg_sources, 'ARGV' => [ @ARGV ] ); - - return @arg_sources; -} - -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 =~ /^#/; - - push( @lines, $line ); - } - close $fh; - - return @lines; -} - - -sub create_ignore_rules { - my $what = shift; - my $where = shift; - my $opts = shift; - - my @opts = @{$opts}; - - my %rules; - - for my $opt ( @opts ) { - if ( $opt =~ /^(is|ext|regex),(.+)$/ ) { - my $method = $1; - my $arg = $2; - if ( $method eq 'regex' ) { - push( @{$rules{regex}}, qr/$arg/ ); - } - else { - ++$rules{$method}{$arg}; - } - } - else { - App::Ack::die( "Invalid argument for --$what: $opt" ); - } - } - - return \%rules; -} - - -sub remove_dir_sep { - my $path = shift; - $path =~ s/[$dir_sep_chars]$//; - - return $path; -} - - -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} ) { - $str = "\\b$str" if $str =~ /^\w/; - $str = "$str\\b" if $str =~ /\w$/; - } - - my $regex_is_lc = $str eq lc $str; - if ( $opt->{i} || ($opt->{smart_case} && $regex_is_lc) ) { - $str = "(?i)$str"; - } - - my $ok = eval { - qr/$str/ - }; - - my $error = $@; - - if ( !$ok ) { - die "Invalid regex '$str':\n $error"; - } - - return $str; -} - - -sub check_regex { - my $regex = shift; - - return unless defined $regex; - - eval { qr/$regex/ }; - if ($@) { - (my $error = $@) =~ s/ at \S+ line \d+.*//; - chomp($error); - App::Ack::die( "Invalid regex '$regex':\n $error" ); - } - - return; -} - - - - -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 ); -} +=head1 ENHANCEMENTS +All enhancement requests MUST first be posted to the ack-users +mailing list at L. 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 in the ack +issues list at Github: L -sub filetypes_supported { - return keys %mappings; -} +Patches are always welcome, but patches with tests get the most +attention. -sub _get_thpppt { - my $y = q{_ /|,\\'!.x',=(www)=, U }; - $y =~ tr/,x!w/\nOo_/; - return $y; -} +=head1 SUPPORT -sub _thpppt { - my $y = _get_thpppt(); - App::Ack::print( "$y ack $_[0]!\n" ); - exit 0; -} +Support for and information about F can be found at: -sub _bar { - 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 +=over 4 - $x =~ s/(.)(.)/$1x(ord($2)-32)/eg; - App::Ack::print( $x ); - exit 0; -} +=item * The ack homepage +L -sub show_help { - my $help_arg = shift || 0; +=item * The ack-users mailing list - return show_help_types() if $help_arg =~ /^types?/; +L - App::Ack::print( <<"END_OF_HELP" ); -Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES] +=item * The ack issues list at Github -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 "-". +L -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. +=item * AnnoCPAN: Annotated CPAN documentation -Example: ack -i select +L -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 +=item * CPAN Ratings -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 +L - -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. +=item * Search CPAN - --print0 Print null byte as separator between filenames, - only works with -f, -g, -l, -L or -c. +L - -s Suppress error messages about nonexistent or - unreadable files. +=item * Git source repository +L -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). +=back +=head1 ACKNOWLEDGEMENTS -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. +How appropriate to have Inowledgements! -File inclusion/exclusion: - --[no]ignore-dir=name Add/Remove directory from the 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 (ack's default behavior) - -n, --no-recurse No descending into subdirectories - --[no]follow Follow symlinks. Default is off. - -k, --known-types Include only files with types that ack recognizes. +Thanks to everyone who has contributed to ack in any way, including +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, +GEbor SzabE, +Tod Hagan, +Michael Hendricks, +Evar ArnfjErE 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 BjErn 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. - --type=X Include only X files, where X is a recognized filetype. - --type=noX Exclude X files. - See "ack --help-types" for supported filetypes. +=head1 COPYRIGHT & LICENSE -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 of type TYPE. - --type-del TYPE Removes all filters associated with TYPE. +Copyright 2005-2013 Andy Lester. +This program is free software; you can redistribute it and/or modify +it under the terms of the Artistic License v2.0. -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 the default definitions that ack includes. - --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 +See http://www.perlfoundation.org/artistic_license_2_0 or the LICENSE.md +file that comes with the ack distribution. -Exit status is 0 if match, 1 if no match. +=cut +package File::Next; -This is version $VERSION of ack. -END_OF_HELP +use strict; +use warnings; - return; - } +our $VERSION = '1.12'; -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. +use File::Spec (); -Note that some extensions may appear in multiple types. For example, -.pod files are both Perl and Parrot. +our $name; # name of the current file +our $dir; # dir of the current file -END_OF_HELP +our %files_defaults; +our %skip_dirs; - 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}; +BEGIN { + %files_defaults = ( + file_filter => undef, + descend_filter => undef, + error_handler => sub { CORE::die @_ }, + warning_handler => sub { CORE::warn @_ }, + sort_files => undef, + follow_symlinks => 1, + nul_separated => 0, + ); + %skip_dirs = map {($_,1)} (File::Spec->curdir, File::Spec->updir); +} - 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 files { + die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__); -sub show_man { - require Pod::Usage; + my ($parms,@queue) = _setup( \%files_defaults, @_ ); + my $filter = $parms->{file_filter}; - Pod::Usage::pod2usage({ - -input => $App::Ack::orig_program_name, - -verbose => 2, - -exitval => 0, - }); + return sub { + while (@queue) { + my ($dirname,$file,$fullpath) = splice( @queue, 0, 3 ); + if ( -f $fullpath || -p $fullpath || $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; + } + elsif ( -d _ ) { + unshift( @queue, _candidate_files( $parms, $fullpath ) ); + } + } # while - return; + return; + }; # iterator } -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; + + + +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->{error_handler}; + + my $filename = $queue[1]; + + if ( !defined($filename) ) { + $err->( 'Must pass a filename to from_file()' ); + return undef; } - my $ver = sprintf( '%vd', $^V ); - my $git_revision = $GIT_REVISION ? " (git commit $GIT_REVISION)" : ''; + my $fh; + if ( $filename eq '-' ) { + $fh = \*STDIN; + } + else { + if ( !open( $fh, '<', $filename ) ) { + $err->( "Unable to open $filename: $!" ); + return undef; + } + } + my $filter = $parms->{file_filter}; - return <<"END_OF_VERSION"; -ack ${VERSION}${git_revision} -Running under Perl $ver at $this_perl + return sub { + 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; + } -$copyright + 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; -This program is free software. You may modify or distribute it -under the terms of the Artistic License v2.0. -END_OF_VERSION + 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 print_version_statement { - App::Ack::print( get_version_statement() ); +sub sort_standard($$) { return $_[0]->[1] cmp $_[1]->[1] } +sub sort_reverse($$) { return $_[1]->[1] cmp $_[0]->[1] } - return; -} +sub reslash { + my $path = shift; + my @parts = split( /\//, $path ); -sub get_copyright { - return $COPYRIGHT; + return $path if @parts < 2; + + return File::Spec->catfile( @parts ); } -sub load_colors { - eval 'use Term::ANSIColor 1.10 ()'; - $ENV{ACK_COLOR_MATCH} ||= 'black on_yellow'; - $ENV{ACK_COLOR_FILENAME} ||= 'bold green'; - $ENV{ACK_COLOR_LINENO} ||= 'bold yellow'; +sub _setup { + my $defaults = shift; + my $passed_parms = ref $_[0] eq 'HASH' ? {%{+shift}} : {}; # copy parm hash - return; -} + my %passed_parms = %{$passed_parms}; + my $parms = {}; + for my $key ( keys %{$defaults} ) { + $parms->{$key} = + exists $passed_parms{$key} + ? delete $passed_parms{$key} + : $defaults->{$key}; + } -# print subs added in order to make it easy for a third party -# module (such as App::Wack) to redefine the display methods -# and show the results in a different way. -sub print { print {$fh} @_; return; } -sub print_first_filename { App::Ack::print( $_[0], "\n" ); return; } -sub print_blank_line { App::Ack::print( "\n" ); return; } -sub print_separator { App::Ack::print( "--\n" ); return; } -sub print_filename { App::Ack::print( $_[0], $_[1] ); return; } -sub print_line_no { App::Ack::print( $_[0], $_[1] ); return; } -sub print_column_no { App::Ack::print( $_[0], $_[1] ); return; } -sub print_count { - my $filename = shift; - my $nmatches = shift; - my $ors = shift; - my $count = shift; - my $show_filename = shift; + # 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 ($show_filename) { - App::Ack::print( $filename ); - App::Ack::print( ':', $nmatches ) if $count; + # 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; } - else { - App::Ack::print( $nmatches ) if $count; + my @queue; + + for ( @_ ) { + my $start = reslash( $_ ); + if (-d $start) { + push @queue, ($start,undef,$start); + } + else { + push @queue, (undef,$start,$start); + } } - App::Ack::print( $ors ); - return; + return ($parms,@queue); } -sub print_count0 { - my $filename = shift; - my $ors = shift; - my $show_filename = shift; - if ($show_filename) { - App::Ack::print( $filename, ':0', $ors ); - } - else { - App::Ack::print( '0', $ors ); +sub _candidate_files { + my $parms = shift; + my $dirname = shift; + + my $dh; + if ( !opendir $dh, $dirname ) { + $parms->{error_handler}->( "$dirname: $!" ); + return; } - return; -} + my @newfiles; + my $descend_filter = $parms->{descend_filter}; + my $follow_symlinks = $parms->{follow_symlinks}; + my $sort_sub = $parms->{sort_files}; -sub set_up_pager { - my $command = shift; + for my $file ( grep { !exists $skip_dirs{$_} } readdir $dh ) { + my $has_stat; - return if App::Ack::output_to_pipe(); + # Only do directory checking if we have a descend_filter + my $fullpath = File::Spec->catdir( $dirname, $file ); + if ( !$follow_symlinks ) { + next if -l $fullpath; + $has_stat = 1; + } - my $pager; - if ( not open( $pager, '|-', $command ) ) { - App::Ack::die( qq{Unable to pipe to pager "$command": $!} ); + 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 ); + } } - $fh = $pager; + closedir $dh; - return; + if ( $sort_sub ) { + return map { @{$_} } sort $sort_sub @newfiles; + } + + return @newfiles; } -sub output_to_pipe { - return $output_to_pipe; -} +1; # End of File::Next +package App::Ack; +use warnings; +use strict; -sub exit_from_ack { - my $nmatches = shift; - my $rc = $nmatches ? 0 : 1; - exit $rc; +our $VERSION; +our $GIT_REVISION; +our $COPYRIGHT; +BEGIN { + $VERSION = '2.04'; + $COPYRIGHT = 'Copyright 2005-2013 Andy Lester.'; + $GIT_REVISION = q{8f405b7}; } -{ +our $fh; -my @capture_indices; -my $match_column_number; +BEGIN { + $fh = *STDOUT; +} -sub does_match { - my ( $opt, $line ) = @_; - my $re = $opt->{regex}; - my $invert = $opt->{v}; +our %types; +our %type_wanted; +our %mappings; +our %ignore_dirs; - return unless $re; +our $is_filter_mode; +our $output_to_pipe; - $match_column_number = undef; - @capture_indices = (); +our $dir_sep_chars; +our $is_cygwin; +our $is_windows; - if ( $invert ? $line !~ /$re/ : $line =~ /$re/ ) { - if ( not $invert ) { - # @- = @LAST_MATCH_START - # @+ = @LAST_MATCH_END - $match_column_number = $-[0] + 1; +use File::Spec 1.00015 (); - if ( @- > 1 ) { - @capture_indices = map { - [ $-[$_], $+[$_] ] - } ( 1 .. $#- ); - } - } - return 1; - } - else { - return; - } -} +BEGIN { + # These have to be checked before any filehandle diddling. + $output_to_pipe = not -t *STDOUT; + $is_filter_mode = -p STDIN; -sub get_capture_indices { - return @capture_indices; + $is_cygwin = ($^O eq 'cygwin'); + $is_windows = ($^O =~ /MSWin32/); + $dir_sep_chars = $is_windows ? quotemeta( '\\/' ) : quotemeta( File::Spec->catfile( '', '' ) ); } -sub get_match_column { - return $match_column_number; -} + +sub remove_dir_sep { + my $path = shift; + $path =~ s/[$dir_sep_chars]$//; + + return $path; } -sub print_matches_in_resource { - my ( $resource, $opt ) = @_; - my $passthru = $opt->{passthru}; - my $max_count = $opt->{m} || -1; - my $nmatches = 0; - my $filename = $resource->name; - my $break = $opt->{break}; - my $heading = $opt->{heading}; - my $ors = $opt->{print0} ? "\0" : "\n"; - my $color = $opt->{color}; - my $print_filename = $opt->{show_filename}; - my $has_printed_for_this_resource = 0; +sub warn { + return CORE::warn( _my_program(), ': ', @_, "\n" ); +} - App::Ack::iterate($resource, $opt, sub { - if ( App::Ack::does_match($opt, $_) ) { - if( !$has_printed_for_this_resource ) { - if( $break && has_printed_something() ) { - App::Ack::print_blank_line(); - } - if( $print_filename) { - if( $heading ) { - my $filename = $resource->name; - if($color) { - $filename = Term::ANSIColor::colored($filename, - $ENV{ACK_COLOR_FILENAME}); - } - App::Ack::print_filename( $filename, $ors ); - } - } - } - App::Ack::print_line_with_context($opt, $filename, $_, $.); - $has_printed_for_this_resource = 1; - $nmatches++; - $max_count--; - } - elsif ( $passthru ) { - chomp; - if( $break && !$has_printed_for_this_resource && has_printed_something() ) { - App::Ack::print_blank_line(); - } - App::Ack::print_line_with_options($opt, $filename, $_, $., ':'); - $has_printed_for_this_resource = 1; - } - return $max_count != 0; - }); - return $nmatches; +sub die { + return CORE::die( _my_program(), ': ', @_, "\n" ); } -sub count_matches_in_resource { - my ( $resource, $opt ) = @_; +sub _my_program { + require File::Basename; + return File::Basename::basename( $0 ); +} - my $nmatches = 0; - App::Ack::iterate( $resource, $opt, sub { - ++$nmatches if App::Ack::does_match($opt, $_); - return 1; - } ); - return $nmatches; +sub filetypes_supported { + return keys %mappings; } -sub resource_has_match { - my ( $resource, $opt ) = @_; +sub _get_thpppt { + my $y = q{_ /|,\\'!.x',=(www)=, U }; + $y =~ tr/,x!w/\nOo_/; + return $y; +} - return count_matches_in_resource($resource, $opt) > 0; +sub _thpppt { + my $y = _get_thpppt(); + App::Ack::print( "$y ack $_[0]!\n" ); + exit 0; } -{ +sub _bar { + 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 -my @before_ctx_lines; -my @after_ctx_lines; -my $is_iterating; + $x =~ s/(.)(.)/$1x(ord($2)-32)/eg; + App::Ack::print( $x ); + exit 0; +} -sub get_context { - if ( not $is_iterating ) { - Carp::croak( 'get_context() called outside of iterate()' ); - } - return ( - scalar(@before_ctx_lines) ? \@before_ctx_lines : undef, - scalar(@after_ctx_lines) ? \@after_ctx_lines : undef, - ); -} +sub show_help { + my $help_arg = shift || 0; -sub iterate { - my ( $resource, $opt, $cb ) = @_; + return show_help_types() if $help_arg =~ /^types?/; - $is_iterating = 1; + App::Ack::print( <<"END_OF_HELP" ); +Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES] - local $opt->{before_context} = $opt->{output} ? 0 : $opt->{before_context}; - local $opt->{after_context} = $opt->{output} ? 0 : $opt->{after_context}; +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 - my $n_before_ctx_lines = $opt->{before_context} || 0; - my $n_after_ctx_lines = $opt->{after_context} || 0; - my $current_line; +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 - @after_ctx_lines = @before_ctx_lines = (); +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 - if ( $resource->next_text() ) { - $current_line = $_; # prime the first line of input - } + -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. - while ( defined $current_line ) { - while ( (@after_ctx_lines < $n_after_ctx_lines) && $resource->next_text() ) { - push @after_ctx_lines, $_; - } + --print0 Print null byte as separator between filenames, + only works with -f, -g, -l, -L or -c. - local $_ = $current_line; - my $former_dot_period = $.; - $. = $. - @after_ctx_lines; + -s Suppress error messages about nonexistent or + unreadable files. - last unless $cb->(); - # I tried doing this with local(), but for some reason, - # $. continued to have its new value after the exit of the - # enclosing block. I'm guessing that $. has some extra - # magic associated with it or something. If someone can - # tell me why this happened, I would love to know! - $. = $former_dot_period; # XXX this won't happen on an exception +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). - push @before_ctx_lines, $current_line; -if($n_after_ctx_lines) { - $current_line = shift @after_ctx_lines; - } - elsif($resource->next_text()) { - $current_line = $_; - } - else { - undef $current_line; - } - shift @before_ctx_lines while @before_ctx_lines > $n_before_ctx_lines; - } - $is_iterating = 0; # XXX this won't happen on an exception - # then again, do we care? ack doesn't really - # handle exceptions anyway. +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. - return; -} +File inclusion/exclusion: + --[no]ignore-dir=name Add/Remove directory from the 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 (ack's default behavior) + -n, --no-recurse No descending into subdirectories + --[no]follow Follow symlinks. Default is off. + -k, --known-types Include only files with 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. -my $has_printed_something; +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 of type TYPE. + --type-del TYPE Removes all filters associated with TYPE. -BEGIN { - $has_printed_something = 0; -} -sub has_printed_something { - return $has_printed_something; -} +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 the default definitions that ack includes. + --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 -sub print_line_with_options { - my ( $opt, $filename, $line, $line_no, $separator ) = @_; +Exit status is 0 if match, 1 if no match. - $has_printed_something = 1; +This is version $VERSION of ack. +END_OF_HELP - my $print_filename = $opt->{show_filename}; - my $print_column = $opt->{column}; - my $ors = $opt->{print0} ? "\0" : "\n"; - my $heading = $opt->{heading}; - my $output_expr = $opt->{output}; - my $re = $opt->{regex}; - my $color = $opt->{color}; + return; + } - my @line_parts; - if( $color ) { - $filename = Term::ANSIColor::colored($filename, - $ENV{ACK_COLOR_FILENAME}); - $line_no = Term::ANSIColor::colored($line_no, - $ENV{ACK_COLOR_LINENO}); - } - if($print_filename) { - if( $heading ) { - push @line_parts, $line_no; - } - else { - push @line_parts, $filename, $line_no; - } +sub show_help_types { + App::Ack::print( <<'END_OF_HELP' ); +Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES] - if( $print_column ) { - push @line_parts, get_match_column(); - } - } - if( $output_expr ) { - # XXX avoid re-evaluation if we can - while( $line =~ /$re/g ) { - my $output = eval $output_expr; - App::Ack::print( join( $separator, @line_parts, $output ), $ors ); - } - } - else { - my @capture_indices = get_capture_indices(); - if( @capture_indices ) { - my $offset = 0; # additional offset for when we add stuff +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. - foreach my $index_pair ( @capture_indices ) { - my ( $match_start, $match_end ) = @{$index_pair}; +Note that some extensions may appear in multiple types. For example, +.pod files are both Perl and Parrot. - my $substring = substr( $line, - $offset + $match_start, $match_end - $match_start ); - my $substitution = Term::ANSIColor::colored( $substring, - $ENV{ACK_COLOR_MATCH} ); +END_OF_HELP - substr( $line, $offset + $match_start, - $match_end - $match_start, $substitution ); + 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}; - $offset += length( $substitution ) - length( $substring ); - } - } - elsif( $color ) { - # XXX I know $& is a no-no; fix it later - if($line =~ s/$re/Term::ANSIColor::colored($&, $ENV{ACK_COLOR_MATCH})/ge) { - $line .= "\033[0m\033[K"; - } + if ( ref $ext_list ) { + $ext_list = join( '; ', map { $_->to_string } @{$ext_list} ); } - - push @line_parts, $line; - App::Ack::print( join( $separator, @line_parts ), $ors ); + App::Ack::print( sprintf( " --[no]%-*.*s %s\n", $maxlen, $maxlen, $type, $ext_list ) ); } return; } -{ +sub show_man { + require Pod::Usage; -my $is_first_match; -my $previous_file_processed; -my $previous_line_printed; + Pod::Usage::pod2usage({ + -input => $App::Ack::orig_program_name, + -verbose => 2, + -exitval => 0, + }); -BEGIN { - $is_first_match = 1; - $previous_line_printed = -1; + return; } -sub print_line_with_context { - my ( $opt, $filename, $matching_line, $line_no ) = @_; - - my $heading = $opt->{heading}; - if( !defined($previous_file_processed) || - $previous_file_processed ne $filename ) { - $previous_file_processed = $filename; - $previous_line_printed = -1; +sub get_version_statement { + require Config; - if( $heading ) { - $is_first_match = 1; - } + 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 ); - my $ors = $opt->{print0} ? "\0" : "\n"; - my $match_word = $opt->{w}; - my $re = $opt->{regex}; - my $is_tracking_context = $opt->{after_context} || $opt->{before_context}; - my $output_expr = $opt->{output}; - - chomp $matching_line; - - my ( $before_context, $after_context ) = get_context(); - - if ( $before_context ) { - my $first_line = $. - @{$before_context}; - - if ( $first_line <= $previous_line_printed ) { - splice @{$before_context}, 0, $previous_line_printed - $first_line + 1; - $first_line = $. - @{$before_context}; - } - if ( @{$before_context} ) { - my $offset = @{$before_context}; - - if( !$is_first_match && $previous_line_printed != $first_line - 1 ) { - App::Ack::print('--', $ors); - } - foreach my $line (@{$before_context}) { - my $context_line_no = $. - $offset; - if ( $context_line_no <= $previous_line_printed ) { - next; - } - - chomp $line; - App::Ack::print_line_with_options($opt, $filename, $line, $context_line_no, '-'); - $previous_line_printed = $context_line_no; - $offset--; - } - } - } + my $git_revision = $GIT_REVISION ? " (git commit $GIT_REVISION)" : ''; - if ( $. > $previous_line_printed ) { - if( $is_tracking_context && !$is_first_match && $previous_line_printed != $. - 1 ) { - App::Ack::print('--', $ors); - } + return <<"END_OF_VERSION"; +ack ${VERSION}${git_revision} +Running under Perl $ver at $this_perl - App::Ack::print_line_with_options($opt, $filename, $matching_line, $line_no, ':'); - $previous_line_printed = $.; - } +$copyright - if($after_context) { - my $offset = 1; - foreach my $line (@{$after_context}) { - # XXX improve this! - if ( $previous_line_printed >= $. + $offset ) { - $offset++; - next; - } - chomp $line; - my $separator = App::Ack::does_match( $opt, $line ) ? ':' : '-'; - App::Ack::print_line_with_options($opt, $filename, $line, $. + $offset, $separator); - $previous_line_printed = $. + $offset; - $offset++; - } - } +This program is free software. You may modify or distribute it +under the terms of the Artistic License v2.0. +END_OF_VERSION +} - $is_first_match = 0; + +sub print_version_statement { + App::Ack::print( get_version_statement() ); return; } -} -# inefficient, but functional -sub filetypes { - my ( $resource ) = @_; +sub get_copyright { + return $COPYRIGHT; +} - my @matches; - foreach my $k (keys %mappings) { - my $filters = $mappings{$k}; +# print subs added in order to make it easy for a third party +# module (such as App::Wack) to redefine the display methods +# and show the results in a different way. +sub print { print {$fh} @_; return; } +sub print_first_filename { App::Ack::print( $_[0], "\n" ); return; } +sub print_blank_line { App::Ack::print( "\n" ); return; } +sub print_separator { App::Ack::print( "--\n" ); return; } +sub print_filename { App::Ack::print( $_[0], $_[1] ); return; } +sub print_line_no { App::Ack::print( $_[0], $_[1] ); return; } +sub print_column_no { App::Ack::print( $_[0], $_[1] ); return; } +sub print_count { + my $filename = shift; + my $nmatches = shift; + my $ors = shift; + my $count = shift; + my $show_filename = shift; - foreach my $filter (@{$filters}) { - # clone the resource - my $clone = $resource->clone; - if ( $filter->filter($clone) ) { - push @matches, $k; - last; - } - } + if ($show_filename) { + App::Ack::print( $filename ); + App::Ack::print( ':', $nmatches ) if $count; + } + else { + App::Ack::print( $nmatches ) if $count; } + App::Ack::print( $ors ); - return sort @matches; + return; } -# 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 ) = @_; +sub print_count0 { + my $filename = shift; + my $ors = shift; + my $show_filename = shift; - if ( $is_windows ) { - return File::Next::reslash( $filename ); + if ($show_filename) { + App::Ack::print( $filename, ':0', $ors ); } 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; - } + App::Ack::print( '0', $ors ); + } + + 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 create_ackrc { - print "$_\n" for ( '--ignore-ack-defaults', App::Ack::ConfigDefault::options() ); + +sub exit_from_ack { + my $nmatches = shift; + + my $rc = $nmatches ? 0 : 1; + exit $rc; } @@ -2910,6 +2926,10 @@ sub is_binary { } +sub open { + return FAIL(); +} + sub needs_line_scan { return FAIL(); @@ -2921,17 +2941,17 @@ sub reset { } -sub next_text { +sub close { return FAIL(); } -sub close { +sub clone { return FAIL(); } -sub clone { +sub firstliney { return FAIL(); } @@ -3025,6 +3045,8 @@ package App::Ack::Resource::Basic; use warnings; use strict; +use Fcntl (); + BEGIN { our @ISA = 'App::Ack::Resource'; } @@ -3037,17 +3059,12 @@ sub new { my $self = bless { filename => $filename, fh => undef, - opened => undef, + opened => 0, }, $class; if ( $self->{filename} eq '-' ) { - $self->{fh} = *STDIN; - } - else { - if ( !open( $self->{fh}, '<', $self->{filename} ) && $App::Ack::report_bad_filenames ) { - App::Ack::warn( "$self->{filename}: $!" ); - return; - } + $self->{fh} = *STDIN; + $self->{opened} = 1; } return $self; @@ -3055,9 +3072,7 @@ sub new { sub name { - my $self = shift; - - return $self->{filename}; + return $_[0]->{filename}; } @@ -3092,20 +3107,13 @@ sub needs_line_scan { sub reset { my $self = shift; - if( !seek( $self->{fh}, 0, 0 ) && $App::Ack::report_bad_filenames ) { - App::Ack::warn( "$self->{filename}: $!" ); + # return if we haven't opened the file yet + if ( !defined($self->{fh}) ) { + return; } - return; -} - - -sub next_text { - if ( defined ($_ = readline $_[0]->{fh}) ) { - $. = ++$_[0]->{line}; - s/[\r\n]+$//; # chomp may not handle this - $_ .= "\n"; # add back newline (XXX make it native) - return 1; + if( !seek( $self->{fh}, 0, 0 ) && $App::Ack::report_bad_filenames ) { + App::Ack::warn( "$self->{filename}: $!" ); } return; @@ -3115,10 +3123,17 @@ sub next_text { 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; } @@ -3129,6 +3144,40 @@ sub clone { return __PACKAGE__->new($self->name); } +sub firstliney { + my ( $self ) = @_; + + my $fh = $self->open(); + + unless(exists $self->{firstliney}) { + 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}; +} + +sub open { + my ( $self ) = @_; + + return $self->{fh} if $self->{opened}; + + unless ( open $self->{fh}, '<', $self->{filename} ) { + return; + } + + $self->{opened} = 1; + + return $self->{fh}; +} 1; package App::Ack::Filter; @@ -3265,12 +3314,9 @@ sub filter { my $re = $self->{'regex'}; - my $buffer; - my $rc = sysread( $resource->{fh}, $buffer, 250 ); - return unless $rc; - $buffer =~ s/[\r\n].*//s; + my $line = $resource->firstliney; - return $buffer =~ /$re/; + return $line =~ /$re/; } sub inspect { @@ -3463,37 +3509,55 @@ use warnings; use Cwd 3.00 (); use File::Spec 3.00; -BEGIN { - if ($App::Ack::is_windows) { - require Win32; - } -} +use if ($^O =~ /MSWin32/ ? 1 : 0), "Win32"; +our $is_win = 0; + sub new { my ( $class ) = @_; + $is_win = $^O =~ /MSWin32/, + return bless {}, $class; } sub _remove_redundancies { my ( @configs ) = @_; - my %dev_and_inode_seen; + if ( $is_win ) { + # inode stat always returns 0 on windows, + # so just check filenames + my (%seen, @uniq); - foreach my $path ( @configs ) { - my ( $dev, $inode ) = (stat $path)[0, 1]; + foreach my $path (@configs) { + push @uniq, $path unless $seen{$path}; + $seen{$path} = 1; + } - if( defined($dev) ) { - if( $dev_and_inode_seen{"$dev:$inode"} ) { - undef $path; - } - else { - $dev_and_inode_seen{"$dev:$inode"} = 1; + return @uniq; + } + + else { + + my %dev_and_inode_seen; + + foreach my $path ( @configs ) { + my ( $dev, $inode ) = (stat $path)[0, 1]; + + if( defined($dev) ) { + if( $dev_and_inode_seen{"$dev:$inode"} ) { + undef $path; + } + else { + $dev_and_inode_seen{"$dev:$inode"} = 1; + } } } + + return grep { defined() } @configs; + } - return grep { defined() } @configs; } sub _check_for_ackrc { @@ -3514,7 +3578,7 @@ sub _check_for_ackrc { sub find_config_files { my @config_files; - if($App::Ack::is_windows) { + if( $is_win ) { push @config_files, map { File::Spec->catfile($_, 'ackrc') } ( Win32::GetFolderPath(Win32::CSIDL_COMMON_APPDATA()), Win32::GetFolderPath(Win32::CSIDL_APPDATA()), @@ -3524,6 +3588,7 @@ sub find_config_files { push @config_files, '/etc/ackrc'; } + if ( $ENV{'ACKRC'} && -f $ENV{'ACKRC'} ) { push @config_files, $ENV{'ACKRC'}; } @@ -3546,6 +3611,30 @@ sub find_config_files { 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 =~ /^#/; + + push( @lines, $line ); + } + close $fh; + + return @lines; +} + 1; package App::Ack::ConfigLoader; @@ -3553,7 +3642,7 @@ use strict; use warnings; use Carp 1.04 (); -use Getopt::Long 2.36 (); +use Getopt::Long 2.35 (); use Text::ParseWords 3.1 (); @@ -3675,7 +3764,9 @@ sub process_filetypes { if ( ref($args) ) { # $args are modified in place, so no need to munge $arg_sources - Getopt::Long::GetOptionsFromArray($args, %type_arg_specs); + local @ARGV = @{$args}; + Getopt::Long::GetOptions(%type_arg_specs); + @{$args} = @ARGV; } else { ( undef, $arg_sources->[$i + 1] ) = @@ -3728,7 +3819,7 @@ EOT 'color-lineno=s' => \$ENV{ACK_COLOR_LINENO}, 'column!' => \$opt->{column}, count => \$opt->{count}, - 'create-ackrc' => sub { App::Ack::create_ackrc(); exit; }, + 'create-ackrc' => sub { print "$_\n" for ( '--ignore-ack-defaults', App::Ack::ConfigDefault::options() ); exit; }, 'env!' => sub { my ( undef, $value ) = @_; @@ -3855,10 +3946,11 @@ sub process_other { 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::GetOptionsFromArray( \@copy, + Getopt::Long::GetOptions( 'help-types' => \$is_help_types_active, ); @@ -3872,7 +3964,9 @@ sub process_other { my $ret; if ( ref($args) ) { - $ret = Getopt::Long::GetOptionsFromArray( $args, %{$arg_specs} ); + local @ARGV = @{$args}; + $ret = Getopt::Long::GetOptions( %{$arg_specs} ); + @{$args} = @ARGV; } else { ( $ret, $arg_sources->[$i + 1] ) = @@ -3901,10 +3995,12 @@ sub should_dump_options { my ( $name, $options ) = @{$sources}[$i, $i + 1]; if($name eq 'ARGV') { my $dump; + local @ARGV = @{$options}; Getopt::Long::Configure('default', 'pass_through', 'no_auto_help', 'no_auto_version'); - Getopt::Long::GetOptionsFromArray($options, + Getopt::Long::GetOptions( 'dump' => \$dump, ); + @{$options} = @ARGV; return $dump; } } @@ -3953,14 +4049,14 @@ sub explode_sources { $j--; my @copy = @chunk; - Getopt::Long::GetOptionsFromArray(\@chunk, + local @ARGV = @chunk; + Getopt::Long::GetOptions( 'type-add=s' => $add_type, 'type-set=s' => $add_type, 'type-del=s' => $del_type, ); - Getopt::Long::GetOptionsFromArray(\@chunk, %{$arg_spec}); + Getopt::Long::GetOptions( %{$arg_spec} ); - splice @copy, -1 * @chunk if @chunk; # XXX explain this push @new_sources, $name, \@copy; } } @@ -4037,9 +4133,11 @@ sub remove_default_options_if_needed { my ( $name, $args ) = @{$sources}[ $index, $index + 1 ]; if (ref($args)) { - Getopt::Long::GetOptionsFromArray($args, + local @ARGV = @{$args}; + Getopt::Long::GetOptions( 'ignore-ack-defaults' => \$should_remove, ); + @{$args} = @ARGV; } else { ( undef, $sources->[$index + 1] ) = Getopt::Long::GetOptionsFromString($args, @@ -4148,6 +4246,58 @@ sub process_args { 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, $ackrc ); + } + + push @arg_sources, Defaults => [ App::Ack::ConfigDefault::options() ]; + + foreach my $file ( @files) { + my @lines = App::Ack::ConfigFinder::read_rcfile($file); + push ( @arg_sources, $file, \@lines ) if @lines; + } + + if ( $ENV{ACK_OPTIONS} && !$noenv ) { + push( @arg_sources, 'ACK_OPTIONS' => $ENV{ACK_OPTIONS} ); + } + + push( @arg_sources, 'ARGV' => [ @ARGV ] ); + + return @arg_sources; +} + 1; # End of App::Ack::ConfigLoader package App::Ack::ConfigDefault; @@ -4249,6 +4399,11 @@ sub _options_block { # minified Javascript --ignore-file=match:/[.]min[.]js$/ +--ignore-file=match:/[.]js[.]min$/ + +# minified CSS +--ignore-file=match:/[.]min[.]css$/ +--ignore-file=match:/[.]css[.]min$/ # Filetypes defined @@ -4311,6 +4466,9 @@ sub _options_block { # 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 @@ -4391,8 +4549,8 @@ sub _options_block { --type-add=scheme:ext:scm,ss # Shell ---type-add=shell:ext:sh,bash,csh,tcsh,ksh,zsh ---type-add=shell:firstlinematch:/^#!.*\b(?:ba|t?c|k|z)?sh\b/ +--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 -- 2.47.1