From 2e4ab99bfd835289d4794767ab171ec294d52542 Mon Sep 17 00:00:00 2001 From: Tony Duckles Date: Sat, 14 Feb 2015 09:25:35 -0600 Subject: [PATCH] ack v2.15_01 --- bin/ack | 7651 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 4056 insertions(+), 3595 deletions(-) diff --git a/bin/ack b/bin/ack index 66d807c..50d4290 100755 --- a/bin/ack +++ b/bin/ack @@ -4,4930 +4,5391 @@ # Please DO NOT EDIT or send patches for it. # # Please take a look at the source from -# http://github.com/petdance/ack2 +# https://github.com/petdance/ack2 # and submit patches against the individual files # that build ack. # -package File::Next; +package main; use strict; use warnings; +our $VERSION = '2.15_01'; # Check http://beyondgrep.com/ for updates -our $VERSION = '1.12'; +use 5.008008; +use Getopt::Long 2.38 (); +use Carp 1.04 (); +use File::Spec (); -use File::Spec (); +# XXX Don't make this so brute force +# See also: https://github.com/petdance/ack2/issues/89 -our $name; # name of the current file -our $dir; # dir of the current file +our $opt_after_context; +our $opt_before_context; +our $opt_output; +our $opt_print0; +our $opt_color; +our $opt_heading; +our $opt_show_filename; +our $opt_regex; +our $opt_break; +our $opt_count; +our $opt_v; +our $opt_m; +our $opt_g; +our $opt_f; +our $opt_lines; +our $opt_L; +our $opt_l; +our $opt_passthru; +our $opt_column; +# flag if we need any context tracking +our $is_tracking_context; -our %files_defaults; -our %skip_dirs; +# These are all our globals. -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); -} +MAIN: { + $App::Ack::orig_program_name = $0; + $0 = join(' ', 'ack', $0); + if ( $App::Ack::VERSION ne $main::VERSION ) { + App::Ack::die( "Program/library version mismatch\n\t$0 is $main::VERSION\n\t$INC{'App/Ack.pm'} is $App::Ack::VERSION" ); + } + # Do preliminary arg checking; + my $env_is_usable = 1; + for my $arg ( @ARGV ) { + last if ( $arg eq '--' ); -sub files { - die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__); + # Get the --thpppt, --bar, --cathy checking out of the way. + $arg =~ /^--th[pt]+t+$/ and App::Ack::_thpppt($arg); + $arg eq '--bar' and App::Ack::_bar(); + $arg eq '--cathy' and App::Ack::_cathy(); - my ($parms,@queue) = _setup( \%files_defaults, @_ ); - my $filter = $parms->{file_filter}; + # See if we want to ignore the environment. (Don't tell Al Gore.) + $arg eq '--env' and $env_is_usable = 1; + $arg eq '--noenv' and $env_is_usable = 0; + } - 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 + if ( !$env_is_usable ) { + my @keys = ( 'ACKRC', grep { /^ACK_/ } keys %ENV ); + delete @ENV{@keys}; + } + load_colors(); - return; - }; # iterator + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); + Getopt::Long::Configure('pass_through', 'no_auto_abbrev'); + Getopt::Long::GetOptions( + 'help' => sub { App::Ack::show_help(); exit; }, + 'version' => sub { App::Ack::print_version_statement(); exit; }, + 'man' => sub { App::Ack::show_man(); exit; }, + ); + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); + + if ( !@ARGV ) { + App::Ack::show_help(); + exit 1; + } + + main(); } +sub _compile_descend_filter { + my ( $opt ) = @_; + my $idirs = 0; + my $dont_ignore_dirs = 0; + for my $filter (@{$opt->{idirs} || []}) { + if ($filter->is_inverted()) { + $dont_ignore_dirs++; + } + else { + $idirs++; + } + } + # if we have one or more --noignore-dir directives, we can't ignore + # entire subdirectory hierarchies, so we return an "accept all" + # filter and scrutinize the files more in _compile_file_filter + return if $dont_ignore_dirs; + return unless $idirs; + $idirs = $opt->{idirs}; -sub from_file { - die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__); + return sub { + my $resource = App::Ack::Resource::Basic->new($File::Next::dir); + return !grep { $_->filter($resource) } @{$idirs}; + }; +} - my ($parms,@queue) = _setup( \%files_defaults, @_ ); - my $err = $parms->{error_handler}; - my $warn = $parms->{error_handler}; +sub _compile_file_filter { + my ( $opt, $start ) = @_; - my $filename = $queue[1]; + my $ifiles_filters = $opt->{ifiles}; - if ( !defined($filename) ) { - $err->( 'Must pass a filename to from_file()' ); - return undef; - } + my $filters = $opt->{'filters'} || []; + my $direct_filters = App::Ack::Filter::Collection->new(); + my $inverse_filters = App::Ack::Filter::Collection->new(); - my $fh; - if ( $filename eq '-' ) { - $fh = \*STDIN; - } - else { - if ( !open( $fh, '<', $filename ) ) { - $err->( "Unable to open $filename: $!" ); - return undef; + foreach my $filter (@{$filters}) { + if ($filter->is_inverted()) { + # We want to check if files match the uninverted filters + $inverse_filters->add($filter->invert()); + } + else { + $direct_filters->add($filter); } } - my $filter = $parms->{file_filter}; + + my %is_member_of_starting_set = map { (get_file_id($_) => 1) } @{$start}; + + my @ignore_dir_filter = @{$opt->{idirs} || []}; + my @is_inverted = map { $_->is_inverted() } @ignore_dir_filter; + # this depends on InverseFilter->invert returning the original + # filter (for optimization) + @ignore_dir_filter = map { $_->is_inverted() ? $_->invert() : $_ } @ignore_dir_filter; + my $dont_ignore_dir_filter = grep { $_ } @is_inverted; + my $previous_dir = ''; + my $previous_dir_ignore_result; return sub { - 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; + if ( $opt_g ) { + if ( $File::Next::name =~ /$opt_regex/ && $opt_v ) { + return; + } + if ( $File::Next::name !~ /$opt_regex/ && !$opt_v ) { + return; } + } + # ack always selects files that are specified on the command + # line, regardless of filetype. If you want to ack a JPEG, + # and say "ack foo whatever.jpg" it will do it for you. + return 1 if $is_member_of_starting_set{ get_file_id($File::Next::name) }; - 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->(); + if ( $dont_ignore_dir_filter ) { + if ( $previous_dir eq $File::Next::dir ) { + if ( $previous_dir_ignore_result ) { + return 0; + } } - return wantarray ? ($dirname,$file,$fullpath) : $fullpath; - } # while - close $fh; + else { + my @dirs = File::Spec->splitdir($File::Next::dir); - return; - }; # iterator -} + my $is_ignoring = 0; -sub _bad_invocation { - my $good = (caller(1))[3]; - my $bad = $good; - $bad =~ s/(.+)::/$1->/; - return "$good must not be invoked as $bad"; -} + for ( my $i = 0; $i < @dirs; $i++) { + my $dir_rsrc = App::Ack::Resource::Basic->new(File::Spec->catfile(@dirs[0 .. $i])); -sub sort_standard($$) { return $_[0]->[1] cmp $_[1]->[1] } -sub sort_reverse($$) { return $_[1]->[1] cmp $_[0]->[1] } + my $j = 0; + for my $filter (@ignore_dir_filter) { + if ( $filter->filter($dir_rsrc) ) { + $is_ignoring = !$is_inverted[$j]; + } + $j++; + } + } -sub reslash { - my $path = shift; + $previous_dir = $File::Next::dir; + $previous_dir_ignore_result = $is_ignoring; - my @parts = split( /\//, $path ); + if ( $is_ignoring ) { + return 0; + } + } + } - return $path if @parts < 2; + # Ignore named pipes found in directory searching. Named + # pipes created by subprocesses get specified on the command + # line, so the rule of "always select whatever is on the + # command line" wins. + return 0 if -p $File::Next::name; - return File::Spec->catfile( @parts ); -} + # We can't handle unreadable filenames; report them. + if ( not -r _ ) { + use filetest 'access'; + if ( not -R $File::Next::name ) { + if ( $App::Ack::report_bad_filenames ) { + App::Ack::warn( "${File::Next::name}: cannot open file for reading" ); + } + return 0; + } + } + my $resource = App::Ack::Resource::Basic->new($File::Next::name); -sub _setup { - my $defaults = shift; - my $passed_parms = ref $_[0] eq 'HASH' ? {%{+shift}} : {}; # copy parm hash + if ( $ifiles_filters && $ifiles_filters->filter($resource) ) { + return 0; + } - my %passed_parms = %{$passed_parms}; + my $match_found = $direct_filters->filter($resource); - my $parms = {}; - for my $key ( keys %{$defaults} ) { - $parms->{$key} = - exists $passed_parms{$key} - ? delete $passed_parms{$key} - : $defaults->{$key}; - } + # Don't bother invoking inverse filters unless we consider the current resource a match + if ( $match_found && $inverse_filters->filter( $resource ) ) { + $match_found = 0; + } + return $match_found; + }; +} - # 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" ); - } +sub show_types { + my $resource = shift; + my $ors = shift; - # 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; + my @types = filetypes( $resource ); + my $types = join( ',', @types ); + my $arrow = @types ? ' => ' : ' =>'; + App::Ack::print( $resource->name, $arrow, join( ',', @types ), $ors ); - for ( @_ ) { - my $start = reslash( $_ ); - if (-d $start) { - push @queue, ($start,undef,$start); - } - else { - push @queue, (undef,$start,$start); - } - } + return; +} - return ($parms,@queue); +# Set default colors, load Term::ANSIColor +sub load_colors { + eval 'use Term::ANSIColor 1.10 ()'; + eval 'use Win32::Console::ANSI' if $App::Ack::is_windows; + + $ENV{ACK_COLOR_MATCH} ||= 'black on_yellow'; + $ENV{ACK_COLOR_FILENAME} ||= 'bold green'; + $ENV{ACK_COLOR_LINENO} ||= 'bold yellow'; + + return; } +sub filetypes { + my ( $resource ) = @_; -sub _candidate_files { - my $parms = shift; - my $dirname = shift; + my @matches; - my $dh; - if ( !opendir $dh, $dirname ) { - $parms->{error_handler}->( "$dirname: $!" ); - return; - } + foreach my $k (keys %App::Ack::mappings) { + my $filters = $App::Ack::mappings{$k}; - my @newfiles; - my $descend_filter = $parms->{descend_filter}; - my $follow_symlinks = $parms->{follow_symlinks}; - my $sort_sub = $parms->{sort_files}; + foreach my $filter (@{$filters}) { + # clone the resource + my $clone = $resource->clone; + if ( $filter->filter($clone) ) { + push @matches, $k; + last; + } + } + } - for my $file ( grep { !exists $skip_dirs{$_} } readdir $dh ) { - my $has_stat; + # http://search.cpan.org/dist/Perl-Critic/lib/Perl/Critic/Policy/Subroutines/ProhibitReturnSort.pm + @matches = sort @matches; + return @matches; +} - # 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; - } +# 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 ( $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 ] ); + if ( $App::Ack::is_windows ) { + return File::Next::reslash( $filename ); + } + else { + # XXX is this the best method? it always hits the FS + if( my ( $dev, $inode ) = (stat($filename))[0, 1] ) { + return join(':', $dev, $inode); } else { - push( @newfiles, $dirname, $file, $fullpath ); + # XXX this could be better + return $filename; } } - closedir $dh; - - if ( $sort_sub ) { - return map { @{$_} } sort $sort_sub @newfiles; - } - - return @newfiles; } +# Returns a regex object based on a string and command-line options. +# Dies when the regex $str is undefined (i.e. not given on command line). -1; # End of File::Next -package App::Ack; +sub build_regex { + my $str = shift; + my $opt = shift; -use warnings; -use strict; + defined $str or App::Ack::die( 'No regular expression found.' ); + $str = quotemeta( $str ) if $opt->{Q}; + if ( $opt->{w} ) { + my $pristine_str = $str; -our $VERSION; -our $GIT_REVISION; -our $COPYRIGHT; -BEGIN { - $VERSION = '2.10'; - $COPYRIGHT = 'Copyright 2005-2013 Andy Lester.'; - $GIT_REVISION = q{af91cce}; -} + $str = "(?:$str)"; + $str = "\\b$str" if $pristine_str =~ /^\w/; + $str = "$str\\b" if $pristine_str =~ /\w$/; + } -our $fh; + my $regex_is_lc = $str eq lc $str; + if ( $opt->{i} || ($opt->{smart_case} && $regex_is_lc) ) { + $str = "(?i)$str"; + } -BEGIN { - $fh = *STDOUT; -} + my $re = eval { qr/$str/m }; + if ( !$re ) { + die "Invalid regex '$str':\n $@"; + } + return $re; -our %types; -our %type_wanted; -our %mappings; -our %ignore_dirs; +} -our $is_filter_mode; -our $output_to_pipe; +my $match_column_number; -our $dir_sep_chars; -our $is_cygwin; -our $is_windows; +{ -use File::Spec 1.00015 (); +# number of context lines +my $n_before_ctx_lines; +my $n_after_ctx_lines; -BEGIN { - # These have to be checked before any filehandle diddling. - $output_to_pipe = not -t *STDOUT; - $is_filter_mode = -p STDIN; +# array to keep track of lines that might be required for a "before" context +my @before_context_buf; +# position to insert next line in @before_context_buf +my $before_context_pos; - $is_cygwin = ($^O eq 'cygwin'); - $is_windows = ($^O eq 'MSWin32'); - $dir_sep_chars = $is_windows ? quotemeta( '\\/' ) : quotemeta( File::Spec->catfile( '', '' ) ); -} +# number of "after" context lines still pending +my $after_context_pending; +# number of latest line that got printed +my $printed_line_no; +my $is_iterating; -sub remove_dir_sep { - my $path = shift; - $path =~ s/[$dir_sep_chars]$//; +my $is_first_match; +my $has_printed_something; - return $path; +BEGIN { + $has_printed_something = 0; } +# setup context tracking variables +sub setup_line_context { + $n_before_ctx_lines = $opt_output ? 0 : ($opt_before_context || 0); + $n_after_ctx_lines = $opt_output ? 0 : ($opt_after_context || 0); -sub warn { - return CORE::warn( _my_program(), ': ', @_, "\n" ); -} + @before_context_buf = (undef) x $n_before_ctx_lines; + $before_context_pos = 0; + $is_tracking_context = $n_before_ctx_lines || $n_after_ctx_lines; -sub die { - return CORE::die( _my_program(), ': ', @_, "\n" ); -} + $is_first_match = 1; -sub _my_program { - require File::Basename; - return File::Basename::basename( $0 ); + return; } +# adjust context tracking variables when entering a new file +sub setup_line_context_for_file { + $printed_line_no = 0; + $after_context_pending = 0; + if ( $opt_heading && !$opt_lines ) { + $is_first_match = 1; + } -sub filetypes_supported { - return keys %mappings; + return; } -sub _get_thpppt { - my $y = q{_ /|,\\'!.x',=(www)=, U }; - $y =~ tr/,x!w/\nOo_/; - return $y; -} +=for Developers -sub _thpppt { - my $y = _get_thpppt(); - App::Ack::print( "$y ack $_[0]!\n" ); - exit 0; -} +This subroutine jumps through a number of optimization hoops to +try to be fast in the more common use cases of ack. For one thing, +in non-context tracking searches (not using -A, -B, or -C), +conditions that normally would be checked inside the loop happen +outside, resulting in three nearly identical loops for -v, --passthru, +and normal searching. Any changes that happen to one should propagate +to the others if they make sense. The non-context branches also inline +does_match for performance reasons; any relevant changes that happen here +must also happen there. -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 +=cut - App::Ack::__pic($x); -} +sub print_matches_in_resource { + my ( $resource, $opt ) = @_; -sub _cathy { - my $x = <<'CATHY'; - 0+!--+! - 0|! "C!H!O!C!O!L!A!T!E!!! !|! - 0|! "C!H!O!C!O!L!A!T!E!!! !|! - 0|! "C!H!O!C!O!L!A!T!E!!! !|! - 0|! $A"C!K!!! $|! - 0+!--+! - 6\! 1:!,!.! ! - 7\! /.!M!~!Z!M!~! - 8\! /~!D! "M! ! - 4.! $\! /M!~!.!8! +.!M# 4 - 0,!.! (\! .~!M!N! ,+!I!.!M!.! 3 - /?!O!.!M!:! '\! .O!.! +~!Z!=!N!.! 4 - ..! !D!Z!.!Z!.! '\! 9=!M".! 6 - /.! !.!~!M".! '\! 8~! 9 - 4M!.! /.!7!N!M!.! F - 4.! &:!M! !N"M# !M"N!M! #D!M&=! = - :M!7!M#:! !~!M!7!,!$!M!:! #.! !O!N!.!M!:!M# ; - 8Z!M"~!N!$!D!.!N!?! !I!N!.! (?!M! !M!,!D!M".! 9 - (?!Z!M!N!:! )=!M!O!8!.!M!+!M! !M!,! !O!M! +,!M!.!M!~!Z!N!M!:! &:!~! 0 - &8!7!.!~!M"D!M!,! &M!?!=!8! !M!,!O! !M!+! !+!O!.!M! $M#~! !.!8!M!Z!.!M! !O!M"Z! %:!~!M!Z!M!Z!.! + - &:!M!7!,! *M!.!Z!M! !8"M!.!M!~! !.!M!.!=! #~!8!.!M! !7!M! "N!Z#I! !D!M!,!M!.! $."M!,! !M!.! * - 2$!O! "N! !.!M!I! !7" "M! "+!O! !~!M! !d!O!.!7!I!M!.! !.!O!=!M!.! !M",!M!.! %.!$!O!D! + - 1~!O! "M!+! !8!$! "M! "?!O! %Z!8!D!M!?!8!I!O!7!M! #M!.!M! "M",!M! 4 - 07!~! ".!8! !.!M! "I!+! !.!M! &Z!D!.!7!=!M! !:!.!M! #:!8"+! !.!+!8! !8! 3 - /~!M! #N! !~!M!$! !.!M! !.!M" &~!M! "~!M!O! "D! $M! !8! "M!,!M!+!D!.! 1 - #.! #?!M!N!.! #~!O! $M!.!7!$! "?" !?!~!M! '7!8!?!M!.!+!M"O! $?"$!D! !.!O! !$!7!I!.! 0 - $,!M!:!O!?! ".! !?!=! $=!:!O! !M! "M! !M! !+!$! (.! +.!M! !M!.! !8! !+"Z!~! $:!M!$! !.! ' - #.!8!.!I!$! $7!I! %M" !=!M! !~!M!D! "7!I! .I!O! %?!=!,!D! !,!M! !D!~!8!~! %D!M! ( - #.!M"?! $=!O! %=!N! "8!.! !Z!M! #M!~! (M!:! #.!M" &O! !M!.! !?!,! !8!.!N!~! $8!N!M!,!.! % - *$!O! &M!,! "O! !.!M!.! #M! (~!M( &O!.! !7! "M! !.!M!.!M!,! #.!M! !M! & - )=!8!.! $.!M!O!.! "$!.!I!N! !I!M# (7!M(I! %D"Z!M! "=!I! "M! !M!:! #~!D! ' - )D! &8!N!:! ".!O! !M!="M! "M! (7!M) %." !M!D!."M!.! !$!=! !M!,! + - (M! &+!.!M! #Z!7!O!M!.!~!8! +,!M#D!?!M#D! #.!Z!M#,!Z!?! !~!N! "N!.! !M! + - 'D!:! %$!D! !?! #M!Z! !8!.! !M"?!7!?!7! '+!I!D! !?!O!:!M!:! ":!M!:! !M!7".!M! "8!+! !:!D! !.!M! * - %.!O!:! $.!O!+! !D!.! #M! "M!.!+!N!I!Z! "7!M!N!M!N!?!I!7!Z!=!M'D"~! #M!.!8!$! !:! !.!M! "N!?! !,!O! ) - !.!?!M!:!M!I! %8!,! "M!.! #M! "N! !M!.! !M!.! !+!~! !.!M!.! ':!M! $M! $M!Z!$! !M!.! "D! "M! "?!M! ( - !7!8! !+!I! ".! "$!=! ":!$! "+! !M!.! !O! !M!I!M".! !=!~! ",!O! '=!M! $$!,! #N!:! ":!8!.! !D!~! !,!M!.! !:!M!.! & - !:!,!.! &Z" #D! !.!8!."M!.! !8!?!Z!M!.!M! #Z!~! !?!M!Z!.! %~!O!.!8!$!N!8!O!I!:!~! !+! #M!.! !.!M!.! !+!M! ".!~!M!+! $ - !.! 'D!I! #?!M!.!M!,! !.!Z! !.!8! #M&O!I!?! (~!I!M"." !M!Z!.! !M!N!.! "+!$!.! "M!.! !M!?!.! "8!M! $ - (O!8! $M! !M!.! ".!:! !+!=! #M! #.!M! !+" *$!M":!.! !M!~! "M!7! #M! #7!Z! "M"$!M!.! !.! # - '$!Z! #.!7!+!M! $.!,! !+!:! #N! #.!M!.!+!M! +D!M! #=!N! ":!O! #=!M! #Z!D! $M!I! % - $,! ".! $.!M" %$!.! !?!~! "+!7!." !.!M!,! !M! *,!N!M!.$M!?! "D!,! #M!.! #N! + - ,M!Z! &M! "I!,! "M! %I!M! !?!=!.! (Z!8!M! $:!M!.! !,!M! $D! #.!M!.! ) - +8!O! &.!8! "I!,! !~!M! &N!M! !M!D! '?!N!O!." $?!7! "?!~! #M!.! #I!D!.! ( - 3M!,! "N!.! !D" &.!+!M!.! !M":!.":!M!7!M!D! 'M!.! "M!.! "M!,! $I! ) - 3I! #M! "M!,! !:! &.!M" ".!,! !.!$!M!I! #.! !:! !.!M!?! "N!+! ".! / - 1M!,! #.!M!8!M!=!.! +~!N"O!Z"~! *+!M!.! "M! 2 - 0.!M! &M!.! 8:! %.!M!Z! "M!=! *O!,! % - 0?!$! &N! )." .,! %."M! ":!M!.! 0 - 0N!:! %?!O! #.! ..! &,! &.!D!,! "N!I! 0 -CATHY - App::Ack::__pic($x); -} + my $max_count = $opt_m || -1; + my $nmatches = 0; + my $filename = $resource->name; + my $ors = $opt_print0 ? "\0" : "\n"; -sub __pic { - my($compressed) = @_; - $compressed =~ s/(.)(.)/$1x(ord($2)-32)/eg; - App::Ack::print( $compressed ); - exit 0; -} + my $has_printed_for_this_resource = 0; + $is_iterating = 1; -sub show_help { - my $help_arg = shift || 0; + my $fh = $resource->open(); + if ( !$fh ) { + if ( $App::Ack::report_bad_filenames ) { + App::Ack::warn( "$filename: $!" ); + } + return 0; + } + + my $display_filename = $filename; + if ( $opt_show_filename && $opt_heading && $opt_color ) { + $display_filename = Term::ANSIColor::colored($display_filename, $ENV{ACK_COLOR_FILENAME}); + } + + # check for context before the main loop, so we don't + # pay for it if we don't need it + if ( $is_tracking_context ) { + $after_context_pending = 0; + while ( <$fh> ) { + if ( does_match($opt, $_) && $max_count ) { + if ( !$has_printed_for_this_resource ) { + if ( $opt_break && $has_printed_something ) { + App::Ack::print_blank_line(); + } + if ( $opt_show_filename && $opt_heading ) { + App::Ack::print_filename( $display_filename, $ors ); + } + } + print_line_with_context($opt, $filename, $_, $.); + $has_printed_for_this_resource = 1; + $nmatches++; + $max_count--; + } + elsif ( $opt_passthru ) { + chomp; # XXX proper newline handling? + # XXX inline this call? + if ( $opt_break && !$has_printed_for_this_resource && $has_printed_something ) { + App::Ack::print_blank_line(); + } + print_line_with_options($opt, $filename, $_, $., ':'); + $has_printed_for_this_resource = 1; + } + else { + chomp; # XXX proper newline handling? + print_line_if_context($opt, $filename, $_, $., '-'); + } + + last if ($max_count == 0) && ($after_context_pending == 0); + } + } + else { + local $_; + + if ( $opt_passthru ) { + while ( <$fh> ) { + $match_column_number = undef; + if ( $opt_v ? !/$opt_regex/o : /$opt_regex/o ) { + if ( !$opt_v ) { + $match_column_number = $-[0] + 1; + } + if ( !$has_printed_for_this_resource ) { + if ( $opt_break && $has_printed_something ) { + App::Ack::print_blank_line(); + } + if ( $opt_show_filename && $opt_heading ) { + App::Ack::print_filename( $display_filename, $ors ); + } + } + print_line_with_context($opt, $filename, $_, $.); + $has_printed_for_this_resource = 1; + $nmatches++; + $max_count--; + } + else { + chomp; # XXX proper newline handling? + if ( $opt_break && !$has_printed_for_this_resource && $has_printed_something ) { + App::Ack::print_blank_line(); + } + print_line_with_options($opt, $filename, $_, $., ':'); + $has_printed_for_this_resource = 1; + } + last unless $max_count != 0; + } + } + elsif ( $opt_v ) { + $match_column_number = undef; + while ( <$fh> ) { + if ( !/$opt_regex/o ) { + if ( !$has_printed_for_this_resource ) { + if ( $opt_break && $has_printed_something ) { + App::Ack::print_blank_line(); + } + if ( $opt_show_filename && $opt_heading ) { + App::Ack::print_filename( $display_filename, $ors ); + } + } + print_line_with_context($opt, $filename, $_, $.); + $has_printed_for_this_resource = 1; + $nmatches++; + $max_count--; + } + last unless $max_count != 0; + } + } + else { + # XXX unroll first match check ($has_printed_for_this_resource) + # XXX what if this is a *huge* file? (see also -l) + my $contents = do { + local $/; + <$fh>; + }; + + my $prev_match_end = 0; + my $line_no = 1; + + $match_column_number = undef; + while ( $contents =~ /$opt_regex/og ) { + my $match_start = $-[0]; + my $match_end = $+[0]; + + pos($contents) = $prev_match_end; + $prev_match_end = $match_end; + + while ( $contents =~ /\n/og && $-[0] < $match_start ) { + $line_no++; + } + + my $start_line = rindex($contents, "\n", $match_start); + my $end_line = index($contents, "\n", $match_end); + + if ( $start_line == -1 ) { + $start_line = 0; + } + else { + $start_line++; + } + + if ( $end_line == -1 ) { + $end_line = length($contents); + } + else { + $end_line--; + } + $match_column_number = $match_start - $start_line + 1; + if ( !$has_printed_for_this_resource ) { + if ( $opt_break && $has_printed_something ) { + App::Ack::print_blank_line(); + } + if ( $opt_show_filename && $opt_heading ) { + App::Ack::print_filename( $display_filename, $ors ); + } + } + my $line = substr($contents, $start_line, $end_line - $start_line + 1); + $line =~ s/[\r\n]+$//g; + print_line_with_options($opt, $filename, $line, $line_no, ':'); + + pos($contents) = $end_line + 1; + + $has_printed_for_this_resource = 1; + $nmatches++; + $max_count--; + + last unless $max_count != 0; + } + } + + } + + $is_iterating = 0; # XXX this won't happen on an exception + # then again, do we care? ack doesn't really + # handle exceptions anyway. + + return $nmatches; +} + +sub print_line_with_options { + my ( $opt, $filename, $line, $line_no, $separator ) = @_; + + $has_printed_something = 1; + $printed_line_no = $line_no; + + my $ors = $opt_print0 ? "\0" : "\n"; + + my @line_parts; + + if( $opt_color ) { + $filename = Term::ANSIColor::colored($filename, + $ENV{ACK_COLOR_FILENAME}); + $line_no = Term::ANSIColor::colored($line_no, + $ENV{ACK_COLOR_LINENO}); + } + + if($opt_show_filename) { + if( $opt_heading ) { + push @line_parts, $line_no; + } + else { + push @line_parts, $filename, $line_no; + } + + if( $opt_column ) { + push @line_parts, get_match_column(); + } + } + if( $opt_output ) { + while ( $line =~ /$opt_regex/og ) { + # XXX We need to stop using eval() for --output. See https://github.com/petdance/ack2/issues/421 + my $output = eval $opt_output; + App::Ack::print( join( $separator, @line_parts, $output ), $ors ); + } + } + else { + if ( $opt_color ) { + $line =~ /$opt_regex/o; # this match is redundant, but we need + # to perfom it in order to get if + # capture groups are set + + if ( @+ > 1 ) { # if we have captures + while ( $line =~ /$opt_regex/og ) { + my $offset = 0; # additional offset for when we add stuff + my $previous_match_end = 0; + + for ( my $i = 1; $i < @+; $i++ ) { + my ( $match_start, $match_end ) = ( $-[$i], $+[$i] ); + + next unless defined($match_start); + next if $match_start < $previous_match_end; + + my $substring = substr( $line, + $offset + $match_start, $match_end - $match_start ); + my $substitution = Term::ANSIColor::colored( $substring, + $ENV{ACK_COLOR_MATCH} ); + + substr( $line, $offset + $match_start, + $match_end - $match_start, $substitution ); + + $previous_match_end = $match_end; # offsets do not need to be applied + $offset += length( $substitution ) - length( $substring ); + } + + pos($line) = $+[0] + $offset; + } + } + else { + my $matched = 0; # flag; if matched, need to escape afterwards + + while ( $line =~ /$opt_regex/og ) { + + $matched = 1; + my ( $match_start, $match_end ) = ($-[0], $+[0]); + next unless defined($match_start); + + my $substring = substr( $line, $match_start, + $match_end - $match_start ); + my $substitution = Term::ANSIColor::colored( $substring, + $ENV{ACK_COLOR_MATCH} ); + + substr( $line, $match_start, $match_end - $match_start, + $substitution ); + + pos($line) = $match_end + + (length( $substitution ) - length( $substring )); + } + # XXX why do we do this? + $line .= "\033[0m\033[K" if $matched; + } + } + + push @line_parts, $line; + App::Ack::print( join( $separator, @line_parts ), $ors ); + } + + return; +} + +sub iterate { + my ( $resource, $opt, $cb ) = @_; + + $is_iterating = 1; + + my $fh = $resource->open(); + if ( !$fh ) { + if ( $App::Ack::report_bad_filenames ) { + App::Ack::warn( $resource->name . ': ' . $! ); + } + return; + } + + # Check for context before the main loop, so we don't pay for it if we don't need it. + if ( $is_tracking_context ) { + $after_context_pending = 0; + + while ( <$fh> ) { + last unless $cb->(); + } + } + else { + local $_; + + while ( <$fh> ) { + last unless $cb->(); + } + } + + $is_iterating = 0; # XXX this won't happen on an exception + # then again, do we care? ack doesn't really + # handle exceptions anyway. + + return; +} + +sub print_line_with_context { + my ( $opt, $filename, $matching_line, $line_no ) = @_; + + my $ors = $opt_print0 ? "\0" : "\n"; + my $is_tracking_context = $opt_after_context || $opt_before_context; + + $matching_line =~ s/[\r\n]+$//g; + + # check if we need to print context lines first + if( $is_tracking_context ) { + my $before_unprinted = $line_no - $printed_line_no - 1; + if ( !$is_first_match && ( !$printed_line_no || $before_unprinted > $n_before_ctx_lines ) ) { + App::Ack::print('--', $ors); + } + + # We want at most $n_before_ctx_lines of context + if ( $before_unprinted > $n_before_ctx_lines ) { + $before_unprinted = $n_before_ctx_lines; + } + + while ( $before_unprinted > 0 ) { + my $line = $before_context_buf[($before_context_pos - $before_unprinted + $n_before_ctx_lines) % $n_before_ctx_lines]; + + chomp $line; + + # Disable $opt->{column} since there are no matches in the context lines + local $opt_column = 0; + + print_line_with_options($opt, $filename, $line, $line_no-$before_unprinted, '-'); + $before_unprinted--; + } + } + + print_line_with_options($opt, $filename, $matching_line, $line_no, ':'); + + # We want to get the next $n_after_ctx_lines printed + $after_context_pending = $n_after_ctx_lines; + + $is_first_match = 0; + + return; +} + +# print the line only if it's part of a context we need to display +sub print_line_if_context { + my ( $opt, $filename, $line, $line_no, $separator ) = @_; + + if ( $after_context_pending ) { + # Disable $opt->{column} since there are no matches in the context lines + local $opt_column = 0; + print_line_with_options( $opt, $filename, $line, $line_no, $separator ); + --$after_context_pending; + } + elsif ( $n_before_ctx_lines ) { + # save line for "before" context + $before_context_buf[$before_context_pos] = $_; + $before_context_pos = ($before_context_pos+1) % $n_before_ctx_lines; + } + + return; +} + +} + +# does_match() MUST have an $opt_regex set. + +=for Developers + +This subroutine is inlined a few places in print_matches_in_resource +for performance reasons, so any changes here must be copied there as +well. + +=cut + +sub does_match { + my ( $opt, $line ) = @_; + + $match_column_number = undef; + + if ( $opt_v ) { + return ( $line !~ /$opt_regex/o ); + } + else { + if ( $line =~ /$opt_regex/o ) { + # @- = @LAST_MATCH_START + # @+ = @LAST_MATCH_END + $match_column_number = $-[0] + 1; + return 1; + } + else { + return; + } + } +} + +sub get_match_column { + return $match_column_number; +} + +sub resource_has_match { + my ( $resource, $opt ) = @_; + + my $has_match = 0; + my $fh = $resource->open(); + if ( !$fh ) { + if ( $App::Ack::report_bad_filenames ) { + App::Ack::warn( $resource->name . ': ' . $! ); + } + } + else { + if ( $opt_v ) { + while ( <$fh> ) { + if (!/$opt_regex/o) { + $has_match = 1; + last; + } + } + } + else { + # XXX read in chunks + # XXX only do this for certain file sizes? + my $content = do { + local $/; + <$fh>; + }; + $has_match = $content =~ /$opt_regex/o; + } + close $fh; + } + + return $has_match; +} + +sub count_matches_in_resource { + my ( $resource, $opt ) = @_; + + my $nmatches = 0; + my $fh = $resource->open(); + if ( !$fh ) { + if ( $App::Ack::report_bad_filenames ) { + App::Ack::warn( $resource->name . ': ' . $! ); + } + } + else { + if ( $opt_v ) { + while ( <$fh> ) { + ++$nmatches if (!/$opt_regex/o); + } + } + else { + my $content = do { + local $/; + <$fh>; + }; + $nmatches =()= ($content =~ /$opt_regex/og); + } + close $fh; + } + + return $nmatches; +} + +sub main { + my @arg_sources = App::Ack::ConfigLoader::retrieve_arg_sources(); + + my $opt = App::Ack::ConfigLoader::process_args( @arg_sources ); + + $opt_after_context = $opt->{after_context}; + $opt_before_context = $opt->{before_context}; + $opt_output = $opt->{output}; + $opt_print0 = $opt->{print0}; + $opt_color = $opt->{color}; + $opt_heading = $opt->{heading}; + $opt_show_filename = $opt->{show_filename}; + $opt_regex = $opt->{regex}; + $opt_break = $opt->{break}; + $opt_count = $opt->{count}; + $opt_v = $opt->{v}; + $opt_m = $opt->{m}; + $opt_g = $opt->{g}; + $opt_f = $opt->{f}; + $opt_lines = $opt->{lines}; + $opt_L = $opt->{L}; + $opt_l = $opt->{l}; + $opt_passthru = $opt->{passthru}; + $opt_column = $opt->{column}; + + $App::Ack::report_bad_filenames = !$opt->{dont_report_bad_filenames}; + + if ( $opt->{flush} ) { + $| = 1; + } + + if ( !defined($opt_color) && !$opt_g ) { + my $windows_color = 1; + if ( $App::Ack::is_windows ) { + $windows_color = eval { require Win32::Console::ANSI; }; + } + $opt_color = !App::Ack::output_to_pipe() && $windows_color; + } + if ( not defined $opt_heading and not defined $opt_break ) { + $opt_heading = $opt_break = $opt->{break} = !App::Ack::output_to_pipe(); + } + + if ( defined($opt->{H}) || defined($opt->{h}) ) { + $opt_show_filename = $opt->{show_filename} = $opt->{H} && !$opt->{h}; + } + + if ( my $output = $opt_output ) { + $output =~ s{\\}{\\\\}g; + $output =~ s{"}{\\"}g; + $opt_output = qq{"$output"}; + } + + my $resources; + if ( $App::Ack::is_filter_mode && !$opt->{files_from} ) { # probably -x + $resources = App::Ack::Resources->from_stdin( $opt ); + $opt_regex = shift @ARGV if not defined $opt_regex; + $opt_regex = $opt->{regex} = build_regex( $opt_regex, $opt ); + } + else { + if ( $opt_f || $opt_lines ) { + if ( $opt_regex ) { + App::Ack::warn( "regex ($opt_regex) specified with -f or --lines" ); + App::Ack::exit_from_ack( 0 ); # XXX the 0 is misleading + } + } + else { + $opt_regex = shift @ARGV if not defined $opt_regex; + $opt_regex = $opt->{regex} = build_regex( $opt_regex, $opt ); + } + if ( $opt_regex && $opt_regex =~ /\n/ ) { + App::Ack::exit_from_ack( 0 ); + } + my @start; + if ( not defined $opt->{files_from} ) { + @start = @ARGV; + } + if ( !exists($opt->{show_filename}) ) { + unless(@start == 1 && !(-d $start[0])) { + $opt_show_filename = $opt->{show_filename} = 1; + } + } - return show_help_types() if $help_arg =~ /^types?/; + 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" ); + } + } - App::Ack::print( <<"END_OF_HELP" ); -Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES] + $opt->{file_filter} = _compile_file_filter($opt, \@start); + $opt->{descend_filter} = _compile_descend_filter($opt); -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 "-". + $resources = App::Ack::Resources->from_argv( $opt, \@start ); + } + } + App::Ack::set_up_pager( $opt->{pager} ) if defined $opt->{pager}; -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. + my $ors = $opt_print0 ? "\0" : "\n"; + my $only_first = $opt->{1}; -Example: ack -i select + my $nmatches = 0; + my $total_count = 0; -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 + setup_line_context( $opt ); -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 +RESOURCES: + while ( my $resource = $resources->next ) { + if ($is_tracking_context) { + setup_line_context_for_file($opt); + } - -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. + # 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($opt_m) && $nmatches >= $opt_m; + } + elsif ( $opt_g ) { + if ( $opt->{show_types} ) { + show_types( $resource, $ors ); + } + else { + local $opt_show_filename = 0; # XXX Why is this local? - --print0 Print null byte as separator between filenames, - only works with -f, -g, -l, -L or -c. + print_line_with_options($opt, '', $resource->name, 0, $ors); + } + ++$nmatches; + last RESOURCES if defined($opt_m) && $nmatches >= $opt_m; + } + elsif ( $opt_lines ) { + my %line_numbers; + foreach my $line ( @{ $opt_lines } ) { + my @lines = split /,/, $line; + @lines = map { + /^(\d+)-(\d+)$/ + ? ( $1 .. $2 ) + : $_ + } @lines; + @line_numbers{@lines} = (1) x @lines; + } - -s Suppress error messages about nonexistent or - unreadable files. + my $filename = $resource->name; + local $opt_color = 0; -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). + iterate($resource, $opt, sub { + chomp; + if ( $line_numbers{$.} ) { + print_line_with_context($opt, $filename, $_, $.); + } + elsif ( $opt_passthru ) { + print_line_with_options($opt, $filename, $_, $., ':'); + } + elsif ( $is_tracking_context ) { + print_line_if_context($opt, $filename, $_, $., '-'); + } + return 1; + }); + } + elsif ( $opt_count ) { + my $matches_for_this_file = count_matches_in_resource( $resource, $opt ); -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. + if ( not $opt_show_filename ) { + $total_count += $matches_for_this_file; + next RESOURCES; + } -File inclusion/exclusion: - --[no]ignore-dir=name Add/remove directory from list of ignored dirs - --[no]ignore-directory=name Synonym for ignore-dir - --ignore-file=filter Add filter for ignoring files - -r, -R, --recurse Recurse into subdirectories (default: on) - -n, --no-recurse No descending into subdirectories - --[no]follow Follow symlinks. Default is off. - -k, --known-types Include only files of types that ack recognizes. + if ( !$opt_l || $matches_for_this_file > 0) { + if ( $opt_show_filename ) { + App::Ack::print( $resource->name, ':', $matches_for_this_file, $ors ); + } + else { + App::Ack::print( $matches_for_this_file, $ors ); + } + } + } + elsif ( $opt_l || $opt_L ) { + my $is_match = resource_has_match( $resource, $opt ); - --type=X Include only X files, where X is a recognized - filetype. - --type=noX Exclude X files. - See "ack --help-types" for supported filetypes. + if ( $opt_L ? !$is_match : $is_match ) { + App::Ack::print( $resource->name, $ors ); + ++$nmatches; -File type specification: - --type-set TYPE:FILTER:FILTERARGS - Files with the given FILTERARGS applied to the - given FILTER are recognized as being of type - TYPE. This replaces an existing definition for - type TYPE. - --type-add TYPE:FILTER:FILTERARGS - Files with the given FILTERARGS applied to the - given FILTER are recognized as being type TYPE. - --type-del TYPE Removes all filters associated with TYPE. + last RESOURCES if $only_first; + last RESOURCES if defined($opt_m) && $nmatches >= $opt_m; + } + } + else { + $nmatches += print_matches_in_resource( $resource, $opt ); + if ( $nmatches && $only_first ) { + last RESOURCES; + } + } + } + if ( $opt_count && !$opt_show_filename ) { + App::Ack::print( $total_count, "\n" ); + } -Miscellaneous: - --[no]env Ignore environment variables and global ackrc - files. --env is legal but redundant. - --ackrc=filename Specify an ackrc file to use - --ignore-ack-defaults Ignore default definitions included with ack. - --create-ackrc Outputs a default ackrc for your customization - to standard output. - --help, -? This help - --help-types Display all known types - --dump Dump information on which options are loaded - from which RC files - --[no]filter Force ack to treat standard input as a pipe - (--filter) or tty (--nofilter) - --man Man page - --version Display version & copyright - --thpppt Bill the Cat - --bar The warning admiral - --cathy Chocolate! Chocolate! Chocolate! + close $App::Ack::fh; + App::Ack::exit_from_ack( $nmatches ); +} + + + +=head1 NAME + +ack - grep-like text finder + +=head1 SYNOPSIS + + ack [options] PATTERN [FILE...] + ack -f [options] [DIRECTORY...] + +=head1 DESCRIPTION + +Ack is designed as an alternative to F for programmers. + +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. + +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. + +Ack can also list files that would be searched, without actually +searching them, to let you take advantage of ack's file-type filtering +capabilities. + +=head1 FILE SELECTION + +If files are not specified for searching, either on the command +line or piped in with the C<-x> option, I delves into +subdirectories selecting files for searching. + +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. -Exit status is 0 if match, 1 if no match. +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. -This is version $VERSION of ack. -END_OF_HELP +The default options for I ignore certain files and directories. These +include: - return; - } +=over 4 +=item * Backup files: Files matching F<#*#> or ending with F<~>. +=item * Coredumps: Files matching F -sub show_help_types { - App::Ack::print( <<'END_OF_HELP' ); -Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES] +=item * Version control directories like F<.svn> and F<.git>. -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. +=back -Note that some extensions may appear in multiple types. For example, -.pod files are both Perl and Parrot. +Run I with the C<--dump> option to see what settings are set. -END_OF_HELP +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 @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}; +=head1 DIRECTORY SELECTION - 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 ) ); - } +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. - return; -} +For a complete list of directories that do not get searched, run +C. -sub show_man { - require Pod::Usage; +=head1 WHEN TO USE GREP - Pod::Usage::pod2usage({ - -input => $App::Ack::orig_program_name, - -verbose => 2, - -exitval => 0, - }); +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. - return; -} +E.g., searching through huge files looking for regexes that can be +expressed with I syntax should be quicker with I. +If your script or parent program uses I C<--quiet> or C<--silent> +or needs exit 2 on IO error, use I. -sub get_version_statement { - require Config; +=head1 OPTIONS - 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 ); +=over 4 - my $git_revision = $GIT_REVISION ? " (git commit $GIT_REVISION)" : ''; +=item B<--ackrc> - return <<"END_OF_VERSION"; -ack ${VERSION}${git_revision} -Running under Perl $ver at $this_perl +Specifies an ackrc file to load after all others; see L. -$copyright +=item B<-A I>, B<--after-context=I> -This program is free software. You may modify or distribute it -under the terms of the Artistic License v2.0. -END_OF_VERSION -} +Print I lines of trailing context after matching lines. +=item B<-B I>, B<--before-context=I> -sub print_version_statement { - App::Ack::print( get_version_statement() ); +Print I lines of leading context before matching lines. - return; -} +=item B<--[no]break> +Print a break between results from different files. On by default +when used interactively. -sub get_copyright { - return $COPYRIGHT; -} +=item B<-C [I]>, B<--context[=I]> +Print I lines (default 2) of context around matching lines. -# 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; +=item B<-c>, B<--count> - if ($show_filename) { - App::Ack::print( $filename ); - App::Ack::print( ':', $nmatches ) if $count; - } - else { - App::Ack::print( $nmatches ) if $count; - } - App::Ack::print( $ors ); +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. - return; -} +If combined with B<-h> (B<--no-filename>) ack outputs only one total +count. -sub print_count0 { - my $filename = shift; - my $ors = shift; - my $show_filename = shift; +=item B<--[no]color>, B<--[no]colour> - if ($show_filename) { - App::Ack::print( $filename, ':0', $ors ); - } - else { - App::Ack::print( '0', $ors ); - } +B<--color> highlights the matching text. B<--nocolor> suppresses +the color. This is on by default unless the output is redirected. - return; -} +On Windows, this option is off by default unless the +L module is installed or the C +environment variable is used. -sub set_up_pager { - my $command = shift; +=item B<--color-filename=I> - return if App::Ack::output_to_pipe(); +Sets the color to be used for filenames. - my $pager; - if ( not open( $pager, '|-', $command ) ) { - App::Ack::die( qq{Unable to pipe to pager "$command": $!} ); - } - $fh = $pager; +=item B<--color-match=I> - return; -} +Sets the color to be used for matches. +=item B<--color-lineno=I> -sub output_to_pipe { - return $output_to_pipe; -} +Sets the color to be used for line numbers. +=item B<--[no]column> -sub exit_from_ack { - my $nmatches = shift; +Show the column number of the first match. This is helpful for +editors that can place your cursor at a given position. - my $rc = $nmatches ? 0 : 1; - exit $rc; -} +=item B<--create-ackrc> +Dumps the default ack options to standard output. This is useful for +when you want to customize the defaults. +=item B<--dump> -1; # End of App::Ack -package App::Ack::Resource; +Writes the list of options loaded and where they came from to standard +output. Handy for debugging. +=item B<--[no]env> -use warnings; -use strict; -use overload - '""' => 'name'; +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. -sub FAIL { - require Carp; - Carp::confess( 'Must be overloaded' ); -} +=item B<--flush> +B<--flush> flushes output immediately. This is off by default +unless ack is running interactively (when output goes to a pipe or +file). -sub new { - return FAIL(); -} +=item B<-f> +Only print the files that would be searched, without actually doing +any searching. PATTERN must not be specified, or it will be taken +as a path to search. -sub name { - return FAIL(); -} +=item B<--files-from=I> +The list of files to be searched is specified in I. The list of +files are separated by newlines. If I is C<->, the list is loaded +from standard input. -sub is_binary { - return FAIL(); -} +=item B<--[no]filter> +Forces ack to act as if it were receiving input via a pipe. -sub open { - return FAIL(); -} +=item B<--[no]follow> +Follow or don't follow symlinks, other than whatever starting files +or directories were specified on the command line. -sub needs_line_scan { - return FAIL(); -} +This is off by default. +=item B<-g I> -sub reset { - return FAIL(); -} +Print files where the relative path + filename matches I. +This option can be combined with B<--color> to make it easier to spot +the match. +=item B<--[no]group> -sub close { - return FAIL(); -} +B<--group> groups matches by file name. This is the default +when used interactively. + +B<--nogroup> prints one result per line, like grep. This is the +default when output is redirected. + +=item B<-H>, B<--with-filename> + +Print the filename for each match. This is the default unless searching +a single explicitly specified file. + +=item B<-h>, B<--no-filename> +Suppress the prefixing of filenames on output when multiple files are +searched. -sub clone { - return FAIL(); -} +=item B<--[no]heading> +Print a filename heading above each file's results. This is the default +when used interactively. -sub firstliney { - return FAIL(); -} +=item B<--help>, B<-?> -1; -package App::Ack::Resources; +Print a short help statement. +=item B<--help-types>, B<--help=types> +Print all known types. -use warnings; -use strict; +=item B<-i>, B<--ignore-case> +Ignore case distinctions in PATTERN -sub from_argv { - my $class = shift; - my $opt = shift; - my $start = shift; +=item B<--ignore-ack-defaults> - my $self = bless {}, $class; +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 $file_filter = undef; - my $descend_filter = $opt->{descend_filter}; +=item B<--[no]ignore-dir=I>, B<--[no]ignore-directory=I> - if( $opt->{n} ) { - $descend_filter = sub { - return 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). - $self->{iter} = - File::Next::files( { - file_filter => $opt->{file_filter}, - descend_filter => $descend_filter, - error_handler => sub { my $msg = shift; App::Ack::warn( $msg ) }, - sort_files => $opt->{sort_files}, - follow_symlinks => $opt->{follow}, - }, @{$start} ); +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. - return $self; -} +=item B<--ignore-file=I> +Ignore files matching I. The filters are specified +identically to file type filters as seen in L. -sub from_file { - my $class = shift; - my $opt = shift; - my $file = shift; +=item B<-k>, B<--known-types> - my $iter = - File::Next::from_file( { - error_handler => sub { my $msg = shift; App::Ack::warn( $msg ) }, - warning_handler => sub { my $msg = shift; App::Ack::warn( $msg ) }, - sort_files => $opt->{sort_files}, - }, $file ) or return undef; +Limit selected files to those with types that ack knows about. This is +equivalent to the default behavior found in ack 1. - return bless { - iter => $iter, - }, $class; -} +=item B<--lines=I> -# This is for reading input lines from STDIN, not the list of files from STDIN -sub from_stdin { - my $class = shift; - my $opt = shift; +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. - my $self = bless {}, $class; +=item B<-l>, B<--files-with-matches> - my $has_been_called = 0; +Only print the filenames of matching files, instead of the matching text. - $self->{iter} = sub { - if ( !$has_been_called ) { - $has_been_called = 1; - return '-'; - } - return; - }; +=item B<-L>, B<--files-without-matches> - return $self; -} +Only print the filenames of files that do I match. -sub next { - my $self = shift; +=item B<--match I> - my $file = $self->{iter}->() or return; +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. - return App::Ack::Resource::Basic->new( $file ); -} + # search for foo and bar in given files + ack file1 t/file* --match foo + ack file1 t/file* --match bar -1; -package App::Ack::Resource::Basic; +=item B<-m=I>, B<--max-count=I> +Stop reading a file after I matches. -use warnings; -use strict; +=item B<--man> -use Fcntl (); +Print this manual page. -BEGIN { - our @ISA = 'App::Ack::Resource'; -} +=item B<-n>, B<--no-recurse> +No descending into subdirectories. -sub new { - my $class = shift; - my $filename = shift; +=item B<-o> - my $self = bless { - filename => $filename, - fh => undef, - opened => 0, - }, $class; +Show only the part of each line matching PATTERN (turns off text +highlighting) - if ( $self->{filename} eq '-' ) { - $self->{fh} = *STDIN; - $self->{opened} = 1; - } +=item B<--output=I> - return $self; -} +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">. +=item B<--pager=I>, B<--nopager> -sub name { - return $_[0]->{filename}; -} +B<--pager> directs ack's output through I. This can also be specified +via the C and C environment variables. +Using --pager does not suppress grouping and coloring like piping +output on the command-line does. +B<--nopager> cancels any setting in ~/.ackrc, C or C. +No output will be sent through a pager. -sub needs_line_scan { - my $self = shift; - my $opt = shift; +=item B<--passthru> - return 1 if $opt->{v}; +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: - my $size = -s $self->{fh}; - if ( $size == 0 ) { - return 0; - } - elsif ( $size > 100_000 ) { - return 1; - } + # Watch a log file, and highlight a certain IP address + $ tail -f ~/access.log | ack --passthru 123.45.67.89 - my $buffer; - my $rc = sysread( $self->{fh}, $buffer, $size ); - if ( !defined($rc) && $App::Ack::report_bad_filenames ) { - App::Ack::warn( "$self->{filename}: $!" ); - return 1; - } - return 0 unless $rc && ( $rc == $size ); +=item B<--print0> - my $regex = $opt->{regex}; - return $buffer =~ /$regex/m; -} +Only works in conjunction with -f, -g, -l or -c (filename output). The filenames +are output separated with a null byte instead of the usual newline. This is +helpful when dealing with filenames that contain whitespace, e.g. + # remove all files of type html + ack -f --html --print0 | xargs -0 rm -f -sub reset { - my $self = shift; +=item B<-Q>, B<--literal> - # return if we haven't opened the file yet - if ( !defined($self->{fh}) ) { - return; - } +Quote all metacharacters in PATTERN, it is treated as a literal. - if( !seek( $self->{fh}, 0, 0 ) && $App::Ack::report_bad_filenames ) { - App::Ack::warn( "$self->{filename}: $!" ); - } +=item B<-r>, B<-R>, B<--recurse> - return; -} +Recurse into sub-directories. This is the default and just here for +compatibility with grep. You can also use it for turning B<--no-recurse> off. +=item B<-s> -sub close { - my $self = shift; +Suppress error messages about nonexistent or unreadable files. This is taken +from fgrep. - # return if we haven't opened the file yet - if ( !defined($self->{fh}) ) { - return; - } +=item B<--[no]smart-case>, B<--no-smart-case> - if ( !close($self->{fh}) && $App::Ack::report_bad_filenames ) { - App::Ack::warn( $self->name() . ": $!" ); - } +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. - $self->{opened} = 0; +B<-i> always overrides this option. - return; -} +=item B<--sort-files> +Sorts the found files lexicographically. Use this if you want your file +listings to be deterministic between runs of I. -sub clone { - my ( $self ) = @_; +=item B<--show-types> - return __PACKAGE__->new($self->name); -} +Outputs the filetypes that ack associates with each file. -sub firstliney { - my ( $self ) = @_; +Works with B<-f> and B<-g> options. - my $fh = $self->open(); +=item B<--type=[no]TYPE> - 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; - } +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 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. - $self->close; +Type specifications can be repeated and are ORed together. - return $self->{firstliney}; -} +See I for a list of valid types. -sub open { - my ( $self ) = @_; +=item B<--type-add I:I:I> - return $self->{fh} if $self->{opened}; +Files with the given FILTERARGS applied to the given FILTER +are recognized as being of (the existing) type TYPE. +See also L. - unless ( open $self->{fh}, '<', $self->{filename} ) { - return; - } - $self->{opened} = 1; +=item B<--type-set I:I:I> - return $self->{fh}; -} +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. -1; -package App::Ack::ConfigDefault; +=item B<--type-del I> -use warnings; -use strict; +The filters associated with TYPE are removed from Ack, and are no longer considered +for searches. -sub options { - my @options = split( /\n/, _options_block() ); - @options = grep { /./ && !/^#/ } @options; +=item B<-v>, B<--invert-match> - return @options; -} +Invert match: select non-matching lines -sub _options_block { - return <<'HERE'; -# This is the default ackrc for ack 2.0 +=item B<--version> -# There are four different ways to match -# -# is: Match the filename exactly -# -# ext: Match the extension of the filename exactly -# -# match: Match the filename against a Perl regular expression -# -# firstlinematch: Match the first 250 characters of the first line -# of text against a Perl regular expression. This is only for -# the --type-add option. +Display version and copyright information. +=item B<-w>, B<--word-regexp> -### Directories to ignore +Force PATTERN to match only whole words. The PATTERN is wrapped with +C<\b> metacharacters. -# Bazaar ---ignore-directory=is:.bzr +=item B<-x> -# Codeville ---ignore-directory=is:.cdv +An abbreviation for B<--files-from=->; the list of files to search are read +from standard input, with one line per file. -# Interface Builder ---ignore-directory=is:~.dep ---ignore-directory=is:~.dot ---ignore-directory=is:~.nib ---ignore-directory=is:~.plst +=item B<-1> -# Git ---ignore-directory=is:.git +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. -# Mercurial ---ignore-directory=is:.hg +=item B<--thpppt> -# quilt ---ignore-directory=is:.pc +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. -# Subversion ---ignore-directory=is:.svn +=item B<--bar> -# Monotone ---ignore-directory=is:_MTN +Check with the admiral for traps. -# CVS ---ignore-directory=is:CVS +=item B<--cathy> -# RCS ---ignore-directory=is:RCS +Chocolate, Chocolate, Chocolate! -# SCCS ---ignore-directory=is:SCCS +=back -# darcs ---ignore-directory=is:_darcs +=head1 THE .ackrc FILE -# Vault/Fortress ---ignore-directory=is:_sgbak +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: -# autoconf ---ignore-directory=is:autom4te.cache + # Always sort the files + --sort-files -# Perl module building ---ignore-directory=is:blib ---ignore-directory=is:_build + # Always color, even if piping to a another program + --color -# Perl Devel::Cover module's output directory ---ignore-directory=is:cover_db + # Use "less -r" as my pager + --pager=less -r -# Node modules created by npm ---ignore-directory=is:node_modules +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>. -# CMake cache ---ignore-directory=is:CMakeFiles +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. -### Files to ignore +=head1 Defining your own types -# Backup files ---ignore-file=ext:bak ---ignore-file=match:/~$/ +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. -# Emacs swap files ---ignore-file=match:/^#.+#$/ +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. -# vi/vim swap files ---ignore-file=match:/[._].*\.swp$/ +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. -# core dumps ---ignore-file=match:/core\.\d+$/ +When defining your own types in the F<.ackrc> file you have to use +the following: -# minified Javascript ---ignore-file=match:/[.-]min[.]js$/ ---ignore-file=match:/[.]js[.]min$/ + --type-set=eiffel:ext:e,eiffel -# minified CSS ---ignore-file=match:/[.]min[.]css$/ ---ignore-file=match:/[.]css[.]min$/ +or writing on separate lines -# PDFs, because they pass Perl's -T detection ---ignore-file=ext:pdf + --type-set + eiffel:ext:e,eiffel -# Common graphics, just as an optimization ---ignore-file=ext:gif,jpg,jpeg,png +The following does B work in the F<.ackrc> file: + --type-set eiffel:ext:e,eiffel -### Filetypes defined -# Perl http://perl.org/ ---type-add=perl:ext:pl,pm,pod,t,psgi ---type-add=perl:firstlinematch:/^#!.*\bperl/ +In order to see all currently defined types, use I<--help-types>, e.g. +I -# Perl tests ---type-add=perltest:ext:t +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. -# Makefiles http://www.gnu.org/s/make/ ---type-add=make:ext:mk ---type-add=make:ext:mak ---type-add=make:is:makefile ---type-add=make:is:Makefile ---type-add=make:is:GNUmakefile +=over 4 -# Rakefiles http://rake.rubyforge.org/ ---type-add=rake:is:Rakefile +=item is:I -# CMake http://www.cmake.org/ ---type-add=cmake:is:CMakeLists.txt ---type-add=cmake:ext:cmake +I filters match the target filename exactly. It takes exactly one +argument, which is the name of the file to match. -# Actionscript ---type-add=actionscript:ext:as,mxml +Example: -# Ada http://www.adaic.org/ ---type-add=ada:ext:ada,adb,ads + --type-set make:is:Makefile -# ASP http://msdn.microsoft.com/en-us/library/aa286483.aspx ---type-add=asp:ext:asp +=item ext:I[,I[,...]] -# ASP.Net http://www.asp.net/ ---type-add=aspx:ext:master,ascx,asmx,aspx,svc +I filters match the extension of the target file against a list +of extensions. No leading dot is needed for the extensions. -# Assembly ---type-add=asm:ext:asm,s +Example: -# Batch ---type-add=batch:ext:bat,cmd + --type-set perl:ext:pl,pm,t -# ColdFusion http://en.wikipedia.org/wiki/ColdFusion ---type-add=cfmx:ext:cfc,cfm,cfml +=item match:I -# Clojure http://clojure.org/ ---type-add=clojure:ext:clj +I filters match the target filename against a regular expression. +The regular expression is made case insensitive for the search. -# C -# .xs are Perl C files ---type-add=cc:ext:c,h,xs +Example: -# C header files ---type-add=hh:ext:h + --type-set make:match:/(gnu)?makefile/ -# CoffeeScript http://coffeescript.org/ ---type-add=coffeescript:ext:coffee +=item firstlinematch:I -# C++ ---type-add=cpp:ext:cpp,cc,cxx,m,hpp,hh,h,hxx +I matches the first line of the target file against a +regular expression. Like I, the regular expression is made +case insensitive. -# C# ---type-add=csharp:ext:cs +Example: -# CSS http://www.w3.org/Style/CSS/ ---type-add=css:ext:css + --type-add perl:firstlinematch:/perl/ -# Dart http://www.dartlang.org/ ---type-add=dart:ext:dart +=back -# Delphi http://en.wikipedia.org/wiki/Embarcadero_Delphi ---type-add=delphi:ext:pas,int,dfm,nfm,dof,dpk,dproj,groupproj,bdsgroup,bdsproj +More filter types may be made available in the future. -# Elixir http://elixir-lang.org/ ---type-add=elixir:ext:ex,exs +=head1 ENVIRONMENT VARIABLES -# Emacs Lisp http://www.gnu.org/software/emacs ---type-add=elisp:ext:el +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. -# Erlang http://www.erlang.org/ ---type-add=erlang:ext:erl,hrl +=over 4 + +=item ACKRC + +Specifies the location of the user's F<.ackrc> file. If this file doesn't +exist, F looks in the default location. + +=item ACK_OPTIONS + +This variable specifies default options to be placed in front of +any explicit options on the command line. -# Fortran http://en.wikipedia.org/wiki/Fortran ---type-add=fortran:ext:f,f77,f90,f95,f03,for,ftn,fpp +=item ACK_COLOR_FILENAME -# Google Go http://golang.org/ ---type-add=go:ext:go +Specifies the color of the filename when it's printed in B<--group> +mode. By default, it's "bold green". -# Groovy http://groovy.codehaus.org/ ---type-add=groovy:ext:groovy,gtmpl,gpp,grunit,gradle +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. -# Haskell http://www.haskell.org/ ---type-add=haskell:ext:hs,lhs +This option can also be set with B<--color-filename>. -# HTML ---type-add=html:ext:htm,html +=item ACK_COLOR_MATCH -# Java http://www.oracle.com/technetwork/java/index.html ---type-add=java:ext:java,properties +Specifies the color of the matching text when printed in B<--color> +mode. By default, it's "black on_yellow". -# JavaScript ---type-add=js:ext:js +This option can also be set with B<--color-match>. -# JSP http://www.oracle.com/technetwork/java/javaee/jsp/index.html ---type-add=jsp:ext:jsp,jspx,jhtm,jhtml +See B for the color specifications. -# JSON http://www.json.org/ ---type-add=json:ext:json +=item ACK_COLOR_LINENO -# Less http://www.lesscss.org/ ---type-add=less:ext:less +Specifies the color of the line number when printed in B<--color> +mode. By default, it's "bold yellow". -# Common Lisp http://common-lisp.net/ ---type-add=lisp:ext:lisp,lsp +This option can also be set with B<--color-lineno>. -# Lua http://www.lua.org/ ---type-add=lua:ext:lua ---type-add=lua:firstlinematch:/^#!.*\blua(jit)?/ +See B for the color specifications. -# Objective-C ---type-add=objc:ext:m,h +=item ACK_PAGER -# Objective-C++ ---type-add=objcpp:ext:mm,h +Specifies a pager program, such as C, C or C, to which +ack will send its output. -# OCaml http://caml.inria.fr/ ---type-add=ocaml:ext:ml,mli +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. -# Matlab http://en.wikipedia.org/wiki/MATLAB ---type-add=matlab:ext:m +C overrides C if both are specified. -# Parrot http://www.parrot.org/ ---type-add=parrot:ext:pir,pasm,pmc,ops,pod,pg,tg +=item ACK_PAGER_COLOR -# PHP http://www.php.net/ ---type-add=php:ext:php,phpt,php3,php4,php5,phtml ---type-add=php:firstlinematch:/^#!.*\bphp/ +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. -# Plone http://plone.org/ ---type-add=plone:ext:pt,cpt,metadata,cpy,py +If you are not on Windows, you never need to use C. -# Python http://www.python.org/ ---type-add=python:ext:py ---type-add=python:firstlinematch:/^#!.*\bpython/ +=back -# R http://www.r-project.org/ ---type-add=rr:ext:R +=head1 AVAILABLE COLORS -# Ruby http://www.ruby-lang.org/ ---type-add=ruby:ext:rb,rhtml,rjs,rxml,erb,rake,spec ---type-add=ruby:is:Rakefile ---type-add=ruby:firstlinematch:/^#!.*\bruby/ +F uses the colors available in Perl's L module, which +provides the following listed values. Note that case does not matter when using +these values. -# Rust http://www.rust-lang.org/ ---type-add=rust:ext:rs +=head2 Foreground colors -# Sass http://sass-lang.com ---type-add=sass:ext:sass,scss + black red green yellow blue magenta cyan white -# Scala http://www.scala-lang.org/ ---type-add=scala:ext:scala + bright_black bright_red bright_green bright_yellow + bright_blue bright_magenta bright_cyan bright_white -# Scheme http://groups.csail.mit.edu/mac/projects/scheme/ ---type-add=scheme:ext:scm,ss +=head2 Background colors -# Shell ---type-add=shell:ext:sh,bash,csh,tcsh,ksh,zsh,fish ---type-add=shell:firstlinematch:/^#!.*\b(?:ba|t?c|k|z|fi)?sh\b/ + on_black on_red on_green on_yellow + on_blue on_magenta on_cyan on_white -# Smalltalk http://www.smalltalk.org/ ---type-add=smalltalk:ext:st + on_bright_black on_bright_red on_bright_green on_bright_yellow + on_bright_blue on_bright_magenta on_bright_cyan on_bright_white -# SQL http://www.iso.org/iso/catalogue_detail.htm?csnumber=45498 ---type-add=sql:ext:sql,ctl +=head1 ACK & OTHER TOOLS -# Tcl http://www.tcl.tk/ ---type-add=tcl:ext:tcl,itcl,itk +=head2 Vim integration -# LaTeX http://www.latex-project.org/ ---type-add=tex:ext:tex,cls,sty +F integrates easily with the Vim text editor. Set this in your +F<.vimrc> to use F instead of F: -# Template Toolkit http://template-toolkit.org/ ---type-add=tt:ext:tt,tt2,ttml + set grepprg=ack\ -k -# Visual Basic ---type-add=vb:ext:bas,cls,frm,ctl,vb,resx +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: -# Verilog ---type-add=verilog:ext:v,vh,sv + :grep Dumper perllib -# VHDL http://www.eda.org/twiki/bin/view.cgi/P1076/WebHome ---type-add=vhdl:ext:vhd,vhdl +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. -# Vim http://www.vim.org/ ---type-add=vim:ext:vim +L -# XML http://www.w3.org/TR/REC-xml/ ---type-add=xml:ext:xml,dtd,xsl,xslt,ent ---type-add=xml:firstlinematch:/<[?]xml/ +=head2 Emacs integration -# YAML http://yaml.org/ ---type-add=yaml:ext:yaml,yml -HERE -} +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." -1; -package App::Ack::ConfigFinder; +L +=head2 TextMate integration -use strict; -use warnings; +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" -use Cwd 3.00 (); -use File::Spec 3.00; +=head2 Shell and Return Code -use if ($^O eq 'MSWin32'), 'Win32'; +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. +(Shell exit code 1 is C<$?=256> in perl with C or backticks.) -sub new { - my ( $class ) = @_; +The I code 2 for errors is not used. - return bless {}, $class; -} +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. -sub _remove_redundancies { - my ( @configs ) = @_; +=cut - if ( $App::Ack::is_windows ) { - # inode stat always returns 0 on windows, so just check filenames. - my (%seen, @uniq); +=head1 DEBUGGING ACK PROBLEMS - foreach my $path (@configs) { - push @uniq, $path unless $seen{$path}; - $seen{$path} = 1; - } +If ack gives you output you're not expecting, start with a few simple steps. - return @uniq; - } +=head2 Use B<--noenv> - else { +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>. - my %dev_and_inode_seen; +=head2 Use B<-f> to see what files have been selected - foreach my $path ( @configs ) { - my ( $dev, $inode ) = (stat $path)[0, 1]; +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. - if( defined($dev) ) { - if( $dev_and_inode_seen{"$dev:$inode"} ) { - undef $path; - } - else { - $dev_and_inode_seen{"$dev:$inode"} = 1; - } - } - } +=head2 Use B<--dump> - return grep { defined() } @configs; +This lists the ackrc files that are loaded and the options loaded +from them. +So for example you can find a list of directories that do not get searched or where filetypes are defined. - } -} +=head1 TIPS -sub _check_for_ackrc { - return unless defined $_[0]; +=head2 Use the F<.ackrc> file. - my @files = grep { -f } - map { File::Spec->catfile(@_, $_) } - qw(.ackrc _ackrc); +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. - die File::Spec->catdir(@_) . " contains both .ackrc and _ackrc.\n" . - "Please remove one of those files.\n" - if @files > 1; +=head2 Use F<-f> for working with big codesets - return wantarray ? @files : $files[0]; -} # end _check_for_ackrc +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: + # Change all "this" to "that" in all Perl files in a tree. + ack -f --perl | xargs perl -p -i -e's/this/that/g' -sub find_config_files { - my @config_files; +or if you prefer: - if ( $App::Ack::is_windows ) { - push @config_files, map { File::Spec->catfile($_, 'ackrc') } ( - Win32::GetFolderPath(Win32::CSIDL_COMMON_APPDATA()), - Win32::GetFolderPath(Win32::CSIDL_APPDATA()), - ); - } - else { - push @config_files, '/etc/ackrc'; - } + perl -p -i -e's/this/that/g' $(ack -f --perl) +=head2 Use F<-Q> when in doubt about metacharacters - if ( $ENV{'ACKRC'} && -f $ENV{'ACKRC'} ) { - push @config_files, $ENV{'ACKRC'}; - } - else { - push @config_files, _check_for_ackrc($ENV{'HOME'}); - } +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... - my @dirs = File::Spec->splitdir(Cwd::getcwd()); - while(@dirs) { - my $ackrc = _check_for_ackrc(@dirs); - if(defined $ackrc) { - push @config_files, $ackrc; - last; - } - pop @dirs; - } +=head2 Use ack to watch log files - # XXX we only test for existence here, so if the file is - # deleted out from under us, this will fail later. =( - return _remove_redundancies( @config_files ); -} +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. + ack -Q aa.bb.cc.dd /path/to/access.log | ack -Q -B5 troublesome.gif -sub read_rcfile { - my $file = shift; +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. - return unless defined $file && -e $file; +=head2 Examples of F<--output> - my @lines; +Following variables are useful in the expansion string: - open( my $fh, '<', $file ) or App::Ack::die( "Unable to read $file: $!" ); - while ( my $line = <$fh> ) { - chomp $line; - $line =~ s/^\s+//; - $line =~ s/\s+$//; +=over 4 - next if $line eq ''; - next if $line =~ /^#/; +=item C<$&> - push( @lines, $line ); - } - close $fh; +The whole string matched by PATTERN. - return @lines; -} +=item C<$1>, C<$2>, ... -1; -package App::Ack::ConfigLoader; +The contents of the 1st, 2nd ... bracketed group in PATTERN. -use strict; -use warnings; +=item C<$`> -use Carp 1.04 (); -use Getopt::Long 2.35 (); -use Text::ParseWords 3.1 (); +The string before the match. +=item C<$'> -my @INVALID_COMBINATIONS; +The string after the match. -BEGIN { - my @context = qw( -A -B -C --after-context --before-context --context ); - my @pretty = qw( --heading --group --break ); - my @filename = qw( -h -H --with-filename --no-filename ); +=back - @INVALID_COMBINATIONS = ( - # XXX normalize - [qw(-l)] => [@context, @pretty, @filename, qw(-L -o --passthru --output --max-count --column -f -g --show-types)], - [qw(-L)] => [@context, @pretty, @filename, qw(-l -o --passthru --output --max-count --column -f -g --show-types -c --count)], - [qw(--line)] => [@context, @pretty, @filename, qw(-l --files-with-matches --files-without-matches -L -o --passthru --match -m --max-count -1 -c --count --column --print0 -f -g --show-types)], - [qw(-o)] => [@context, qw(--output -c --count --column --column -f --show-types)], - [qw(--passthru)] => [@context, qw(--output --column -m --max-count -1 -c --count -f -g)], - [qw(--output)] => [@context, qw(-c --count -f -g)], - [qw(--match)] => [qw(-f -g)], - [qw(-m --max-count)] => [qw(-1 -f -g -c --count)], - [qw(-h --no-filename)] => [qw(-H --with-filename -f -g --group --heading)], - [qw(-H --with-filename)] => [qw(-h --no-filename -f -g)], - [qw(-c --count)] => [@context, @pretty, qw(--column -f -g)], - [qw(--column)] => [qw(-f -g)], - [@context] => [qw(-f -g)], - [qw(-f)] => [qw(-g), @pretty], - [qw(-g)] => [qw(-f), @pretty], - ); -} +For more details and other variables see +L. -sub process_filter_spec { - my ( $spec ) = @_; +This example shows how to add text around a particular pattern +(in this case adding _ around word with "e") - if ( $spec =~ /^(\w+):(\w+):(.*)/ ) { - my ( $type_name, $ext_type, $arguments ) = ( $1, $2, $3 ); + 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 - return ( $type_name, - App::Ack::Filter->create_filter($ext_type, split(/,/, $arguments)) ); - } - elsif ( $spec =~ /^(\w+)=(.*)/ ) { # Check to see if we have ack1-style argument specification. - my ( $type_name, $extensions ) = ( $1, $2 ); +This shows how to pick out particular parts of a match using ( ) within regular expression. - my @extensions = split(/,/, $extensions); - foreach my $extension ( @extensions ) { - $extension =~ s/^[.]//; - } + ack '=head(\d+)\s+(.*)' --output=' $1 : $2' + input file contains "=head1 NAME" + output "1 : NAME" - return ( $type_name, App::Ack::Filter->create_filter('ext', @extensions) ); - } - else { - Carp::croak "invalid filter specification '$spec'"; - } -} +=head2 Share your knowledge -sub uninvert_filter { - my ( $opt, @filters ) = @_; +Join the ack-users mailing list. Send me your tips and I may add +them here. - return unless defined $opt->{filters} && @filters; +=head1 FAQ - # Loop through all the registered filters. If we hit one that - # matches this extension and it's inverted, we need to delete it from - # the options. - for ( my $i = 0; $i < @{ $opt->{filters} }; $i++ ) { - my $opt_filter = @{ $opt->{filters} }[$i]; +=head2 Why isn't ack finding a match in (some file)? - # XXX Do a real list comparison? This just checks string equivalence. - if ( $opt_filter->is_inverted() && "$opt_filter->{filter}" eq "@filters" ) { - splice @{ $opt->{filters} }, $i, 1; - $i--; - } - } -} +Probably because it's of a type that ack doesn't recognize. ack's +searching behavior is driven by filetype. B -sub process_filetypes { - my ( $opt, $arg_sources ) = @_; +Use the C<-f> switch to see a list of files that ack will search +for you. You can use the C<--show-types> switch to show which type +ack thinks each file is. - Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); # start with default options, minus some annoying ones - Getopt::Long::Configure( - 'no_ignore_case', - 'no_auto_abbrev', - 'pass_through', - ); - my %additional_specs; +=head2 Wouldn't it be great if F did search & replace? - my $add_spec = sub { - my ( undef, $spec ) = @_; +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. - my ( $name, $filter ) = process_filter_spec($spec); +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: - push @{ $App::Ack::mappings{$name} }, $filter; + $ perl -i -p -e's/foo/bar/g' $(ack -f --php) - $additional_specs{$name . '!'} = sub { - my ( undef, $value ) = @_; +=head2 Can I make ack recognize F<.xyz> files? - my @filters = @{ $App::Ack::mappings{$name} }; - if ( not $value ) { - @filters = map { $_->invert() } @filters; - } - else { - uninvert_filter( $opt, @filters ); - } +Yes! Please see L. If you think +that F should recognize a type by default, please see +L. - push @{ $opt->{'filters'} }, @filters; - }; - }; +=head2 There's already a program/package called ack. - my $set_spec = sub { - my ( undef, $spec ) = @_; +Yes, I know. - my ( $name, $filter ) = process_filter_spec($spec); +=head2 Why is it called ack if it's called ack-grep? - $App::Ack::mappings{$name} = [ $filter ]; +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. - $additional_specs{$name . '!'} = sub { - my ( undef, $value ) = @_; +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. - my @filters = @{ $App::Ack::mappings{$name} }; - if ( not $value ) { - @filters = map { $_->invert() } @filters; - } +To do that, run this with F or as root: - push @{ $opt->{'filters'} }, @filters; - }; - }; + ln -s /usr/bin/ack-grep /usr/bin/ack - my $delete_spec = sub { - my ( undef, $name ) = @_; +Alternatively, you could use a shell alias: - delete $App::Ack::mappings{$name}; - delete $additional_specs{$name . '!'}; - }; + # bash/zsh + alias ack=ack-grep - my %type_arg_specs = ( - 'type-add=s' => $add_spec, - 'type-set=s' => $set_spec, - 'type-del=s' => $delete_spec, - ); + # csh + alias ack ack-grep - for ( my $i = 0; $i < @{$arg_sources}; $i += 2) { - my ( $source_name, $args ) = @{$arg_sources}[ $i, $i + 1]; +=head2 What does F mean? - if ( ref($args) ) { - # $args are modified in place, so no need to munge $arg_sources - local @ARGV = @{$args}; - Getopt::Long::GetOptions(%type_arg_specs); - @{$args} = @ARGV; - } - else { - ( undef, $arg_sources->[$i + 1] ) = - Getopt::Long::GetOptionsFromString($args, %type_arg_specs); - } - } +Nothing. I wanted a name that was easy to type and that you could +pronounce as a single syllable. - $additional_specs{'k|known-types'} = sub { - my ( undef, $value ) = @_; +=head2 Can I do multi-line regexes? - my @filters = map { @{$_} } values(%App::Ack::mappings); +No, ack does not support regexes that match multiple lines. Doing +so would require reading in the entire file at a time. - push @{ $opt->{'filters'} }, @filters; - }; +If you want to see lines near your match, use the C<--A>, C<--B> +and C<--C> switches for displaying context. - return \%additional_specs; -} +=head2 Why is ack telling me I have an invalid option when searching for C<+foo>? -sub removed_option { - my ( $option, $explanation ) = @_; +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!) - $explanation ||= ''; - return sub { - warn "Option '$option' is not valid in ack 2\n$explanation"; - exit 1; - }; -} +=head2 Why does C<"ack '.{40000,}'"> fail? Isn't that a valid regex? -sub get_arg_spec { - my ( $opt, $extra_specs ) = @_; +The Perl language limits the repetition quanitifier to 32K. You +can search for C<.{32767}> but not C<.{32768}>. - my $dash_a_explanation = < sub { $opt->{1} = $opt->{m} = 1 }, - 'A|after-context=i' => \$opt->{after_context}, - 'B|before-context=i' - => \$opt->{before_context}, - 'C|context:i' => sub { shift; my $val = shift; $opt->{before_context} = $opt->{after_context} = ($val || 2) }, - 'a' => removed_option('-a', $dash_a_explanation), - 'all' => removed_option('--all', $dash_a_explanation), - 'break!' => \$opt->{break}, - c => \$opt->{count}, - 'color|colour!' => \$opt->{color}, - 'color-match=s' => \$ENV{ACK_COLOR_MATCH}, - 'color-filename=s' => \$ENV{ACK_COLOR_FILENAME}, - 'color-lineno=s' => \$ENV{ACK_COLOR_LINENO}, - 'column!' => \$opt->{column}, - count => \$opt->{count}, - 'create-ackrc' => sub { print "$_\n" for ( '--ignore-ack-defaults', App::Ack::ConfigDefault::options() ); exit; }, - 'env!' => sub { - my ( undef, $value ) = @_; +Ack can load its configuration from many sources. The following list +specifies the sources Ack looks for configuration files; each one +that is found is loaded in the order specified here, and +each one overrides options set in any of the sources preceding +it. (For example, if I set --sort-files in my user ackrc, and +--nosort-files on the command line, the command line takes +precedence) - if ( !$value ) { - $opt->{noenv_seen} = 1; - } - }, - f => \$opt->{f}, - 'files-from=s' => \$opt->{files_from}, - 'filter!' => \$App::Ack::is_filter_mode, - flush => \$opt->{flush}, - 'follow!' => \$opt->{follow}, - g => \$opt->{g}, - G => removed_option('-G'), - 'group!' => sub { shift; $opt->{heading} = $opt->{break} = shift }, - 'heading!' => \$opt->{heading}, - 'h|no-filename' => \$opt->{h}, - 'H|with-filename' => \$opt->{H}, - 'i|ignore-case' => \$opt->{i}, - 'ignore-directory|ignore-dir=s' # XXX Combine this version with the negated version below - => sub { - my ( undef, $dir ) = @_; - - $dir = App::Ack::remove_dir_sep( $dir ); - if ( $dir !~ /^(?:is|match):/ ) { - $dir = 'is:' . $dir; - } - push @{ $opt->{idirs} }, $dir; - }, - 'ignore-file=s' => sub { - my ( undef, $file ) = @_; - push @{ $opt->{ifiles} }, $file; - }, - 'lines=s' => sub { shift; my $val = shift; push @{$opt->{lines}}, $val }, - 'l|files-with-matches' - => \$opt->{l}, - 'L|files-without-matches' - => \$opt->{L}, - 'm|max-count=i' => \$opt->{m}, - 'match=s' => \$opt->{regex}, - 'n|no-recurse' => \$opt->{n}, - o => sub { $opt->{output} = '$&' }, - 'output=s' => \$opt->{output}, - 'pager:s' => sub { - my ( undef, $value ) = @_; +=over 4 - $opt->{pager} = $value || $ENV{PAGER}; - }, - 'noignore-directory|noignore-dir=s' - => sub { - my ( undef, $dir ) = @_; - - # XXX can you do --noignore-dir=match,...? - $dir = App::Ack::remove_dir_sep( $dir ); - if ( $dir !~ /^(?:is|match):/ ) { - $dir = 'is:' . $dir; - } - if ( $dir !~ /^(?:is|match):/ ) { - Carp::croak("invalid noignore-directory argument: '$dir'"); - } - - @{ $opt->{idirs} } = grep { - $_ ne $dir - } @{ $opt->{idirs} }; - - push @{ $opt->{no_ignore_dirs} }, $dir; - }, - 'nopager' => sub { $opt->{pager} = undef }, - 'passthru' => \$opt->{passthru}, - 'print0' => \$opt->{print0}, - 'Q|literal' => \$opt->{Q}, - 'r|R|recurse' => sub { $opt->{n} = 0 }, - 's' => \$opt->{dont_report_bad_filenames}, - 'show-types' => \$opt->{show_types}, - 'smart-case!' => \$opt->{smart_case}, - 'sort-files' => \$opt->{sort_files}, - 'type=s' => sub { - my ( $getopt, $value ) = @_; +=item * - my $cb_value = 1; - if ( $value =~ s/^no// ) { - $cb_value = 0; - } +Defaults are loaded from App::Ack::ConfigDefaults. This can be omitted +using C<--ignore-ack-defaults>. - my $callback = $extra_specs->{ $value . '!' }; +=item * Global ackrc - if ( $callback ) { - $callback->( $getopt, $cb_value ); - } - else { - Carp::croak( "Unknown type '$value'" ); - } - }, - 'u' => removed_option('-u'), - 'unrestricted' => removed_option('--unrestricted'), - 'v|invert-match' => \$opt->{v}, - 'w|word-regexp' => \$opt->{w}, - 'x' => sub { $opt->{files_from} = '-' }, +Options are then loaded from the global ackrc. This is located at +C on Unix-like systems. - 'version' => sub { App::Ack::print_version_statement(); exit; }, - 'help|?:s' => sub { shift; App::Ack::show_help(@_); exit; }, - 'help-types' => sub { App::Ack::show_help_types(); exit; }, - 'man' => sub { App::Ack::show_man(); exit; }, - $extra_specs ? %{$extra_specs} : (), - }; # arg_specs -} +Under Windows XP and earlier, the global ackrc is at +C -sub process_other { - my ( $opt, $extra_specs, $arg_sources ) = @_; +Under Windows Vista/7, the global ackrc is at +C - Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); # start with default options, minus some annoying ones - Getopt::Long::Configure( - 'bundling', - 'no_ignore_case', - ); +The C<--noenv> option prevents all ackrc files from being loaded. - my $argv_source; - my $is_help_types_active; +=item * User ackrc - for ( my $i = 0; $i < @{$arg_sources}; $i += 2 ) { - my ( $source_name, $args ) = @{$arg_sources}[ $i, $i + 1 ]; +Options are then loaded from the user's ackrc. This is located at +C<$HOME/.ackrc> on Unix-like systems. - if ( $source_name eq 'ARGV' ) { - $argv_source = $args; - last; - } - } +Under Windows XP and earlier, the user's ackrc is at +C. - if ( $argv_source ) { # This *should* always be true, but you never know... - my @copy = @{$argv_source}; - local @ARGV = @copy; +Under Windows Vista/7, the user's ackrc is at +C. - Getopt::Long::Configure('pass_through'); +If you want to load a different user-level ackrc, it may be specified +with the C<$ACKRC> environment variable. - Getopt::Long::GetOptions( - 'help-types' => \$is_help_types_active, - ); +The C<--noenv> option prevents all ackrc files from being loaded. - Getopt::Long::Configure('no_pass_through'); - } +=item * Project ackrc - my $arg_specs = get_arg_spec($opt, $extra_specs); +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>. - for ( my $i = 0; $i < @{$arg_sources}; $i += 2) { - my ($source_name, $args) = @{$arg_sources}[$i, $i + 1]; +=item * --ackrc - my $ret; - if ( ref($args) ) { - local @ARGV = @{$args}; - $ret = Getopt::Long::GetOptions( %{$arg_specs} ); - @{$args} = @ARGV; - } - else { - ( $ret, $arg_sources->[$i + 1] ) = - Getopt::Long::GetOptionsFromString( $args, %{$arg_specs} ); - } - if ( !$ret ) { - if ( !$is_help_types_active ) { - my $where = $source_name eq 'ARGV' ? 'on command line' : "in $source_name"; - App::Ack::die( "Invalid option $where" ); - } - } - if ( $opt->{noenv_seen} ) { - App::Ack::die( "--noenv found in $source_name" ); - } - } +The C<--ackrc> option may be included on the command line to specify an +ackrc file that can override all others. It is consulted even if C<--noenv> +is present. - # XXX We need to check on a -- in the middle of a non-ARGV source +=item * ACK_OPTIONS - return; -} +Options are then loaded from the environment variable C. This can +be omitted using C<--noenv>. -sub should_dump_options { - my ( $sources ) = @_; +=item * Command line - for(my $i = 0; $i < @{$sources}; $i += 2) { - 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::GetOptions( - 'dump' => \$dump, - ); - @{$options} = @ARGV; - return $dump; - } - } - return; -} +Options are then loaded from the command line. -sub explode_sources { - my ( $sources ) = @_; +=back - my @new_sources; +=head1 DIFFERENCES BETWEEN ACK 1.X AND ACK 2.X - Getopt::Long::Configure('default', 'pass_through', 'no_auto_help', 'no_auto_version'); +A lot of changes were made for ack 2; here is a list of them. - my %opt; - my $arg_spec = get_arg_spec(\%opt); +=head2 GENERAL CHANGES - my $add_type = sub { - my ( undef, $arg ) = @_; +=over 4 - # XXX refactor? - if ( $arg =~ /(\w+)=/) { - $arg_spec->{$1} = sub {}; - } - else { - ( $arg ) = split /:/, $arg; - $arg_spec->{$arg} = sub {}; - } - }; +=item * - my $del_type = sub { - my ( undef, $arg ) = @_; +When no selectors are specified, ack 1.x only searches through files that +it can map to a file type. ack 2.x, by contrast, will search through +every regular, non-binary file that is not explicitly ignored via +B<--ignore-file> or B<--ignore-dir>. This is similar to the behavior of the +B<-a/--all> option in ack 1.x. - delete $arg_spec->{$arg}; - }; +=item * - for(my $i = 0; $i < @{$sources}; $i += 2) { - my ( $name, $options ) = @{$sources}[$i, $i + 1]; - if ( ref($options) ne 'ARRAY' ) { - $sources->[$i + 1] = $options = - [ Text::ParseWords::shellwords($options) ]; - } - for ( my $j = 0; $j < @{$options}; $j++ ) { - next unless $options->[$j] =~ /^-/; - my @chunk = ( $options->[$j] ); - push @chunk, $options->[$j] while ++$j < @{$options} && $options->[$j] !~ /^-/; - $j--; +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. - my @copy = @chunk; - local @ARGV = @chunk; - Getopt::Long::GetOptions( - 'type-add=s' => $add_type, - 'type-set=s' => $add_type, - 'type-del=s' => $del_type, - ); - Getopt::Long::GetOptions( %{$arg_spec} ); +=item * - push @new_sources, $name, \@copy; - } - } +ack now loads multiple ackrc files; see L for +details. - return \@new_sources; -} +=item * -sub compare_opts { - my ( $a, $b ) = @_; +ack's default filter definitions aren't special; you may tell ack to +completely disregard them if you don't like them. - my $first_a = $a->[0]; - my $first_b = $b->[0]; +=back - $first_a =~ s/^--?//; - $first_b =~ s/^--?//; +=head2 REMOVED OPTIONS - return $first_a cmp $first_b; -} +=over 4 -sub dump_options { - my ( $sources ) = @_; +=item * - $sources = explode_sources($sources); +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. - my %opts_by_source; - my @source_names; +=item * - for(my $i = 0; $i < @{$sources}; $i += 2) { - my ( $name, $contents ) = @{$sources}[$i, $i + 1]; - if ( not $opts_by_source{$name} ) { - $opts_by_source{$name} = []; - push @source_names, $name; - } - push @{$opts_by_source{$name}}, $contents; - } +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. - foreach my $name (@source_names) { - my $contents = $opts_by_source{$name}; +=item * - print $name, "\n"; - print '=' x length($name), "\n"; - print ' ', join(' ', @{$_}), "\n" foreach sort { compare_opts($a, $b) } @{$contents}; - } +The B<--binary> option has been removed. - return; -} +=item * -sub remove_default_options_if_needed { - my ( $sources ) = @_; +The B<--skipped> option has been removed. - my $default_index; +=item * - foreach my $index ( 0 .. $#$sources ) { - if ( $sources->[$index] eq 'Defaults' ) { - $default_index = $index; - last; - } - } +The B<--text> option has been removed. - return $sources unless defined $default_index; +=item * - my $should_remove = 0; +The B<--invert-file-match> option has been removed. Instead, you may +use B<-v> with B<-g>. - # Start with default options, minus some annoying ones. - Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); - Getopt::Long::Configure( - 'no_ignore_case', - 'no_auto_abbrev', - 'pass_through', - ); +=back + +=head2 CHANGED OPTIONS - foreach my $index ( $default_index + 2 .. $#$sources ) { - next if $index % 2 != 0; +=over 4 + +=item * + +The options that modify the regular expression's behavior (B<-i>, B<-w>, +B<-Q>, and B<-v>) may now be used with B<-g>. + +=back - my ( $name, $args ) = @{$sources}[ $index, $index + 1 ]; +=head2 ADDED OPTIONS - if (ref($args)) { - local @ARGV = @{$args}; - Getopt::Long::GetOptions( - 'ignore-ack-defaults' => \$should_remove, - ); - @{$args} = @ARGV; - } - else { - ( undef, $sources->[$index + 1] ) = Getopt::Long::GetOptionsFromString($args, - 'ignore-ack-defaults' => \$should_remove, - ); - } - } +=over 4 - Getopt::Long::Configure('default'); - Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); +=item * - return $sources unless $should_remove; +B<--files-from> was added so that a user may submit a list of filenames as +a list of files to search. - my @copy = @{$sources}; - splice @copy, $default_index, 2; - return \@copy; -} +=item * -sub check_for_mutually_exclusive_options { - my ( $arg_sources ) = @_; +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. - my %mutually_exclusive_with; - my @copy = @{$arg_sources}; +=item * - for(my $i = 0; $i < @INVALID_COMBINATIONS; $i += 2) { - my ( $lhs, $rhs ) = @INVALID_COMBINATIONS[ $i, $i + 1 ]; +B<-s> was added to tell ack to suppress error messages about non-existent or +unreadable files. - foreach my $l_opt ( @{$lhs} ) { - foreach my $r_opt ( @{$rhs} ) { - push @{ $mutually_exclusive_with{ $l_opt } }, $r_opt; - push @{ $mutually_exclusive_with{ $r_opt } }, $l_opt; - } - } - } +=item * - while( @copy ) { - my %set_opts; +B<--ignore-directory> and B<--noignore-directory> were added as aliases for +B<--ignore-dir> and B<--noignore-dir> respectively. - my ( $source_name, $args ) = splice @copy, 0, 2; - $args = ref($args) ? [ @{$args} ] : [ Text::ParseWords::shellwords($args) ]; +=item * - foreach my $opt ( @{$args} ) { - next unless $opt =~ /^[-+]/; - last if $opt eq '--'; +B<--ignore-file> was added so that users may specify patterns of files to +ignore (ex. /.*~$/). - if( $opt =~ /^(.*)=/ ) { - $opt = $1; - } - elsif ( $opt =~ /^(-[^-]).+/ ) { - $opt = $1; - } +=item * - $set_opts{ $opt } = 1; +B<--dump> was added to allow users to easily find out which options are +set where. - my $mutex_opts = $mutually_exclusive_with{ $opt }; +=item * - next unless $mutex_opts; +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. - foreach my $mutex_opt ( @{$mutex_opts} ) { - if($set_opts{ $mutex_opt }) { - die "Options '$mutex_opt' and '$opt' are mutually exclusive\n"; - } - } - } - } -} +=item * -sub process_args { - my $arg_sources = \@_; +B<--type-del> was added to selectively remove file type definitions. - my %opt = ( - pager => $ENV{ACK_PAGER_COLOR} || $ENV{ACK_PAGER}, - ); +=item * - check_for_mutually_exclusive_options($arg_sources); +B<--ignore-ack-defaults> was added so that users may ignore ack's default +options in favor of their own. - $arg_sources = remove_default_options_if_needed($arg_sources); +=item * - if ( should_dump_options($arg_sources) ) { - dump_options($arg_sources); - exit(0); - } +B<--bar> was added so ack users may consult Admiral Ackbar. - my $type_specs = process_filetypes(\%opt, $arg_sources); - process_other(\%opt, $type_specs, $arg_sources); - while ( @{$arg_sources} ) { - my ( $source_name, $args ) = splice( @{$arg_sources}, 0, 2 ); +=back - # All of our sources should be transformed into an array ref - if ( ref($args) ) { - if ( $source_name eq 'ARGV' ) { - @ARGV = @{$args}; - } - elsif (@{$args}) { - Carp::croak "source '$source_name' has extra arguments!"; - } - } - else { - Carp::croak 'The impossible has occurred!'; - } - } - my $filters = ($opt{filters} ||= []); +=head1 AUTHOR - # Throw the default filter in if no others are selected. - if ( not grep { !$_->is_inverted() } @{$filters} ) { - push @{$filters}, App::Ack::Filter::Default->new(); - } - return \%opt; -} +Andy Lester, C<< >> +=head1 BUGS -sub retrieve_arg_sources { - my @arg_sources; +Please report any bugs or feature requests to the issues list at +Github: L - my $noenv; - my $ackrc; +=head1 ENHANCEMENTS - Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); - Getopt::Long::Configure('pass_through'); - Getopt::Long::Configure('no_auto_abbrev'); +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. - Getopt::Long::GetOptions( - 'noenv' => \$noenv, - 'ackrc=s' => \$ackrc, - ); +There is a list of enhancements I want to make to F in the ack +issues list at Github: L - Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); +Patches are always welcome, but patches with tests get the most +attention. - my @files; +=head1 SUPPORT - 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 ); - } +Support for and information about F can be found at: - push @arg_sources, Defaults => [ App::Ack::ConfigDefault::options() ]; +=over 4 - foreach my $file ( @files) { - my @lines = App::Ack::ConfigFinder::read_rcfile($file); - push ( @arg_sources, $file, \@lines ) if @lines; - } +=item * The ack homepage - if ( $ENV{ACK_OPTIONS} && !$noenv ) { - push( @arg_sources, 'ACK_OPTIONS' => $ENV{ACK_OPTIONS} ); - } +L - push( @arg_sources, 'ARGV' => [ @ARGV ] ); +=item * The ack-users mailing list - return @arg_sources; -} +L -1; # End of App::Ack::ConfigLoader -package App::Ack::Filter; +=item * The ack issues list at Github -use strict; -use warnings; -use overload - '""' => 'to_string'; +L -use Carp 1.04 (); +=item * AnnoCPAN: Annotated CPAN documentation -my %filter_types; +L +=item * CPAN Ratings -sub create_filter { - my ( undef, $type, @args ) = @_; +L - if ( my $package = $filter_types{$type} ) { - return $package->new(@args); - } - Carp::croak "Unknown filter type '$type'"; -} +=item * Search CPAN +L -sub register_filter { - my ( undef, $type, $package ) = @_; +=item * Git source repository - $filter_types{$type} = $package; +L - return; -} +=back +=head1 ACKNOWLEDGEMENTS -sub invert { - my ( $self ) = @_; +How appropriate to have Inowledgements! - return App::Ack::Filter::Inverse->new( $self ); -} +Thanks to everyone who has contributed to ack in any way, including +Stephen Thirlwall, +Jonah Bishop, +Chris Rebert, +Denis Howe, +RaEl GundEn, +James McCoy, +Daniel Perrett, +Steven Lee, +Jonathan Perret, +Fraser Tweedale, +RaEl GundEn, +Steffen Jaeckel, +Stephan Hohe, +Michael Beijen, +Alexandr Ciornii, +Christian Walde, +Charles Lee, +Joe McMahon, +John Warwick, +David Steinbrunner, +Kara Martens, +Volodymyr Medvid, +Ron Savage, +Konrad Borowski, +Dale Sedivic, +Michael McClimon, +Andrew Black, +Ralph Bodenner, +Shaun Patterson, +Ryan Olson, +Shlomi Fish, +Karen Etheridge, +Olivier Mengue, +Matthew Wild, +Scott Kyle, +Nick Hooey, +Bo Borgerson, +Mark Szymanski, +Marq Schneider, +Packy Anderson, +JR Boyens, +Dan Sully, +Ryan Niebur, +Kent Fredric, +Mike Morearty, +Ingmar Vanhassel, +Eric Van Dewoestine, +Sitaram Chamarty, +Adam James, +Richard Carlsson, +Pedro Melo, +AJ Schuster, +Phil Jackson, +Michael Schwern, +Jan Dubois, +Christopher J. Madsen, +Matthew Wickline, +David Dyck, +Jason Porritt, +Jjgod Jiang, +Thomas Klausner, +Uri Guttman, +Peter Lewis, +Kevin Riggle, +Ori Avtalion, +Torsten Blix, +Nigel Metheringham, +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. +=head1 COPYRIGHT & LICENSE -sub is_inverted { - return 0; -} +Copyright 2005-2015 Andy Lester. +This program is free software; you can redistribute it and/or modify +it under the terms of the Artistic License v2.0. -sub to_string { - my ( $self ) = @_; +See http://www.perlfoundation.org/artistic_license_2_0 or the LICENSE.md +file that comes with the ack distribution. - return '(unimplemented to_string)'; -} +=cut +package App::Ack; +use warnings; +use strict; -sub inspect { - my ( $self ) = @_; - return ref($self); +our $VERSION; +our $COPYRIGHT; +BEGIN { + $VERSION = '2.15_01'; + $COPYRIGHT = 'Copyright 2005-2015 Andy Lester.'; } -1; -package App::Ack::Filter::Extension; +our $fh; -use strict; -use warnings; BEGIN { - our @ISA = 'App::Ack::Filter'; + $fh = *STDOUT; } -sub new { - my ( $class, @extensions ) = @_; - - my $exts = join('|', map { "\Q$_\E"} @extensions); - my $re = qr/[.](?:$exts)$/i; +our %types; +our %type_wanted; +our %mappings; +our %ignore_dirs; - return bless { - extensions => \@extensions, - regex => $re, - groupname => 'ExtensionGroup', - }, $class; -} +our $is_filter_mode; +our $output_to_pipe; -sub create_group { - return App::Ack::Filter::ExtensionGroup->new(); -} +our $dir_sep_chars; +our $is_cygwin; +our $is_windows; -sub filter { - my ( $self, $resource ) = @_; +use File::Spec 1.00015 (); - my $re = $self->{'regex'}; +BEGIN { + # These have to be checked before any filehandle diddling. + $output_to_pipe = not -t *STDOUT; + $is_filter_mode = -p STDIN; - return $resource->name =~ /$re/; + $is_cygwin = ($^O eq 'cygwin'); + $is_windows = ($^O eq 'MSWin32'); + $dir_sep_chars = $is_windows ? quotemeta( '\\/' ) : quotemeta( File::Spec->catfile( '', '' ) ); } -sub inspect { - my ( $self ) = @_; - - my $re = $self->{'regex'}; - return ref($self) . " - $re"; -} - -sub to_string { - my ( $self ) = @_; - my $exts = $self->{'extensions'}; +sub remove_dir_sep { + my $path = shift; + $path =~ s/[$dir_sep_chars]$//; - return join(' ', map { ".$_" } @{$exts}); + return $path; } -BEGIN { - App::Ack::Filter->register_filter(ext => __PACKAGE__); -} -1; -package App::Ack::Filter::FirstLineMatch; -use strict; -use warnings; -BEGIN { - our @ISA = 'App::Ack::Filter'; +sub warn { + return CORE::warn( _my_program(), ': ', @_, "\n" ); } -sub new { - my ( $class, $re ) = @_; - - $re =~ s{^/|/$}{}g; # XXX validate? - $re = qr{$re}i; - return bless { - regex => $re, - }, $class; +sub die { + return CORE::die( _my_program(), ': ', @_, "\n" ); } -# This test reads the first 250 characters of a file, then just uses the -# first line found in that. This prevents reading something like an entire -# .min.js file (which might be only one "line" long) into memory. - -sub filter { - my ( $self, $resource ) = @_; - - my $re = $self->{'regex'}; - - my $line = $resource->firstliney; - - return $line =~ /$re/; +sub _my_program { + require File::Basename; + return File::Basename::basename( $0 ); } -sub inspect { - my ( $self ) = @_; - my $re = $self->{'regex'}; - return ref($self) . " - $re"; +sub filetypes_supported { + return keys %mappings; } -sub to_string { - my ( $self ) = @_; - - (my $re = $self->{regex}) =~ s{\([^:]*:(.*)\)$}{$1}; - - return "first line matches /$re/"; +sub _get_thpppt { + my $y = q{_ /|,\\'!.x',=(www)=, U }; + $y =~ tr/,x!w/\nOo_/; + return $y; } -BEGIN { - App::Ack::Filter->register_filter(firstlinematch => __PACKAGE__); +sub _thpppt { + my $y = _get_thpppt(); + App::Ack::print( "$y ack $_[0]!\n" ); + exit 0; } -1; -package App::Ack::Filter::Is; +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 -use strict; -use warnings; -BEGIN { - our @ISA = 'App::Ack::Filter'; + return App::Ack::__pic($x); } -use File::Spec 3.00 (); - -sub new { - my ( $class, $filename ) = @_; - - return bless { - filename => $filename, - groupname => 'IsGroup', - }, $class; +sub _cathy { + my $x = <<'CATHY'; + 0+!--+! + 0|! "C!H!O!C!O!L!A!T!E!!! !|! + 0|! "C!H!O!C!O!L!A!T!E!!! !|! + 0|! "C!H!O!C!O!L!A!T!E!!! !|! + 0|! $A"C!K!!! $|! + 0+!--+! + 6\! 1:!,!.! ! + 7\! /.!M!~!Z!M!~! + 8\! /~!D! "M! ! + 4.! $\! /M!~!.!8! +.!M# 4 + 0,!.! (\! .~!M!N! ,+!I!.!M!.! 3 + /?!O!.!M!:! '\! .O!.! +~!Z!=!N!.! 4 + ..! !D!Z!.!Z!.! '\! 9=!M".! 6 + /.! !.!~!M".! '\! 8~! 9 + 4M!.! /.!7!N!M!.! F + 4.! &:!M! !N"M# !M"N!M! #D!M&=! = + :M!7!M#:! !~!M!7!,!$!M!:! #.! !O!N!.!M!:!M# ; + 8Z!M"~!N!$!D!.!N!?! !I!N!.! (?!M! !M!,!D!M".! 9 + (?!Z!M!N!:! )=!M!O!8!.!M!+!M! !M!,! !O!M! +,!M!.!M!~!Z!N!M!:! &:!~! 0 + &8!7!.!~!M"D!M!,! &M!?!=!8! !M!,!O! !M!+! !+!O!.!M! $M#~! !.!8!M!Z!.!M! !O!M"Z! %:!~!M!Z!M!Z!.! + + &:!M!7!,! *M!.!Z!M! !8"M!.!M!~! !.!M!.!=! #~!8!.!M! !7!M! "N!Z#I! !D!M!,!M!.! $."M!,! !M!.! * + 2$!O! "N! !.!M!I! !7" "M! "+!O! !~!M! !d!O!.!7!I!M!.! !.!O!=!M!.! !M",!M!.! %.!$!O!D! + + 1~!O! "M!+! !8!$! "M! "?!O! %Z!8!D!M!?!8!I!O!7!M! #M!.!M! "M",!M! 4 + 07!~! ".!8! !.!M! "I!+! !.!M! &Z!D!.!7!=!M! !:!.!M! #:!8"+! !.!+!8! !8! 3 + /~!M! #N! !~!M!$! !.!M! !.!M" &~!M! "~!M!O! "D! $M! !8! "M!,!M!+!D!.! 1 + #.! #?!M!N!.! #~!O! $M!.!7!$! "?" !?!~!M! '7!8!?!M!.!+!M"O! $?"$!D! !.!O! !$!7!I!.! 0 + $,!M!:!O!?! ".! !?!=! $=!:!O! !M! "M! !M! !+!$! (.! +.!M! !M!.! !8! !+"Z!~! $:!M!$! !.! ' + #.!8!.!I!$! $7!I! %M" !=!M! !~!M!D! "7!I! .I!O! %?!=!,!D! !,!M! !D!~!8!~! %D!M! ( + #.!M"?! $=!O! %=!N! "8!.! !Z!M! #M!~! (M!:! #.!M" &O! !M!.! !?!,! !8!.!N!~! $8!N!M!,!.! % + *$!O! &M!,! "O! !.!M!.! #M! (~!M( &O!.! !7! "M! !.!M!.!M!,! #.!M! !M! & + )=!8!.! $.!M!O!.! "$!.!I!N! !I!M# (7!M(I! %D"Z!M! "=!I! "M! !M!:! #~!D! ' + )D! &8!N!:! ".!O! !M!="M! "M! (7!M) %." !M!D!."M!.! !$!=! !M!,! + + (M! &+!.!M! #Z!7!O!M!.!~!8! +,!M#D!?!M#D! #.!Z!M#,!Z!?! !~!N! "N!.! !M! + + 'D!:! %$!D! !?! #M!Z! !8!.! !M"?!7!?!7! '+!I!D! !?!O!:!M!:! ":!M!:! !M!7".!M! "8!+! !:!D! !.!M! * + %.!O!:! $.!O!+! !D!.! #M! "M!.!+!N!I!Z! "7!M!N!M!N!?!I!7!Z!=!M'D"~! #M!.!8!$! !:! !.!M! "N!?! !,!O! ) + !.!?!M!:!M!I! %8!,! "M!.! #M! "N! !M!.! !M!.! !+!~! !.!M!.! ':!M! $M! $M!Z!$! !M!.! "D! "M! "?!M! ( + !7!8! !+!I! ".! "$!=! ":!$! "+! !M!.! !O! !M!I!M".! !=!~! ",!O! '=!M! $$!,! #N!:! ":!8!.! !D!~! !,!M!.! !:!M!.! & + !:!,!.! &Z" #D! !.!8!."M!.! !8!?!Z!M!.!M! #Z!~! !?!M!Z!.! %~!O!.!8!$!N!8!O!I!:!~! !+! #M!.! !.!M!.! !+!M! ".!~!M!+! $ + !.! 'D!I! #?!M!.!M!,! !.!Z! !.!8! #M&O!I!?! (~!I!M"." !M!Z!.! !M!N!.! "+!$!.! "M!.! !M!?!.! "8!M! $ + (O!8! $M! !M!.! ".!:! !+!=! #M! #.!M! !+" *$!M":!.! !M!~! "M!7! #M! #7!Z! "M"$!M!.! !.! # + '$!Z! #.!7!+!M! $.!,! !+!:! #N! #.!M!.!+!M! +D!M! #=!N! ":!O! #=!M! #Z!D! $M!I! % + $,! ".! $.!M" %$!.! !?!~! "+!7!." !.!M!,! !M! *,!N!M!.$M!?! "D!,! #M!.! #N! + + ,M!Z! &M! "I!,! "M! %I!M! !?!=!.! (Z!8!M! $:!M!.! !,!M! $D! #.!M!.! ) + +8!O! &.!8! "I!,! !~!M! &N!M! !M!D! '?!N!O!." $?!7! "?!~! #M!.! #I!D!.! ( + 3M!,! "N!.! !D" &.!+!M!.! !M":!.":!M!7!M!D! 'M!.! "M!.! "M!,! $I! ) + 3I! #M! "M!,! !:! &.!M" ".!,! !.!$!M!I! #.! !:! !.!M!?! "N!+! ".! / + 1M!,! #.!M!8!M!=!.! +~!N"O!Z"~! *+!M!.! "M! 2 + 0.!M! &M!.! 8:! %.!M!Z! "M!=! *O!,! % + 0?!$! &N! )." .,! %."M! ":!M!.! 0 + 0N!:! %?!O! #.! ..! &,! &.!D!,! "N!I! 0 +CATHY + return App::Ack::__pic($x); } -sub create_group { - return App::Ack::Filter::IsGroup->new(); +sub __pic { + my($compressed) = @_; + $compressed =~ s/(.)(.)/$1x(ord($2)-32)/eg; + App::Ack::print( $compressed ); + exit 0; } -sub filter { - my ( $self, $resource ) = @_; - my $filename = $self->{'filename'}; - my $base = (File::Spec->splitpath($resource->name))[2]; +sub show_help { + my $help_arg = shift || 0; - return $base eq $filename; -} + return show_help_types() if $help_arg =~ /^types?/; -sub inspect { - my ( $self ) = @_; + App::Ack::print( <<"END_OF_HELP" ); +Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES] - my $filename = $self->{'filename'}; +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 "-". - return ref($self) . " - $filename"; -} +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. -sub to_string { - my ( $self ) = @_; +Example: ack -i select - my $filename = $self->{'filename'}; +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 - return $filename; -} +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 -BEGIN { - App::Ack::Filter->register_filter(is => __PACKAGE__); -} + -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. -1; -package App::Ack::Filter::Match; + --print0 Print null byte as separator between filenames, + only works with -f, -g, -l, -L or -c. -use strict; -use warnings; -BEGIN { - our @ISA = 'App::Ack::Filter'; -} + -s Suppress error messages about nonexistent or + unreadable files. -use File::Spec 3.00; -sub new { - my ( $class, $re ) = @_; +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). - $re =~ s{^/|/$}{}g; # XXX validate? - $re = qr/$re/i; - return bless { - regex => $re, - }, $class; -} +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. -sub filter { - my ( $self, $resource ) = @_; +File inclusion/exclusion: + --[no]ignore-dir=name Add/remove directory from list of ignored dirs + --[no]ignore-directory=name Synonym for ignore-dir + --ignore-file=filter Add filter for ignoring files + -r, -R, --recurse Recurse into subdirectories (default: on) + -n, --no-recurse No descending into subdirectories + --[no]follow Follow symlinks. Default is off. + -k, --known-types Include only files of types that ack recognizes. - my $re = $self->{'regex'}; - my $base = (File::Spec->splitpath($resource->name))[2]; + --type=X Include only X files, where X is a recognized + filetype. + --type=noX Exclude X files. + See "ack --help-types" for supported filetypes. - return $base =~ /$re/; -} +File type specification: + --type-set TYPE:FILTER:FILTERARGS + Files with the given FILTERARGS applied to the + given FILTER are recognized as being of type + TYPE. This replaces an existing definition for + type TYPE. + --type-add TYPE:FILTER:FILTERARGS + Files with the given FILTERARGS applied to the + given FILTER are recognized as being type TYPE. + --type-del TYPE Removes all filters associated with TYPE. -sub inspect { - my ( $self ) = @_; - my $re = $self->{'regex'}; +Miscellaneous: + --[no]env Ignore environment variables and global ackrc + files. --env is legal but redundant. + --ackrc=filename Specify an ackrc file to use + --ignore-ack-defaults Ignore default definitions included with ack. + --create-ackrc Outputs a default ackrc for your customization + to standard output. + --help, -? This help + --help-types Display all known types + --dump Dump information on which options are loaded + from which RC files + --[no]filter Force ack to treat standard input as a pipe + (--filter) or tty (--nofilter) + --man Man page + --version Display version & copyright + --thpppt Bill the Cat + --bar The warning admiral + --cathy Chocolate! Chocolate! Chocolate! - print ref($self) . " - $re"; -} +Exit status is 0 if match, 1 if no match. -sub to_string { - my ( $self ) = @_; +ack's home page is at http://beyondgrep.com/ - my $re = $self->{'regex'}; +The full ack manual is available by running "ack --man". - return "filename matches $re"; -} +This is version $VERSION of ack. Run "ack --version" for full version info. +END_OF_HELP -BEGIN { - App::Ack::Filter->register_filter(match => __PACKAGE__); -} + return; + } -1; -package App::Ack::Filter::Default; -use strict; -use warnings; -BEGIN { - our @ISA = 'App::Ack::Filter'; -} -sub new { - my ( $class ) = @_; +sub show_help_types { + App::Ack::print( <<'END_OF_HELP' ); +Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES] - return bless {}, $class; -} +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. -sub filter { - my ( $self, $resource ) = @_; +Note that some extensions may appear in multiple types. For example, +.pod files are both Perl and Parrot. - return -T $resource->name; -} +END_OF_HELP -1; -package App::Ack::Filter::Inverse; + 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}; -use strict; -use warnings; -BEGIN { - our @ISA = 'App::Ack::Filter'; + 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 new { - my ( $class, $filter ) = @_; +sub show_man { + require Pod::Usage; - return bless { - filter => $filter, - }, $class; + Pod::Usage::pod2usage({ + -input => $App::Ack::orig_program_name, + -verbose => 2, + -exitval => 0, + }); + + return; } -sub filter { - my ( $self, $resource ) = @_; - my $filter = $self->{'filter'}; - return !$filter->filter( $resource ); -} +sub get_version_statement { + require Config; -sub invert { - my $self = shift; + my $copyright = get_copyright(); + my $this_perl = $Config::Config{perlpath}; + if ($^O ne 'VMS') { + my $ext = $Config::Config{_exe}; + $this_perl .= $ext unless $this_perl =~ m/$ext$/i; + } + my $ver = sprintf( '%vd', $^V ); - return $self->{'filter'}; -} + return <<"END_OF_VERSION"; +ack ${VERSION} +Running under Perl $ver at $this_perl -sub is_inverted { - return 1; +$copyright + +This program is free software. You may modify or distribute it +under the terms of the Artistic License v2.0. +END_OF_VERSION } -sub inspect { - my ( $self ) = @_; - my $filter = $self->{'filter'}; +sub print_version_statement { + App::Ack::print( get_version_statement() ); - return "!$filter"; + return; } -1; -package App::Ack::Filter::Collection; -use strict; -use warnings; -BEGIN { - our @ISA = 'App::Ack::Filter'; +sub get_copyright { + return $COPYRIGHT; } -use File::Spec 3.00 (); -sub new { - my ( $class ) = @_; +# 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; - return bless { - groups => {}, - ungrouped => [], - }, $class; + 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; } -sub filter { - my ( $self, $resource ) = @_; +sub print_count0 { + my $filename = shift; + my $ors = shift; + my $show_filename = shift; - for my $group (values %{$self->{'groups'}}) { - if ($group->filter($resource)) { - return 1; - } + if ($show_filename) { + App::Ack::print( $filename, ':0', $ors ); } - - for my $filter (@{$self->{'ungrouped'}}) { - if ($filter->filter($resource)) { - return 1; - } + else { + App::Ack::print( '0', $ors ); } - return 0; + return; } -sub add { - my ( $self, $filter ) = @_; - - if (exists $filter->{'groupname'}) { - my $groups = $self->{'groups'}; - my $group_name = $filter->{'groupname'}; +sub set_up_pager { + my $command = shift; - my $group; - if (exists $groups->{$group_name}) { - $group = $groups->{$group_name}; - } - else { - $group = $groups->{$group_name} = $filter->create_group(); - } + return if App::Ack::output_to_pipe(); - $group->add($filter); - } - else { - push @{$self->{'ungrouped'}}, $filter; + my $pager; + if ( not open( $pager, '|-', $command ) ) { + App::Ack::die( qq{Unable to pipe to pager "$command": $!} ); } + $fh = $pager; return; } -sub inspect { - my ( $self ) = @_; - return ref($self) . " - $self"; +sub output_to_pipe { + return $output_to_pipe; } -sub to_string { - my ( $self ) = @_; - my $ungrouped = $self->{'ungrouped'}; +sub exit_from_ack { + my $nmatches = shift; - return join(', ', map { "($_)" } @{$ungrouped}); + my $rc = $nmatches ? 0 : 1; + exit $rc; } -1; -package App::Ack::Filter::IsGroup; -use strict; + +1; # End of App::Ack +package App::Ack::Resource; + + use warnings; -BEGIN { - our @ISA = 'App::Ack::Filter'; +use strict; +use overload + '""' => 'name'; + +sub FAIL { + require Carp; + Carp::confess( 'Must be overloaded' ); } -use File::Spec 3.00 (); sub new { - my ( $class ) = @_; + return FAIL(); +} - return bless { - data => {}, - }, $class; + +sub name { + return FAIL(); } -sub add { - my ( $self, $filter ) = @_; - $self->{data}->{ $filter->{filename} } = 1; +sub basename { + return FAIL(); } -sub filter { - my ( $self, $resource ) = @_; - my $data = $self->{'data'}; - my $base = (File::Spec->splitpath($resource->name))[2]; +sub is_binary { + return FAIL(); +} - return exists $data->{$base}; + +sub open { + return FAIL(); } -sub inspect { - my ( $self ) = @_; - return ref($self) . " - $self"; +sub needs_line_scan { + return FAIL(); } -sub to_string { - my ( $self ) = @_; - return join(' ', keys %{$self->{data}}); +sub reset { + return FAIL(); } -1; -package App::Ack::Filter::ExtensionGroup; -use strict; -use warnings; -BEGIN { - our @ISA = 'App::Ack::Filter'; +sub close { + return FAIL(); } -use File::Spec 3.00 (); -sub new { - my ( $class ) = @_; +sub clone { + return FAIL(); +} - return bless { - data => {}, - }, $class; + +sub firstliney { + return FAIL(); } -sub add { - my ( $self, $filter ) = @_; +1; +package App::Ack::Resources; - my $data = $self->{'data'}; - my $extensions = $filter->{'extensions'}; - foreach my $ext (@{$extensions}) { - $data->{lc $ext} = 1; - } -} -sub filter { - my ( $self, $resource ) = @_; +use warnings; +use strict; - if ($resource->name =~ /[.]([^.]*)$/) { - return exists $self->{'data'}->{lc $1}; - } +sub _generate_error_handler { + my $opt = shift; - return 0; + if ( $opt->{dont_report_bad_filenames} ) { + return sub { + my $msg = shift; + # XXX restricting to specific error messages for now; I would + # prefer a different way of doing this + if ( $msg =~ /Permission denied/ ) { + return; + } + App::Ack::warn( $msg ); + }; + } + else { + return sub { + my $msg = shift; + App::Ack::warn( $msg ); + }; + } } -sub inspect { - my ( $self ) = @_; - return ref($self) . " - $self"; -} +sub from_argv { + my $class = shift; + my $opt = shift; + my $start = shift; -sub to_string { - my ( $self ) = @_; + my $self = bless {}, $class; - my $data = $self->{'data'}; + my $file_filter = undef; + my $descend_filter = $opt->{descend_filter}; + + if( $opt->{n} ) { + $descend_filter = sub { + return 0; + }; + } + + $self->{iter} = + File::Next::files( { + file_filter => $opt->{file_filter}, + descend_filter => $descend_filter, + error_handler => _generate_error_handler($opt), + warning_handler => sub {}, + sort_files => $opt->{sort_files}, + follow_symlinks => $opt->{follow}, + }, @{$start} ); - return join(' ', map { ".$_" } (keys %$data)); + return $self; } -1; -package main; -use strict; -use warnings; +sub from_file { + my $class = shift; + my $opt = shift; + my $file = shift; -use 5.008008; + my $iter = + File::Next::from_file( { + error_handler => _generate_error_handler($opt), + warning_handler => _generate_error_handler($opt), + sort_files => $opt->{sort_files}, + }, $file ) or return undef; + return bless { + iter => $iter, + }, $class; +} -# XXX Don't make this so brute force -# See also: https://github.com/petdance/ack2/issues/89 +# This is for reading input lines from STDIN, not the list of files from STDIN +sub from_stdin { + my $class = shift; + my $opt = shift; -use Getopt::Long 2.35 (); + my $self = bless {}, $class; -use Carp 1.04 (); + my $has_been_called = 0; -our $VERSION = '2.10'; -# Check http://beyondgrep.com/ for updates + $self->{iter} = sub { + if ( !$has_been_called ) { + $has_been_called = 1; + return '-'; + } + return; + }; -# These are all our globals. + return $self; +} -MAIN: { - $App::Ack::orig_program_name = $0; - $0 = join(' ', 'ack', $0); - if ( $App::Ack::VERSION ne $main::VERSION ) { - App::Ack::die( "Program/library version mismatch\n\t$0 is $main::VERSION\n\t$INC{'App/Ack.pm'} is $App::Ack::VERSION" ); - } +sub next { + my $self = shift; - # Do preliminary arg checking; - my $env_is_usable = 1; - for my $arg ( @ARGV ) { - last if ( $arg eq '--' ); + my $file = $self->{iter}->() or return; - # Get the --thpppt, --bar, --cathy checking out of the way. - $arg =~ /^--th[pt]+t+$/ and App::Ack::_thpppt($arg); - $arg eq '--bar' and App::Ack::_bar(); - $arg eq '--cathy' and App::Ack::_cathy(); + return App::Ack::Resource::Basic->new( $file ); +} - # See if we want to ignore the environment. (Don't tell Al Gore.) - $arg eq '--env' and $env_is_usable = 1; - $arg eq '--noenv' and $env_is_usable = 0; - } +1; +package App::Ack::Resource::Basic; - if ( !$env_is_usable ) { - my @keys = ( 'ACKRC', grep { /^ACK_/ } keys %ENV ); - delete @ENV{@keys}; - } - load_colors(); - Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); - Getopt::Long::Configure('pass_through', 'no_auto_abbrev'); - Getopt::Long::GetOptions( - 'help' => sub { App::Ack::show_help(); exit; }, - 'version' => sub { App::Ack::print_version_statement(); exit; }, - 'man' => sub { App::Ack::show_man(); exit; }, - ); - Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); +use warnings; +use strict; - if ( !@ARGV ) { - App::Ack::show_help(); - exit 1; - } +use Fcntl (); +use File::Spec (); - main(); +BEGIN { + our @ISA = 'App::Ack::Resource'; } -sub _compile_descend_filter { - my ( $opt ) = @_; - my $idirs = $opt->{idirs}; - my $dont_ignore_dirs = $opt->{no_ignore_dirs}; - # if we have one or more --noignore-dir directives, we can't ignore - # entire subdirectory hierarchies, so we return an "accept all" - # filter and scrutinize the files more in _compile_file_filter - return if $dont_ignore_dirs; - return unless $idirs && @{$idirs}; +sub new { + my $class = shift; + my $filename = shift; - my %ignore_dirs; + my $self = bless { + filename => $filename, + fh => undef, + opened => 0, + }, $class; - foreach my $idir (@{$idirs}) { - if ( $idir =~ /^(\w+):(.*)/ ) { - if ( $1 eq 'is') { - $ignore_dirs{$2} = 1; - } - else { - Carp::croak( 'Non-is filters are not yet supported for --ignore-dir' ); - } - } - else { - Carp::croak( qq{Invalid filter specification "$idir"} ); - } + if ( $self->{filename} eq '-' ) { + $self->{fh} = *STDIN; + $self->{opened} = 1; } - return sub { - return !exists $ignore_dirs{$_} && !exists $ignore_dirs{$File::Next::dir}; - }; + return $self; } -sub _compile_file_filter { - my ( $opt, $start ) = @_; - my $ifiles = $opt->{ifiles}; - $ifiles ||= []; - - my $ifiles_filters = App::Ack::Filter::Collection->new(); - - foreach my $filter_spec (@{$ifiles}) { - if ( $filter_spec =~ /^(\w+):(.+)/ ) { - my ($how,$what) = ($1,$2); - my $filter = App::Ack::Filter->create_filter($how, split(/,/, $what)); - $ifiles_filters->add($filter); - } - else { - Carp::croak( qq{Invalid filter specification "$filter_spec"} ); - } - } +sub name { + return $_[0]->{filename}; +} - my $filters = $opt->{'filters'} || []; - my $direct_filters = App::Ack::Filter::Collection->new(); - my $inverse_filters = App::Ack::Filter::Collection->new(); +sub basename { + my ( $self ) = @_; - foreach my $filter (@{$filters}) { - if ($filter->is_inverted()) { - # We want to check if files match the uninverted filters - $inverse_filters->add($filter->invert()); - } - else { - $direct_filters->add($filter); - } + # XXX definedness? pre-populate the slot with an undef? + unless ( exists $self->{basename} ) { + $self->{basename} = (File::Spec->splitpath($self->name))[2]; } - my %is_member_of_starting_set = map { (get_file_id($_) => 1) } @{$start}; + return $self->{basename}; +} - my $ignore_dir_list = $opt->{idirs}; - my $dont_ignore_dir_list = $opt->{no_ignore_dirs}; - my %ignore_dir_set; - my %dont_ignore_dir_set; +sub needs_line_scan { + my $self = shift; + my $opt = shift; - foreach my $filter (@{ $ignore_dir_list }) { - if ( $filter =~ /^(\w+):(.*)/ ) { - if ( $1 eq 'is' ) { - $ignore_dir_set{ $2 } = 1; - } else { - Carp::croak( 'Non-is filters are not yet supported for --ignore-dir' ); - } - } else { - Carp::croak( qq{Invalid filter specification "$filter"} ); - } + return 1 if $opt->{v}; + + my $size = -s $self->{fh}; + if ( $size == 0 ) { + return 0; } - foreach my $filter (@{ $dont_ignore_dir_list }) { - if ( $filter =~ /^(\w+):(.*)/ ) { - if ( $1 eq 'is' ) { - $dont_ignore_dir_set{ $2 } = 1; - } else { - Carp::croak( 'Non-is filters are not yet supported for --ignore-dir' ); - } - } else { - Carp::croak( qq{Invalid filter specification "$filter"} ); - } + elsif ( $size > 100_000 ) { + return 1; } - return sub { - # ack always selects files that are specified on the command - # line, regardless of filetype. If you want to ack a JPEG, - # and say "ack foo whatever.jpg" it will do it for you. - return 1 if $is_member_of_starting_set{ get_file_id($File::Next::name) }; - - if ( $dont_ignore_dir_list ) { - my ( undef, $dirname ) = File::Spec->splitpath($File::Next::name); - my @dirs = File::Spec->splitdir($dirname); - - my $is_ignoring = 0; + my $buffer; + my $rc = sysread( $self->{fh}, $buffer, $size ); + if ( !defined($rc) && $App::Ack::report_bad_filenames ) { + App::Ack::warn( "$self->{filename}: $!" ); + return 1; + } + return 0 unless $rc && ( $rc == $size ); - foreach my $dir ( @dirs ) { - if ( $ignore_dir_set{ $dir } ) { - $is_ignoring = 1; - } - elsif ( $dont_ignore_dir_set{ $dir } ) { - $is_ignoring = 0; - } - } - if ( $is_ignoring ) { - return 0; - } - } + my $regex = $opt->{regex}; + return $buffer =~ /$regex/m; +} - # Ignore named pipes found in directory searching. Named - # pipes created by subprocesses get specified on the command - # line, so the rule of "always select whatever is on the - # command line" wins. - return 0 if -p $File::Next::name; - # we can't handle unreadable filenames; report them - unless ( -r _ ) { - if ( $App::Ack::report_bad_filenames ) { - App::Ack::warn( "${File::Next::name}: cannot open file for reading" ); - } - return 0; - } +sub reset { + my $self = shift; - my $resource = App::Ack::Resource::Basic->new($File::Next::name); - return 0 if ! $resource; - if ( $ifiles_filters->filter($resource) ) { - return 0; - } + # return if we haven't opened the file yet + if ( !defined($self->{fh}) ) { + return; + } - my $match_found = $direct_filters->filter($resource); + if( !seek( $self->{fh}, 0, 0 ) && $App::Ack::report_bad_filenames ) { + App::Ack::warn( "$self->{filename}: $!" ); + } - # Don't bother invoking inverse filters unless we consider the current resource a match - if ( $match_found && $inverse_filters->filter( $resource ) ) { - $match_found = 0; - } - return $match_found; - }; + return; } -sub show_types { - my $resource = shift; - my $ors = shift; - my @types = filetypes( $resource ); - my $types = join( ',', @types ); - my $arrow = @types ? ' => ' : ' =>'; - App::Ack::print( $resource->name, $arrow, join( ',', @types ), $ors ); +sub close { + my $self = shift; - return; -} + # return if we haven't opened the file yet + if ( !defined($self->{fh}) ) { + return; + } -# Set default colors, load Term::ANSIColor -sub load_colors { - eval 'use Term::ANSIColor 1.10 ()'; - eval 'use Win32::Console::ANSI' if $App::Ack::is_windows; + if ( !close($self->{fh}) && $App::Ack::report_bad_filenames ) { + App::Ack::warn( $self->name() . ": $!" ); + } - $ENV{ACK_COLOR_MATCH} ||= 'black on_yellow'; - $ENV{ACK_COLOR_FILENAME} ||= 'bold green'; - $ENV{ACK_COLOR_LINENO} ||= 'bold yellow'; + $self->{opened} = 0; return; } -sub filetypes { - my ( $resource ) = @_; - - my @matches; - - foreach my $k (keys %App::Ack::mappings) { - my $filters = $App::Ack::mappings{$k}; - foreach my $filter (@{$filters}) { - # clone the resource - my $clone = $resource->clone; - if ( $filter->filter($clone) ) { - push @matches, $k; - last; - } - } - } +sub clone { + my ( $self ) = @_; - # http://search.cpan.org/dist/Perl-Critic/lib/Perl/Critic/Policy/Subroutines/ProhibitReturnSort.pm - @matches = sort @matches; - return @matches; + return __PACKAGE__->new($self->name); } -# 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 firstliney { + my ( $self ) = @_; - if ( $App::Ack::is_windows ) { - return File::Next::reslash( $filename ); - } - else { - # XXX is this the best method? it always hits the FS - if( my ( $dev, $inode ) = (stat($filename))[0, 1] ) { - return join(':', $dev, $inode); - } - else { - # XXX this could be better - return $filename; + my $fh = $self->open(); + + if ( !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; } -} - -# Returns a regex object based on a string and command-line options. -# Dies when the regex $str is undefined (i.e. not given on command line). -sub build_regex { - my $str = shift; - my $opt = shift; + $self->close; - defined $str or App::Ack::die( 'No regular expression found.' ); + return $self->{firstliney}; +} - $str = quotemeta( $str ) if $opt->{Q}; - if ( $opt->{w} ) { - $str = "\\b$str" if $str =~ /^\w/; - $str = "$str\\b" if $str =~ /\w$/; - } +sub open { + my ( $self ) = @_; - my $regex_is_lc = $str eq lc $str; - if ( $opt->{i} || ($opt->{smart_case} && $regex_is_lc) ) { - $str = "(?i)$str"; - } + return $self->{fh} if $self->{opened}; - my $re = eval { qr/$str/ }; - if ( !$re ) { - die "Invalid regex '$str':\n $@"; + if ( ! open $self->{fh}, '<', $self->{filename} ) { + return; } - return $re; + $self->{opened} = 1; + return $self->{fh}; } -{ +1; +package App::Ack::ConfigDefault; -my @before_ctx_lines; -my @after_ctx_lines; -my $is_iterating; +use warnings; +use strict; -my $has_printed_something; -BEGIN { - $has_printed_something = 0; + +sub options { + return split( /\n/, _options_block() ); } -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}; +sub options_clean { + return grep { /./ && !/^#/ } options(); +} - my $has_printed_for_this_resource = 0; - $is_iterating = 1; +sub _options_block { + my $lines = <<'HERE'; +# This is the default ackrc for ack version ==VERSION==. - local $opt->{before_context} = $opt->{output} ? 0 : $opt->{before_context}; - local $opt->{after_context} = $opt->{output} ? 0 : $opt->{after_context}; +# There are four different ways to match +# +# is: Match the filename exactly +# +# ext: Match the extension of the filename exactly +# +# match: Match the filename against a Perl regular expression +# +# firstlinematch: Match the first 250 characters of the first line +# of text against a Perl regular expression. This is only for +# the --type-add option. - my $n_before_ctx_lines = $opt->{before_context} || 0; - my $n_after_ctx_lines = $opt->{after_context} || 0; - @after_ctx_lines = @before_ctx_lines = (); +### Directories to ignore - my $fh = $resource->open(); - if ( !$fh ) { - if ( $App::Ack::report_bad_filenames ) { - App::Ack::warn( "$filename: $!" ); - } - return 0; - } +# Bazaar +# http://bazaar.canonical.com/ +--ignore-directory=is:.bzr - my $display_filename = $filename; - if ( $print_filename && $heading && $color ) { - $display_filename = Term::ANSIColor::colored($display_filename, $ENV{ACK_COLOR_FILENAME}); - } +# Codeville +# http://freecode.com/projects/codeville +--ignore-directory=is:.cdv - # 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 +# Interface Builder (Xcode) +# http://en.wikipedia.org/wiki/Interface_Builder +--ignore-directory=is:~.dep +--ignore-directory=is:~.dot +--ignore-directory=is:~.nib +--ignore-directory=is:~.plst - while ( defined $current_line ) { - while ( (@after_ctx_lines < $n_after_ctx_lines) && defined($_ = <$fh>) ) { - push @after_ctx_lines, $_; - } +# Git +# http://git-scm.com/ +--ignore-directory=is:.git - local $_ = $current_line; - my $former_dot_period = $.; - $. -= @after_ctx_lines; +# Mercurial +# http://mercurial.selenic.com/ +--ignore-directory=is:.hg - 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--; - } - 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>; - } - } - } - else { - local $_; +# quilt +# http://directory.fsf.org/wiki/Quilt +--ignore-directory=is:.pc - 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--; - } - 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; - } - } +# Subversion +# http://subversion.tigris.org/ +--ignore-directory=is:.svn - $is_iterating = 0; # XXX this won't happen on an exception - # then again, do we care? ack doesn't really - # handle exceptions anyway. +# Monotone +# http://www.monotone.ca/ +--ignore-directory=is:_MTN - return $nmatches; -} +# CVS +# http://savannah.nongnu.org/projects/cvs +--ignore-directory=is:CVS -sub print_line_with_options { - my ( $opt, $filename, $line, $line_no, $separator ) = @_; +# RCS +# http://www.gnu.org/software/rcs/ +--ignore-directory=is:RCS - $has_printed_something = 1; +# SCCS +# http://en.wikipedia.org/wiki/Source_Code_Control_System +--ignore-directory=is:SCCS - 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}; +# darcs +# http://darcs.net/ +--ignore-directory=is:_darcs - my @line_parts; +# Vault/Fortress +--ignore-directory=is:_sgbak - if( $color ) { - $filename = Term::ANSIColor::colored($filename, - $ENV{ACK_COLOR_FILENAME}); - $line_no = Term::ANSIColor::colored($line_no, - $ENV{ACK_COLOR_LINENO}); - } +# autoconf +# http://www.gnu.org/software/autoconf/ +--ignore-directory=is:autom4te.cache - if($print_filename) { - if( $heading ) { - push @line_parts, $line_no; - } - else { - push @line_parts, $filename, $line_no; - } +# Perl module building +--ignore-directory=is:blib +--ignore-directory=is:_build - 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 ) { - $line =~ /$opt->{regex}/o; # this match is redundant, but we need - # to perfom it in order to get if - # capture groups are set +# Perl Devel::Cover module's output directory +# https://metacpan.org/release/Devel-Cover +--ignore-directory=is:cover_db - if ( @+ > 1 ) { # if we have captures - while ( $line =~ /$opt->{regex}/og ) { - my $offset = 0; # additional offset for when we add stuff - my $previous_match_end = 0; +# Node modules created by npm +--ignore-directory=is:node_modules - for ( my $i = 1; $i < @+; $i++ ) { - my ( $match_start, $match_end ) = ( $-[$i], $+[$i] ); +# CMake cache +# http://www.cmake.org/ +--ignore-directory=is:CMakeFiles - next unless defined($match_start); - next if $match_start < $previous_match_end; +# Eclipse workspace folder +# http://eclipse.org/ +--ignore-directory=is:.metadata - my $substring = substr( $line, - $offset + $match_start, $match_end - $match_start ); - my $substitution = Term::ANSIColor::colored( $substring, - $ENV{ACK_COLOR_MATCH} ); +# Cabal (Haskell) sandboxes +# http://www.haskell.org/cabal/users-guide/installing-packages.html +--ignore-directory=is:.cabal-sandbox - substr( $line, $offset + $match_start, - $match_end - $match_start, $substitution ); +### Files to ignore - $previous_match_end = $match_end; # offsets do not need to be applied - $offset += length( $substitution ) - length( $substring ); - } +# Backup files +--ignore-file=ext:bak +--ignore-file=match:/~$/ - pos($line) = $+[0] + $offset; - } - } - else { - my $matched = 0; # flag; if matched, need to escape afterwards +# Emacs swap files +--ignore-file=match:/^#.+#$/ + +# vi/vim swap files http://vim.org/ +--ignore-file=match:/[._].*\.swp$/ - while ( $line =~ /$opt->{regex}/og ) { +# core dumps +--ignore-file=match:/core\.\d+$/ - $matched = 1; - my ( $match_start, $match_end ) = ($-[0], $+[0]); - next unless defined($match_start); +# minified Javascript +--ignore-file=match:/[.-]min[.]js$/ +--ignore-file=match:/[.]js[.]min$/ + +# minified CSS +--ignore-file=match:/[.]min[.]css$/ +--ignore-file=match:/[.]css[.]min$/ + +# JS and CSS source maps +--ignore-file=match:/[.]js[.]map$/ +--ignore-file=match:/[.]css[.]map$/ + +# PDFs, because they pass Perl's -T detection +--ignore-file=ext:pdf - my $substring = substr( $line, $match_start, - $match_end - $match_start ); - my $substitution = Term::ANSIColor::colored( $substring, - $ENV{ACK_COLOR_MATCH} ); +# Common graphics, just as an optimization +--ignore-file=ext:gif,jpg,jpeg,png - substr( $line, $match_start, $match_end - $match_start, - $substitution ); - pos($line) = $match_end + - (length( $substitution ) - length( $substring )); - } - # XXX why do we do this? - $line .= "\033[0m\033[K" if $matched; - } - } +### Filetypes defined - push @line_parts, $line; - App::Ack::print( join( $separator, @line_parts ), $ors ); - } +# Perl +# http://perl.org/ +--type-add=perl:ext:pl,pm,pod,t,psgi +--type-add=perl:firstlinematch:/^#!.*\bperl/ - return; -} +# Perl tests +--type-add=perltest:ext:t -sub iterate { - my ( $resource, $opt, $cb ) = @_; +# Makefiles +# http://www.gnu.org/s/make/ +--type-add=make:ext:mk +--type-add=make:ext:mak +--type-add=make:is:makefile +--type-add=make:is:Makefile +--type-add=make:is:Makefile.Debug +--type-add=make:is:Makefile.Release - $is_iterating = 1; +# Rakefiles +# http://rake.rubyforge.org/ +--type-add=rake:is:Rakefile - local $opt->{before_context} = $opt->{output} ? 0 : $opt->{before_context}; - local $opt->{after_context} = $opt->{output} ? 0 : $opt->{after_context}; +# CMake +# http://www.cmake.org/ +--type-add=cmake:is:CMakeLists.txt +--type-add=cmake:ext:cmake - my $n_before_ctx_lines = $opt->{before_context} || 0; - my $n_after_ctx_lines = $opt->{after_context} || 0; +# Actionscript +--type-add=actionscript:ext:as,mxml - @after_ctx_lines = @before_ctx_lines = (); +# Ada +# http://www.adaic.org/ +--type-add=ada:ext:ada,adb,ads - my $fh = $resource->open(); - if ( !$fh ) { - if ( $App::Ack::report_bad_filenames ) { - # XXX direct access to filename - App::Ack::warn( "$resource->{filename}: $!" ); - } - return; - } +# ASP +# http://msdn.microsoft.com/en-us/library/aa286483.aspx +--type-add=asp:ext:asp - # 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 +# ASP.Net +# http://www.asp.net/ +--type-add=aspx:ext:master,ascx,asmx,aspx,svc - while ( defined $current_line ) { - while ( (@after_ctx_lines < $n_after_ctx_lines) && defined($_ = <$fh>) ) { - push @after_ctx_lines, $_; - } +# Assembly +--type-add=asm:ext:asm,s - local $_ = $current_line; - my $former_dot_period = $.; - $. -= @after_ctx_lines; +# Batch +--type-add=batch:ext:bat,cmd - last unless $cb->(); +# ColdFusion +# http://en.wikipedia.org/wiki/ColdFusion +--type-add=cfmx:ext:cfc,cfm,cfml - # 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 +# Clojure +# http://clojure.org/ +--type-add=clojure:ext:clj - 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 $_; +# C +# .xs are Perl C files +--type-add=cc:ext:c,h,xs - while ( <$fh> ) { - last unless $cb->(); - } - } +# C header files +--type-add=hh:ext:h - $is_iterating = 0; # XXX this won't happen on an exception - # then again, do we care? ack doesn't really - # handle exceptions anyway. +# CoffeeScript +# http://coffeescript.org/ +--type-add=coffeescript:ext:coffee - return; -} +# C++ +--type-add=cpp:ext:cpp,cc,cxx,m,hpp,hh,h,hxx -sub get_context { - if ( not $is_iterating ) { - Carp::croak( 'get_context() called outside of iterate()' ); - } +# C++ header files +--type-add=hpp:ext:hpp,hh,h,hxx - return ( - scalar(@before_ctx_lines) ? \@before_ctx_lines : undef, - scalar(@after_ctx_lines) ? \@after_ctx_lines : undef, - ); -} +# C# +--type-add=csharp:ext:cs -} +# CSS +# http://www.w3.org/Style/CSS/ +--type-add=css:ext:css -{ +# Dart +# http://www.dartlang.org/ +--type-add=dart:ext:dart -my $is_first_match; -my $previous_file_processed; -my $previous_line_printed; +# Delphi +# http://en.wikipedia.org/wiki/Embarcadero_Delphi +--type-add=delphi:ext:pas,int,dfm,nfm,dof,dpk,dproj,groupproj,bdsgroup,bdsproj -BEGIN { - $is_first_match = 1; - $previous_line_printed = -1; -} +# Elixir +# http://elixir-lang.org/ +--type-add=elixir:ext:ex,exs -sub print_line_with_context { - my ( $opt, $filename, $matching_line, $line_no ) = @_; +# Emacs Lisp +# http://www.gnu.org/software/emacs +--type-add=elisp:ext:el - my $heading = $opt->{heading}; +# Erlang +# http://www.erlang.org/ +--type-add=erlang:ext:erl,hrl - if( !defined($previous_file_processed) || - $previous_file_processed ne $filename ) { - $previous_file_processed = $filename; - $previous_line_printed = -1; +# Fortran +# http://en.wikipedia.org/wiki/Fortran +--type-add=fortran:ext:f,f77,f90,f95,f03,for,ftn,fpp - if( $heading ) { - $is_first_match = 1; - } - } +# Go +# http://golang.org/ +--type-add=go:ext:go - 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}; +# Groovy +# http://groovy.codehaus.org/ +--type-add=groovy:ext:groovy,gtmpl,gpp,grunit,gradle - $matching_line =~ s/[\r\n]+$//g; +# Haskell +# http://www.haskell.org/ +--type-add=haskell:ext:hs,lhs - my ( $before_context, $after_context ) = get_context(); +# HTML +--type-add=html:ext:htm,html - if ( $before_context ) { - my $first_line = $. - @{$before_context}; +# Jade +# http://jade-lang.com/ +--type-add=jade:ext:jade - 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}; +# Java +# http://www.oracle.com/technetwork/java/index.html +--type-add=java:ext:java,properties - 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; - } +# JavaScript +--type-add=js:ext:js - chomp $line; - print_line_with_options($opt, $filename, $line, $context_line_no, '-'); - $previous_line_printed = $context_line_no; - $offset--; - } - } - } +# JSP +# http://www.oracle.com/technetwork/java/javaee/jsp/index.html +--type-add=jsp:ext:jsp,jspx,jhtm,jhtml - if ( $. > $previous_line_printed ) { - if( $is_tracking_context && !$is_first_match && $previous_line_printed != $. - 1 ) { - App::Ack::print('--', $ors); - } +# JSON +# http://www.json.org/ +--type-add=json:ext:json - print_line_with_options($opt, $filename, $matching_line, $line_no, ':'); - $previous_line_printed = $.; - } +# Less +# http://www.lesscss.org/ +--type-add=less:ext:less - 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++; - } - } +# Common Lisp +# http://common-lisp.net/ +--type-add=lisp:ext:lisp,lsp - $is_first_match = 0; +# Lua +# http://www.lua.org/ +--type-add=lua:ext:lua +--type-add=lua:firstlinematch:/^#!.*\blua(jit)?/ - return; -} +# Objective-C +--type-add=objc:ext:m,h -} +# Objective-C++ +--type-add=objcpp:ext:mm,h -{ +# OCaml +# http://caml.inria.fr/ +--type-add=ocaml:ext:ml,mli -my $match_column_number; +# Matlab +# http://en.wikipedia.org/wiki/MATLAB +--type-add=matlab:ext:m -# does_match() MUST have an $opt->{regex} set. +# Parrot +# http://www.parrot.org/ +--type-add=parrot:ext:pir,pasm,pmc,ops,pod,pg,tg -sub does_match { - my ( $opt, $line ) = @_; +# PHP +# http://www.php.net/ +--type-add=php:ext:php,phpt,php3,php4,php5,phtml +--type-add=php:firstlinematch:/^#!.*\bphp/ - $match_column_number = undef; +# Plone +# http://plone.org/ +--type-add=plone:ext:pt,cpt,metadata,cpy,py - if ( $opt->{v} ) { - return ( $line !~ /$opt->{regex}/o ); - } - else { - if ( $line =~ /$opt->{regex}/o ) { - # @- = @LAST_MATCH_START - # @+ = @LAST_MATCH_END - $match_column_number = $-[0] + 1; - return 1; - } - else { - return; - } - } -} +# Python +# http://www.python.org/ +--type-add=python:ext:py +--type-add=python:firstlinematch:/^#!.*\bpython/ -sub get_match_column { - return $match_column_number; -} +# R +# http://www.r-project.org/ +--type-add=rr:ext:R -} +# reStructured Text +# http://docutils.sourceforge.net/rst.html +--type-add=rst:ext:rst -sub resource_has_match { - my ( $resource, $opt ) = @_; +# Ruby +# http://www.ruby-lang.org/ +--type-add=ruby:ext:rb,rhtml,rjs,rxml,erb,rake,spec +--type-add=ruby:is:Rakefile +--type-add=ruby:firstlinematch:/^#!.*\bruby/ - 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; - } +# Rust +# http://www.rust-lang.org/ +--type-add=rust:ext:rs - return $has_match; -} +# Sass +# http://sass-lang.com +--type-add=sass:ext:sass,scss -sub count_matches_in_resource { - my ( $resource, $opt ) = @_; +# Scala +# http://www.scala-lang.org/ +--type-add=scala:ext:scala - 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; - } +# Scheme +# http://groups.csail.mit.edu/mac/projects/scheme/ +--type-add=scheme:ext:scm,ss - return $nmatches; -} +# Shell +--type-add=shell:ext:sh,bash,csh,tcsh,ksh,zsh,fish +--type-add=shell:firstlinematch:/^#!.*\b(?:ba|t?c|k|z|fi)?sh\b/ -sub main { - my @arg_sources = App::Ack::ConfigLoader::retrieve_arg_sources(); +# Smalltalk +# http://www.smalltalk.org/ +--type-add=smalltalk:ext:st - my $opt = App::Ack::ConfigLoader::process_args( @arg_sources ); +# Smarty +# http://www.smarty.net/ +--type-add=smarty:ext:tpl - $App::Ack::report_bad_filenames = !$opt->{dont_report_bad_filenames}; +# SQL +# http://www.iso.org/iso/catalogue_detail.htm?csnumber=45498 +--type-add=sql:ext:sql,ctl - if ( $opt->{flush} ) { - $| = 1; - } +# Stylus +# http://learnboost.github.io/stylus/ +--type-add=stylus:ext:styl - if ( not defined $opt->{color} ) { - my $windows_color = 1; - if ( $App::Ack::is_windows ) { - $windows_color = eval { require Win32::Console::ANSI; } - } - $opt->{color} = !App::Ack::output_to_pipe() && $windows_color; - } - if ( not defined $opt->{heading} and not defined $opt->{break} ) { - $opt->{heading} = $opt->{break} = !App::Ack::output_to_pipe(); - } +# Tcl +# http://www.tcl.tk/ +--type-add=tcl:ext:tcl,itcl,itk - if ( defined($opt->{H}) || defined($opt->{h}) ) { - $opt->{show_filename}= $opt->{H} && !$opt->{h}; - } +# LaTeX +# http://www.latex-project.org/ +--type-add=tex:ext:tex,cls,sty - if ( my $output = $opt->{output} ) { - $output =~ s{\\}{\\\\}g; - $output =~ s{"}{\\"}g; - $opt->{output} = qq{"$output"}; - } +# Template Toolkit (Perl) +# http://template-toolkit.org/ +--type-add=tt:ext:tt,tt2,ttml - 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; - } - } +# Visual Basic +--type-add=vb:ext:bas,cls,frm,ctl,vb,resx + +# Verilog +--type-add=verilog:ext:v,vh,sv - 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" ); - } - } +# VHDL +# http://www.eda.org/twiki/bin/view.cgi/P1076/WebHome +--type-add=vhdl:ext:vhd,vhdl - $opt->{file_filter} = _compile_file_filter($opt, \@start); - $opt->{descend_filter} = _compile_descend_filter($opt); +# Vim +# http://www.vim.org/ +--type-add=vim:ext:vim - $resources = App::Ack::Resources->from_argv( $opt, \@start ); - } - } - App::Ack::set_up_pager( $opt->{pager} ) if defined $opt->{pager}; +# XML +# http://www.w3.org/TR/REC-xml/ +--type-add=xml:ext:xml,dtd,xsl,xslt,ent +--type-add=xml:firstlinematch:/<[?]xml/ - my $print_filenames = $opt->{show_filename}; - my $max_count = $opt->{m}; - my $ors = $opt->{print0} ? "\0" : "\n"; - my $only_first = $opt->{1}; +# YAML +# http://yaml.org/ +--type-add=yaml:ext:yaml,yml +HERE + $lines =~ s/==VERSION==/$App::Ack::VERSION/sm; - 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. + return $lines; +} - # 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}; +1; +package App::Ack::ConfigFinder; - 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; +use strict; +use warnings; - local $opt->{color} = 0; +use Cwd 3.00 (); +use File::Spec 3.00; - iterate($resource, $opt, sub { - chomp; +use if ($^O eq 'MSWin32'), 'Win32'; - 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 ); - unless ( $opt->{show_filename} ) { - $total_count += $matches_for_this_file; - next RESOURCES; - } +sub new { + my ( $class ) = @_; - 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 ); + return bless {}, $class; +} - if ( $opt->{L} ? !$is_match : $is_match ) { - App::Ack::print( $resource->name, $ors ); - ++$nmatches; - 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; - } +sub _remove_redundancies { + my @configs = @_; + + my %seen; + foreach my $config (@configs) { + my $key = $config->{path}; + if ( not $App::Ack::is_windows ) { + # On Unix, uniquify on inode. + my ($dev, $inode) = (stat $key)[0, 1]; + $key = "$dev:$inode" if defined $dev; } + undef $config if $seen{$key}++; } - - if ( $opt->{count} && !$opt->{show_filename} ) { - App::Ack::print( $total_count, "\n" ); - } - - close $App::Ack::fh; - App::Ack::exit_from_ack( $nmatches ); + return grep { defined } @configs; } +sub _check_for_ackrc { + return unless defined $_[0]; -=head1 NAME + my @files = grep { -f } + map { File::Spec->catfile(@_, $_) } + qw(.ackrc _ackrc); -ack - grep-like text finder + die File::Spec->catdir(@_) . " contains both .ackrc and _ackrc.\n" . + "Please remove one of those files.\n" + if @files > 1; -=head1 SYNOPSIS + return wantarray ? @files : $files[0]; +} # end _check_for_ackrc - ack [options] PATTERN [FILE...] - ack -f [options] [DIRECTORY...] -=head1 DESCRIPTION -Ack is designed as a replacement for 99% of the uses of F. +sub find_config_files { + my @config_files; -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. + if ( $App::Ack::is_windows ) { + push @config_files, map { +{ path => File::Spec->catfile($_, 'ackrc') } } ( + Win32::GetFolderPath(Win32::CSIDL_COMMON_APPDATA()), + Win32::GetFolderPath(Win32::CSIDL_APPDATA()), + ); + } + else { + push @config_files, { path => '/etc/ackrc' }; + } -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. -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. + if ( $ENV{'ACKRC'} && -f $ENV{'ACKRC'} ) { + push @config_files, { path => $ENV{'ACKRC'} }; + } + else { + push @config_files, map { +{ path => $_ } } _check_for_ackrc($ENV{'HOME'}); + } -=head1 FILE SELECTION + # XXX This should go through some untainted cwd-fetching function, and not get untainted inline like this. + my $cwd = Cwd::getcwd(); + $cwd =~ /(.+)/; + $cwd = $1; + my @dirs = File::Spec->splitdir( $cwd ); + while(@dirs) { + my $ackrc = _check_for_ackrc(@dirs); + if(defined $ackrc) { + push @config_files, { project => 1, path => $ackrc }; + last; + } + pop @dirs; + } -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. + # We only test for existence here, so if the file is deleted out from under us, this will fail later. + return _remove_redundancies( @config_files ); +} -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. -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. -The default options for I ignore certain files and directories. These -include: +sub read_rcfile { + my $file = shift; -=over 4 + return unless defined $file && -e $file; -=item * Backup files: Files matching F<#*#> or ending with F<~>. + my @lines; -=item * Coredumps: Files matching F + open( my $fh, '<', $file ) or App::Ack::die( "Unable to read $file: $!" ); + while ( my $line = <$fh> ) { + chomp $line; + $line =~ s/^\s+//; + $line =~ s/\s+$//; -=item * Version control directories like F<.svn> and F<.git>. + next if $line eq ''; + next if $line =~ /^\s*#/; -=back + push( @lines, $line ); + } + close $fh or App::Ack::die( "Unable to close $file: $!" ); -Run I with the C<--dump> option to see what settings are set. + return @lines; +} -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. +1; +package App::Ack::ConfigLoader; -=head1 DIRECTORY SELECTION +use strict; +use warnings; -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. +use Carp 1.04 (); +use Getopt::Long 2.35 (); +use Text::ParseWords 3.1 (); -For a complete list of directories that do not get searched, run -C. -=head1 WHEN TO USE GREP +my @INVALID_COMBINATIONS; -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. +BEGIN { + my @context = qw( -A -B -C --after-context --before-context --context ); + my @pretty = qw( --heading --group --break ); + my @filename = qw( -h -H --with-filename --no-filename ); -E.g., searching through huge files looking for regexes that can be -expressed with I syntax should be quicker with I. + @INVALID_COMBINATIONS = ( + # XXX normalize + [qw(-l)] => [@context, @pretty, @filename, qw(-L -o --passthru --output --max-count --column -f -g --show-types)], + [qw(-L)] => [@context, @pretty, @filename, qw(-l -o --passthru --output --max-count --column -f -g --show-types -c --count)], + [qw(--line)] => [@context, @pretty, @filename, qw(-l --files-with-matches --files-without-matches -L -o --passthru --match -m --max-count -1 -c --count --column --print0 -f -g --show-types)], + [qw(-o)] => [@context, qw(--output -c --count --column --column -f --show-types)], + [qw(--passthru)] => [@context, qw(--output --column -m --max-count -1 -c --count -f -g)], + [qw(--output)] => [@context, qw(-c --count -f -g)], + [qw(--match)] => [qw(-f -g)], + [qw(-m --max-count)] => [qw(-1 -f -g -c --count)], + [qw(-h --no-filename)] => [qw(-H --with-filename -f -g --group --heading)], + [qw(-H --with-filename)] => [qw(-h --no-filename -f -g)], + [qw(-c --count)] => [@context, @pretty, qw(--column -f -g)], + [qw(--column)] => [qw(-f -g)], + [@context] => [qw(-f -g)], + [qw(-f)] => [qw(-g), @pretty], + [qw(-g)] => [qw(-f), @pretty], + ); +} -If your script or parent program uses I C<--quiet> or C<--silent> -or needs exit 2 on IO error, use I. +sub _generate_ignore_dir { + my ( $option_name, $opt ) = @_; -=head1 OPTIONS + my $is_inverted = $option_name =~ /^--no/; -=over 4 + return sub { + my ( undef, $dir ) = @_; -=item B<--ackrc> + $dir = App::Ack::remove_dir_sep( $dir ); + if ( $dir !~ /:/ ) { + $dir = 'is:' . $dir; + } -Specifies an ackrc file to load after all others; see L. + my ( $filter_type, $args ) = split /:/, $dir, 2; -=item B<-A I>, B<--after-context=I> + if ( $filter_type eq 'firstlinematch' ) { + Carp::croak( qq{Invalid filter specification "$filter_type" for option '$option_name'} ); + } -Print I lines of trailing context after matching lines. + my $filter = App::Ack::Filter->create_filter($filter_type, split(/,/, $args)); + my $collection; -=item B<-B I>, B<--before-context=I> + my $previous_inversion_matches = $opt->{idirs} && !($is_inverted xor $opt->{idirs}[-1]->is_inverted()); -Print I lines of leading context before matching lines. + if ( $previous_inversion_matches ) { + $collection = $opt->{idirs}[-1]; + + if ( $is_inverted ) { + # XXX this relies on invert of an inverted filter + # to return the original + $collection = $collection->invert() + } + } + else { + $collection = App::Ack::Filter::Collection->new(); -=item B<--[no]break> + if ( $is_inverted ) { + push @{ $opt->{idirs} }, $collection->invert(); + } + else { + push @{ $opt->{idirs} }, $collection; + } + } -Print a break between results from different files. On by default -when used interactively. + $collection->add($filter); -=item B<-C [I]>, B<--context[=I]> + if ( $filter_type eq 'is' ) { + $collection->add(App::Ack::Filter::IsPath->new($args)); + } + }; +} -Print I lines (default 2) of context around matching lines. +sub process_filter_spec { + my ( $spec ) = @_; -=item B<-c>, B<--count> + if ( $spec =~ /^(\w+):(\w+):(.*)/ ) { + my ( $type_name, $ext_type, $arguments ) = ( $1, $2, $3 ); -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. + return ( $type_name, + App::Ack::Filter->create_filter($ext_type, split(/,/, $arguments)) ); + } + elsif ( $spec =~ /^(\w+)=(.*)/ ) { # Check to see if we have ack1-style argument specification. + my ( $type_name, $extensions ) = ( $1, $2 ); -If combined with B<-h> (B<--no-filename>) ack outputs only one total -count. + my @extensions = split(/,/, $extensions); + foreach my $extension ( @extensions ) { + $extension =~ s/^[.]//; + } -=item B<--[no]color>, B<--[no]colour> + return ( $type_name, App::Ack::Filter->create_filter('ext', @extensions) ); + } + else { + Carp::croak "invalid filter specification '$spec'"; + } +} -B<--color> highlights the matching text. B<--nocolor> suppresses -the color. This is on by default unless the output is redirected. -On Windows, this option is off by default unless the -L module is installed or the C -environment variable is used. +sub uninvert_filter { + my ( $opt, @filters ) = @_; -=item B<--color-filename=I> + return unless defined $opt->{filters} && @filters; -Sets the color to be used for filenames. + # Loop through all the registered filters. If we hit one that + # matches this extension and it's inverted, we need to delete it from + # the options. + for ( my $i = 0; $i < @{ $opt->{filters} }; $i++ ) { + my $opt_filter = @{ $opt->{filters} }[$i]; -=item B<--color-match=I> + # XXX Do a real list comparison? This just checks string equivalence. + if ( $opt_filter->is_inverted() && "$opt_filter->{filter}" eq "@filters" ) { + splice @{ $opt->{filters} }, $i, 1; + $i--; + } + } +} -Sets the color to be used for matches. -=item B<--color-lineno=I> +sub process_filetypes { + my ( $opt, $arg_sources ) = @_; -Sets the color to be used for line numbers. + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); # start with default options, minus some annoying ones + Getopt::Long::Configure( + 'no_ignore_case', + 'no_auto_abbrev', + 'pass_through', + ); + my %additional_specs; -=item B<--[no]column> + my $add_spec = sub { + my ( undef, $spec ) = @_; -Show the column number of the first match. This is helpful for -editors that can place your cursor at a given position. + my ( $name, $filter ) = process_filter_spec($spec); -=item B<--create-ackrc> + push @{ $App::Ack::mappings{$name} }, $filter; -Dumps the default ack options to standard output. This is useful for -when you want to customize the defaults. + $additional_specs{$name . '!'} = sub { + my ( undef, $value ) = @_; -=item B<--dump> + my @filters = @{ $App::Ack::mappings{$name} }; + if ( not $value ) { + @filters = map { $_->invert() } @filters; + } + else { + uninvert_filter( $opt, @filters ); + } -Writes the list of options loaded and where they came from to standard -output. Handy for debugging. + push @{ $opt->{'filters'} }, @filters; + }; + }; -=item B<--[no]env> + my $set_spec = sub { + my ( undef, $spec ) = @_; -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. + my ( $name, $filter ) = process_filter_spec($spec); -=item B<--flush> + $App::Ack::mappings{$name} = [ $filter ]; -B<--flush> flushes output immediately. This is off by default -unless ack is running interactively (when output goes to a pipe or -file). + $additional_specs{$name . '!'} = sub { + my ( undef, $value ) = @_; -=item B<-f> + my @filters = @{ $App::Ack::mappings{$name} }; + if ( not $value ) { + @filters = map { $_->invert() } @filters; + } -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. + push @{ $opt->{'filters'} }, @filters; + }; + }; -=item B<--files-from=I> + my $delete_spec = sub { + my ( undef, $name ) = @_; -The list of files to be searched is specified in I. The list of -files are separated by newlines. If I is C<->, the list is loaded -from standard input. + delete $App::Ack::mappings{$name}; + delete $additional_specs{$name . '!'}; + }; -=item B<--[no]filter> + my %type_arg_specs = ( + 'type-add=s' => $add_spec, + 'type-set=s' => $set_spec, + 'type-del=s' => $delete_spec, + ); -Forces ack to act as if it were receiving input via a pipe. + foreach my $source (@{$arg_sources}) { + my ( $source_name, $args ) = @{$source}{qw/name contents/}; -=item B<--[no]follow> + if ( ref($args) ) { + # $args are modified in place, so no need to munge $arg_sources + local @ARGV = @{$args}; + Getopt::Long::GetOptions(%type_arg_specs); + @{$args} = @ARGV; + } + else { + ( undef, $source->{contents} ) = + Getopt::Long::GetOptionsFromString($args, %type_arg_specs); + } + } -Follow or don't follow symlinks, other than whatever starting files -or directories were specified on the command line. + $additional_specs{'k|known-types'} = sub { + my ( undef, $value ) = @_; -This is off by default. + my @filters = map { @{$_} } values(%App::Ack::mappings); -=item B<-g I> + push @{ $opt->{'filters'} }, @filters; + }; -Print files where the relative path + filename matches I. + return \%additional_specs; +} -=item B<--[no]group> -B<--group> groups matches by file name. This is the default -when used interactively. +sub removed_option { + my ( $option, $explanation ) = @_; -B<--nogroup> prints one result per line, like grep. This is the -default when output is redirected. + $explanation ||= ''; + return sub { + warn "Option '$option' is not valid in ack 2\n$explanation"; + exit 1; + }; +} -=item B<-H>, B<--with-filename> -Print the filename for each match. This is the default unless searching -a single explicitly specified file. +sub get_arg_spec { + my ( $opt, $extra_specs ) = @_; -=item B<-h>, B<--no-filename> + my $dash_a_explanation = <<'EOT'; +This is because we now have -k/--known-types which makes it only select files +of known types, rather than any text file (which is the behavior of ack 1.x). +You may have options in a .ackrc, or in the ACKRC_OPTIONS environment variable. +Try using the --dump flag. +EOT -Suppress the prefixing of filenames on output when multiple files are -searched. -=item B<--[no]heading> + return { + 1 => sub { $opt->{1} = $opt->{m} = 1 }, + 'A|after-context=i' => \$opt->{after_context}, + 'B|before-context=i' + => \$opt->{before_context}, + 'C|context:i' => sub { shift; my $val = shift; $opt->{before_context} = $opt->{after_context} = ($val || 2) }, + 'a' => removed_option('-a', $dash_a_explanation), + 'all' => removed_option('--all', $dash_a_explanation), + 'break!' => \$opt->{break}, + c => \$opt->{count}, + 'color|colour!' => \$opt->{color}, + 'color-match=s' => \$ENV{ACK_COLOR_MATCH}, + 'color-filename=s' => \$ENV{ACK_COLOR_FILENAME}, + 'color-lineno=s' => \$ENV{ACK_COLOR_LINENO}, + 'column!' => \$opt->{column}, + count => \$opt->{count}, + 'create-ackrc' => sub { print "$_\n" for ( '--ignore-ack-defaults', App::Ack::ConfigDefault::options() ); exit; }, + 'env!' => sub { + my ( undef, $value ) = @_; -Print a filename heading above each file's results. This is the default -when used interactively. + if ( !$value ) { + $opt->{noenv_seen} = 1; + } + }, + f => \$opt->{f}, + 'files-from=s' => \$opt->{files_from}, + 'filter!' => \$App::Ack::is_filter_mode, + flush => \$opt->{flush}, + 'follow!' => \$opt->{follow}, + g => \$opt->{g}, + G => removed_option('-G'), + 'group!' => sub { shift; $opt->{heading} = $opt->{break} = shift }, + 'heading!' => \$opt->{heading}, + 'h|no-filename' => \$opt->{h}, + 'H|with-filename' => \$opt->{H}, + 'i|ignore-case' => \$opt->{i}, + 'ignore-directory|ignore-dir=s' => _generate_ignore_dir('--ignore-dir', $opt), + 'ignore-file=s' => sub { + my ( undef, $file ) = @_; -=item B<--help>, B<-?> + my ( $filter_type, $args ) = split /:/, $file, 2; -Print a short help statement. + my $filter = App::Ack::Filter->create_filter($filter_type, split(/,/, $args)); -=item B<--help-types>, B<--help=types> + if ( !$opt->{ifiles} ) { + $opt->{ifiles} = App::Ack::Filter::Collection->new(); + } + $opt->{ifiles}->add($filter); + }, + 'lines=s' => sub { shift; my $val = shift; push @{$opt->{lines}}, $val }, + 'l|files-with-matches' + => \$opt->{l}, + 'L|files-without-matches' + => \$opt->{L}, + 'm|max-count=i' => \$opt->{m}, + 'match=s' => \$opt->{regex}, + 'n|no-recurse' => \$opt->{n}, + o => sub { $opt->{output} = '$&' }, + 'output=s' => \$opt->{output}, + 'pager:s' => sub { + my ( undef, $value ) = @_; -Print all known types. + $opt->{pager} = $value || $ENV{PAGER}; + }, + 'noignore-directory|noignore-dir=s' => _generate_ignore_dir('--noignore-dir', $opt), + 'nopager' => sub { $opt->{pager} = undef }, + 'passthru' => \$opt->{passthru}, + 'print0' => \$opt->{print0}, + 'Q|literal' => \$opt->{Q}, + 'r|R|recurse' => sub { $opt->{n} = 0 }, + 's' => \$opt->{dont_report_bad_filenames}, + 'show-types' => \$opt->{show_types}, + 'smart-case!' => \$opt->{smart_case}, + 'sort-files' => \$opt->{sort_files}, + 'type=s' => sub { + my ( $getopt, $value ) = @_; -=item B<-i>, B<--ignore-case> + my $cb_value = 1; + if ( $value =~ s/^no// ) { + $cb_value = 0; + } -Ignore case distinctions in PATTERN + my $callback = $extra_specs->{ $value . '!' }; -=item B<--ignore-ack-defaults> + if ( $callback ) { + $callback->( $getopt, $cb_value ); + } + else { + Carp::croak( "Unknown type '$value'" ); + } + }, + 'u' => removed_option('-u'), + 'unrestricted' => removed_option('--unrestricted'), + 'v|invert-match' => \$opt->{v}, + 'w|word-regexp' => \$opt->{w}, + 'x' => sub { $opt->{files_from} = '-' }, -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. + 'version' => sub { App::Ack::print_version_statement(); exit; }, + 'help|?:s' => sub { shift; App::Ack::show_help(@_); exit; }, + 'help-types' => sub { App::Ack::show_help_types(); exit; }, + 'man' => sub { App::Ack::show_man(); exit; }, + $extra_specs ? %{$extra_specs} : (), + }; # arg_specs +} -=item B<--[no]ignore-dir=I>, B<--[no]ignore-directory=I> -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). +sub process_other { + my ( $opt, $extra_specs, $arg_sources ) = @_; -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. + # Start with default options, minus some annoying ones. + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); + Getopt::Long::Configure( + 'bundling', + 'no_ignore_case', + ); -=item B<--ignore-file=I> + my $argv_source; + my $is_help_types_active; -Ignore files matching I. The filters are specified -identically to file type filters as seen in L. + foreach my $source (@{$arg_sources}) { + my ( $source_name, $args ) = @{$source}{qw/name contents/}; -=item B<-k>, B<--known-types> + if ( $source_name eq 'ARGV' ) { + $argv_source = $args; + last; + } + } -Limit selected files to those with types that ack knows about. This is -equivalent to the default behavior found in ack 1. + if ( $argv_source ) { # This *should* always be true, but you never know... + my @copy = @{$argv_source}; + local @ARGV = @copy; -=item B<--lines=I> + Getopt::Long::Configure('pass_through'); -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. + Getopt::Long::GetOptions( + 'help-types' => \$is_help_types_active, + ); -=item B<-l>, B<--files-with-matches> + Getopt::Long::Configure('no_pass_through'); + } -Only print the filenames of matching files, instead of the matching text. + my $arg_specs = get_arg_spec($opt, $extra_specs); -=item B<-L>, B<--files-without-matches> + foreach my $source (@{$arg_sources}) { + my ( $source_name, $args ) = @{$source}{qw/name contents/}; -Only print the filenames of files that do I match. + my $args_for_source = $arg_specs; -=item B<--match I> + if ( $source->{project} ) { + my $illegal = sub { + die "Options --output, --pager and --match are forbidden in project .ackrc files.\n"; + }; -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. + $args_for_source = { %$args_for_source, + 'output=s' => $illegal, + 'pager:s' => $illegal, + 'match=s' => $illegal, + }; + } - # search for foo and bar in given files - ack file1 t/file* --match foo - ack file1 t/file* --match bar + my $ret; + if ( ref($args) ) { + local @ARGV = @{$args}; + $ret = Getopt::Long::GetOptions( %{$args_for_source} ); + @{$args} = @ARGV; + } + else { + ( $ret, $source->{contents} ) = + Getopt::Long::GetOptionsFromString( $args, %{$args_for_source} ); + } + if ( !$ret ) { + if ( !$is_help_types_active ) { + my $where = $source_name eq 'ARGV' ? 'on command line' : "in $source_name"; + App::Ack::die( "Invalid option $where" ); + } + } + if ( $opt->{noenv_seen} ) { + App::Ack::die( "--noenv found in $source_name" ); + } + } -=item B<-m=I>, B<--max-count=I> + # XXX We need to check on a -- in the middle of a non-ARGV source -Stop reading a file after I matches. + return; +} -=item B<--man> -Print this manual page. +sub should_dump_options { + my ( $sources ) = @_; -=item B<-n>, B<--no-recurse> + foreach my $source (@{$sources}) { + my ( $name, $options ) = @{$source}{qw/name contents/}; -No descending into subdirectories. + if($name eq 'ARGV') { + my $dump; + local @ARGV = @{$options}; + Getopt::Long::Configure('default', 'pass_through', 'no_auto_help', 'no_auto_version'); + Getopt::Long::GetOptions( + 'dump' => \$dump, + ); + @{$options} = @ARGV; + return $dump; + } + } + return; +} -=item B<-o> -Show only the part of each line matching PATTERN (turns off text -highlighting) +sub explode_sources { + my ( $sources ) = @_; -=item B<--output=I> + my @new_sources; -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">. + Getopt::Long::Configure('default', 'pass_through', 'no_auto_help', 'no_auto_version'); -=item B<--pager=I>, B<--nopager> + my %opt; + my $arg_spec = get_arg_spec(\%opt); -B<--pager> directs ack's output through I. This can also be specified -via the C and C environment variables. + my $add_type = sub { + my ( undef, $arg ) = @_; -Using --pager does not suppress grouping and coloring like piping -output on the command-line does. + # XXX refactor? + if ( $arg =~ /(\w+)=/) { + $arg_spec->{$1} = sub {}; + } + else { + ( $arg ) = split /:/, $arg; + $arg_spec->{$arg} = sub {}; + } + }; -B<--nopager> cancels any setting in ~/.ackrc, C or C. -No output will be sent through a pager. + my $del_type = sub { + my ( undef, $arg ) = @_; -=item B<--passthru> + delete $arg_spec->{$arg}; + }; -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: + foreach my $source (@{$sources}) { + my ( $name, $options ) = @{$source}{qw/name contents/}; + if ( ref($options) ne 'ARRAY' ) { + $source->{contents} = $options = + [ Text::ParseWords::shellwords($options) ]; + } - # Watch a log file, and highlight a certain IP address - $ tail -f ~/access.log | ack --passthru 123.45.67.89 + for my $j ( 0 .. @{$options}-1 ) { + next unless $options->[$j] =~ /^-/; + my @chunk = ( $options->[$j] ); + push @chunk, $options->[$j] while ++$j < @{$options} && $options->[$j] !~ /^-/; + $j--; -=item B<--print0> + my @copy = @chunk; + local @ARGV = @chunk; + Getopt::Long::GetOptions( + 'type-add=s' => $add_type, + 'type-set=s' => $add_type, + 'type-del=s' => $del_type, + ); + Getopt::Long::GetOptions( %{$arg_spec} ); -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. + push @new_sources, { + name => $name, + contents => \@copy, + }; + } + } - # remove all files of type html - ack -f --html --print0 | xargs -0 rm -f + return \@new_sources; +} -=item B<-Q>, B<--literal> -Quote all metacharacters in PATTERN, it is treated as a literal. +sub compare_opts { + my ( $a, $b ) = @_; -=item B<-r>, B<-R>, B<--recurse> + my $first_a = $a->[0]; + my $first_b = $b->[0]; -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. + $first_a =~ s/^--?//; + $first_b =~ s/^--?//; -=item B<-s> + return $first_a cmp $first_b; +} -Suppress error messages about nonexistent or unreadable files. This is taken -from fgrep. -=item B<--[no]smart-case>, B<--no-smart-case> +sub dump_options { + my ( $sources ) = @_; -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. + $sources = explode_sources($sources); -B<-i> always overrides this option. + my %opts_by_source; + my @source_names; -=item B<--sort-files> + foreach my $source (@{$sources}) { + my ( $name, $contents ) = @{$source}{qw/name contents/}; + if ( not $opts_by_source{$name} ) { + $opts_by_source{$name} = []; + push @source_names, $name; + } + push @{$opts_by_source{$name}}, $contents; + } -Sorts the found files lexicographically. Use this if you want your file -listings to be deterministic between runs of I. + foreach my $name (@source_names) { + my $contents = $opts_by_source{$name}; -=item B<--show-types> + print $name, "\n"; + print '=' x length($name), "\n"; + print ' ', join(' ', @{$_}), "\n" foreach sort { compare_opts($a, $b) } @{$contents}; + } -Outputs the filetypes that ack associates with each file. + return; +} -Works with B<-f> and B<-g> options. -=item B<--type=[no]TYPE> +sub remove_default_options_if_needed { + my ( $sources ) = @_; -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>. + my $default_index; -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. + foreach my $index ( 0 .. $#{$sources} ) { + if ( $sources->[$index]{'name'} eq 'Defaults' ) { + $default_index = $index; + last; + } + } -Type specifications can be repeated and are ORed together. + return $sources unless defined $default_index; -See I for a list of valid types. + my $should_remove = 0; -=item B<--type-add I:I:I> + # Start with default options, minus some annoying ones. + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); + Getopt::Long::Configure( + 'no_ignore_case', + 'no_auto_abbrev', + 'pass_through', + ); -Files with the given FILTERARGS applied to the given FILTER -are recognized as being of (the existing) type TYPE. -See also L. + foreach my $index ( $default_index + 1 .. $#{$sources} ) { + my ( $name, $args ) = @{$sources->[$index]}{qw/name contents/}; + if (ref($args)) { + local @ARGV = @{$args}; + Getopt::Long::GetOptions( + 'ignore-ack-defaults' => \$should_remove, + ); + @{$args} = @ARGV; + } + else { + ( undef, $sources->[$index]{contents} ) = Getopt::Long::GetOptionsFromString($args, + 'ignore-ack-defaults' => \$should_remove, + ); + } + } -=item B<--type-set I:I:I> + Getopt::Long::Configure('default'); + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); -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. + return $sources unless $should_remove; -=item B<--type-del I> + my @copy = @{$sources}; + splice @copy, $default_index, 1; + return \@copy; +} -The filters associated with TYPE are removed from Ack, and are no longer considered -for searches. -=item B<-v>, B<--invert-match> +sub check_for_mutually_exclusive_options { + my ( $arg_sources ) = @_; -Invert match: select non-matching lines + my %mutually_exclusive_with; + my @copy = @{$arg_sources}; -=item B<--version> + for(my $i = 0; $i < @INVALID_COMBINATIONS; $i += 2) { + my ( $lhs, $rhs ) = @INVALID_COMBINATIONS[ $i, $i + 1 ]; -Display version and copyright information. + foreach my $l_opt ( @{$lhs} ) { + foreach my $r_opt ( @{$rhs} ) { + push @{ $mutually_exclusive_with{ $l_opt } }, $r_opt; + push @{ $mutually_exclusive_with{ $r_opt } }, $l_opt; + } + } + } -=item B<-w>, B<--word-regexp> + while( @copy ) { + my %set_opts; -Force PATTERN to match only whole words. The PATTERN is wrapped with -C<\b> metacharacters. + my $source = shift @copy; + my ( $source_name, $args ) = @{$source}{qw/name contents/}; + $args = ref($args) ? [ @{$args} ] : [ Text::ParseWords::shellwords($args) ]; -=item B<-x> + foreach my $opt ( @{$args} ) { + next unless $opt =~ /^[-+]/; + last if $opt eq '--'; -An abbreviation for B<--files-from=->; the list of files to search are read -from standard input, with one line per file. + if( $opt =~ /^(.*)=/ ) { + $opt = $1; + } + elsif ( $opt =~ /^(-[^-]).+/ ) { + $opt = $1; + } -=item B<-1> + $set_opts{ $opt } = 1; -Stops after reporting first match of any kind. This is different -from B<--max-count=1> or B<-m1>, where only one match per file is -shown. Also, B<-1> works with B<-f> and B<-g>, where B<-m> does -not. + my $mutex_opts = $mutually_exclusive_with{ $opt }; -=item B<--thpppt> + next unless $mutex_opts; -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. + foreach my $mutex_opt ( @{$mutex_opts} ) { + if($set_opts{ $mutex_opt }) { + die "Options '$mutex_opt' and '$opt' are mutually exclusive\n"; + } + } + } + } +} -=item B<--bar> -Check with the admiral for traps. +sub process_args { + my $arg_sources = \@_; -=item B<--cathy> + my %opt = ( + pager => $ENV{ACK_PAGER_COLOR} || $ENV{ACK_PAGER}, + ); -Chocolate, Chocolate, Chocolate! + check_for_mutually_exclusive_options($arg_sources); -=back + $arg_sources = remove_default_options_if_needed($arg_sources); -=head1 THE .ackrc FILE + if ( should_dump_options($arg_sources) ) { + dump_options($arg_sources); + exit(0); + } -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: + my $type_specs = process_filetypes(\%opt, $arg_sources); + process_other(\%opt, $type_specs, $arg_sources); + while ( @{$arg_sources} ) { + my $source = shift @{$arg_sources}; + my ( $source_name, $args ) = @{$source}{qw/name contents/}; - # Always sort the files - --sort-files + # All of our sources should be transformed into an array ref + if ( ref($args) ) { + if ( $source_name eq 'ARGV' ) { + @ARGV = @{$args}; + } + elsif (@{$args}) { + Carp::croak "source '$source_name' has extra arguments!"; + } + } + else { + Carp::croak 'The impossible has occurred!'; + } + } + my $filters = ($opt{filters} ||= []); - # Always color, even if piping to a another program - --color + # Throw the default filter in if no others are selected. + if ( not grep { !$_->is_inverted() } @{$filters} ) { + push @{$filters}, App::Ack::Filter::Default->new(); + } + return \%opt; +} - # Use "less -r" as my pager - --pager=less -r -Note that arguments with spaces in them do not need to be quoted, -as they are not interpreted by the shell. Basically, each I -in the F<.ackrc> file is interpreted as one element of C<@ARGV>. +sub retrieve_arg_sources { + my @arg_sources; -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. + my $noenv; + my $ackrc; -=head1 Defining your own types + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); + Getopt::Long::Configure('pass_through'); + Getopt::Long::Configure('no_auto_abbrev'); -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. + Getopt::Long::GetOptions( + 'noenv' => \$noenv, + 'ackrc=s' => \$ackrc, + ); -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. + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); -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. + my @files; -When defining your own types in the F<.ackrc> file you have to use -the following: + if ( !$noenv ) { + my $finder = App::Ack::ConfigFinder->new; + @files = $finder->find_config_files; + } + if ( $ackrc ) { + # We explicitly use open so we get a nice error message. + # XXX This is a potential race condition!. + if(open my $fh, '<', $ackrc) { + close $fh; + } + else { + die "Unable to load ackrc '$ackrc': $!" + } + push( @files, { path => $ackrc } ); + } - --type-set=eiffel:ext:e,eiffel + push @arg_sources, { + name => 'Defaults', + contents => [ App::Ack::ConfigDefault::options_clean() ], + }; -or writing on separate lines + foreach my $file ( @files) { + my @lines = App::Ack::ConfigFinder::read_rcfile($file->{path}); + + if(@lines) { + push @arg_sources, { + name => $file->{path}, + contents => \@lines, + project => $file->{project}, + }; + } + } - --type-set - eiffel:ext:e,eiffel + if ( $ENV{ACK_OPTIONS} && !$noenv ) { + push @arg_sources, { + name => 'ACK_OPTIONS', + contents => $ENV{ACK_OPTIONS}, + }; + } -The following does B work in the F<.ackrc> file: + push @arg_sources, { + name => 'ARGV', + contents => [ @ARGV ], + }; - --type-set eiffel:ext:e,eiffel + return @arg_sources; +} +1; # End of App::Ack::ConfigLoader +package App::Ack::Filter; -In order to see all currently defined types, use I<--help-types>, e.g. -I +use strict; +use warnings; -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. +use Carp 1.04 (); -=over 4 +my %filter_types; -=item is:I -I filters match the target filename exactly. It takes exactly one -argument, which is the name of the file to match. +sub create_filter { + my ( undef, $type, @args ) = @_; -Example: + if ( my $package = $filter_types{$type} ) { + return $package->new(@args); + } + Carp::croak "Unknown filter type '$type'"; +} - --type-set make:is:Makefile -=item ext:I[,I[,...]] +sub register_filter { + my ( undef, $type, $package ) = @_; -I filters match the extension of the target file against a list -of extensions. No leading dot is needed for the extensions. + $filter_types{$type} = $package; -Example: + return; +} - --type-set perl:ext:pl,pm,t -=item match:I +sub invert { + my ( $self ) = @_; -I filters match the target filename against a regular expression. -The regular expression is made case insensitive for the search. + return App::Ack::Filter::Inverse->new( $self ); +} -Example: - --type-set make:match:/(gnu)?makefile/ +sub is_inverted { + return 0; +} -=item firstlinematch:I -I matches the first line of the target file against a -regular expression. Like I, the regular expression is made -case insensitive. +sub to_string { + my ( $self ) = @_; -Example: + return '(unimplemented to_string)'; +} - --type-add perl:firstlinematch:/perl/ -=back +sub inspect { + my ( $self ) = @_; -More filter types may be made available in the future. + return ref($self); +} -=head1 ENVIRONMENT VARIABLES +1; +package App::Ack::Filter::Extension; -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. +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} -=over 4 -=item ACKRC +sub new { + my ( $class, @extensions ) = @_; -Specifies the location of the user's F<.ackrc> file. If this file doesn't -exist, F looks in the default location. + my $exts = join('|', map { "\Q$_\E"} @extensions); + my $re = qr/[.](?:$exts)$/i; -=item ACK_OPTIONS + return bless { + extensions => \@extensions, + regex => $re, + groupname => 'ExtensionGroup', + }, $class; +} -This variable specifies default options to be placed in front of -any explicit options on the command line. +sub create_group { + return App::Ack::Filter::ExtensionGroup->new(); +} -=item ACK_COLOR_FILENAME +sub filter { + my ( $self, $resource ) = @_; -Specifies the color of the filename when it's printed in B<--group> -mode. By default, it's "bold green". + my $re = $self->{'regex'}; -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. + return $resource->name =~ /$re/; +} -This option can also be set with B<--color-filename>. +sub inspect { + my ( $self ) = @_; -=item ACK_COLOR_MATCH + my $re = $self->{'regex'}; -Specifies the color of the matching text when printed in B<--color> -mode. By default, it's "black on_yellow". + return ref($self) . " - $re"; +} -This option can also be set with B<--color-match>. +sub to_string { + my ( $self ) = @_; -See B for the color specifications. + my $exts = $self->{'extensions'}; -=item ACK_COLOR_LINENO + return join(' ', map { ".$_" } @{$exts}); +} -Specifies the color of the line number when printed in B<--color> -mode. By default, it's "bold yellow". +BEGIN { + App::Ack::Filter->register_filter(ext => __PACKAGE__); +} -This option can also be set with B<--color-lineno>. +1; +package App::Ack::Filter::FirstLineMatch; -See B for the color specifications. +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} -=item ACK_PAGER +sub new { + my ( $class, $re ) = @_; -Specifies a pager program, such as C, C or C, to which -ack will send its output. + $re =~ s{^/|/$}{}g; # XXX validate? + $re = qr{$re}i; -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. + return bless { + regex => $re, + }, $class; +} -C overrides C if both are specified. +# This test reads the first 250 characters of a file, then just uses the +# first line found in that. This prevents reading something like an entire +# .min.js file (which might be only one "line" long) into memory. -=item ACK_PAGER_COLOR +sub filter { + my ( $self, $resource ) = @_; -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. + my $re = $self->{'regex'}; -If you are not on Windows, you never need to use C. + my $line = $resource->firstliney; -=back + return $line =~ /$re/; +} -=head1 ACK & OTHER TOOLS +sub inspect { + my ( $self ) = @_; -=head2 Vim integration + my $re = $self->{'regex'}; -F integrates easily with the Vim text editor. Set this in your -F<.vimrc> to use F instead of F: + return ref($self) . " - $re"; +} - set grepprg=ack\ -k +sub to_string { + my ( $self ) = @_; -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: + (my $re = $self->{regex}) =~ s{\([^:]*:(.*)\)$}{$1}; - :grep Dumper perllib + return "first line matches /$re/"; +} -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. +BEGIN { + App::Ack::Filter->register_filter(firstlinematch => __PACKAGE__); +} -L +1; +package App::Ack::Filter::Is; -=head2 Emacs integration +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} -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." +use File::Spec 3.00 (); -L +sub new { + my ( $class, $filename ) = @_; -=head2 TextMate integration + return bless { + filename => $filename, + groupname => 'IsGroup', + }, $class; +} -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" +sub create_group { + return App::Ack::Filter::IsGroup->new(); +} -=head2 Shell and Return Code +sub filter { + my ( $self, $resource ) = @_; -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. + my $filename = $self->{'filename'}; + my $base = (File::Spec->splitpath($resource->name))[2]; -(Shell exit code 1 is C<$?=256> in perl with C or backticks.) + return $base eq $filename; +} -The I code 2 for errors is not used. +sub inspect { + my ( $self ) = @_; -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. + my $filename = $self->{'filename'}; -=cut + return ref($self) . " - $filename"; +} -=head1 DEBUGGING ACK PROBLEMS +sub to_string { + my ( $self ) = @_; -If ack gives you output you're not expecting, start with a few simple steps. + my $filename = $self->{'filename'}; -=head2 Use B<--noenv> + return $filename; +} -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>. +BEGIN { + App::Ack::Filter->register_filter(is => __PACKAGE__); +} -=head2 Use B<-f> to see what files have been selected +1; +package App::Ack::Filter::Match; -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. +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} -=head2 Use B<--dump> +use File::Spec 3.00; -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. +sub new { + my ( $class, $re ) = @_; -=head1 TIPS + $re =~ s{^/|/$}{}g; # XXX validate? + $re = qr/$re/i; -=head2 Use the F<.ackrc> file. + return bless { + regex => $re, + groupname => 'MatchGroup', + }, $class; +} -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. +sub create_group { + return App::Ack::Filter::MatchGroup->new; +} -=head2 Use F<-f> for working with big codesets +sub filter { + my ( $self, $resource ) = @_; -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: + my $re = $self->{'regex'}; - # Change all "this" to "that" in all Perl files in a tree. - ack -f --perl | xargs perl -p -i -e's/this/that/g' + return $resource->basename =~ /$re/; +} -or if you prefer: +sub inspect { + my ( $self ) = @_; - perl -p -i -e's/this/that/g' $(ack -f --perl) + my $re = $self->{'regex'}; -=head2 Use F<-Q> when in doubt about metacharacters + print ref($self) . " - $re"; -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... + return; +} -=head2 Use ack to watch log files +sub to_string { + my ( $self ) = @_; -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. + my $re = $self->{'regex'}; - ack -Q aa.bb.cc.dd /path/to/access.log | ack -Q -B5 troublesome.gif + return "filename matches $re"; +} -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. +BEGIN { + App::Ack::Filter->register_filter(match => __PACKAGE__); +} -=head2 Examples of F<--output> +1; +package App::Ack::Filter::Default; -Following variables are useful in the expansion string: +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} -=over 4 +sub new { + my ( $class ) = @_; -=item C<$&> + return bless {}, $class; +} -The whole string matched by PATTERN. +sub filter { + my ( $self, $resource ) = @_; -=item C<$1>, C<$2>, ... + return -T $resource->name; +} -The contents of the 1st, 2nd ... bracketed group in PATTERN. +1; +package App::Ack::Filter::Inverse; -=item C<$`> +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} -The string before the match. +sub new { + my ( $class, $filter ) = @_; -=item C<$'> + return bless { + filter => $filter, + }, $class; +} -The string after the match. +sub filter { + my ( $self, $resource ) = @_; -=back + my $filter = $self->{'filter'}; + return !$filter->filter( $resource ); +} -For more details and other variables see -L. +sub invert { + my $self = shift; -This example shows how to add text around a particular pattern -(in this case adding _ around word with "e") + return $self->{'filter'}; +} - 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 +sub is_inverted { + return 1; +} -This shows how to pick out particular parts of a match using ( ) within regular expression. +sub inspect { + my ( $self ) = @_; - ack '=head(\d+)\s+(.*)' --output=' $1 : $2' - input file contains "=head1 NAME" - output "1 : NAME" + my $filter = $self->{'filter'}; -=head2 Share your knowledge + return "!$filter"; +} -Join the ack-users mailing list. Send me your tips and I may add -them here. +1; +package App::Ack::Filter::Collection; -=head1 FAQ +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} -=head2 Why isn't ack finding a match in (some file)? +sub new { + my ( $class ) = @_; -Probably because it's of a type that ack doesn't recognize. ack's -searching behavior is driven by filetype. B + return bless { + groups => {}, + ungrouped => [], + }, $class; +} -Use the C<-f> switch to see a list of files that ack will search -for you. You can use the C<--show-types> switch to show which type -ack thinks each file is. +sub filter { + my ( $self, $resource ) = @_; -=head2 Wouldn't it be great if F did search & replace? + for my $group (values %{$self->{'groups'}}) { + if ($group->filter($resource)) { + return 1; + } + } -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. + for my $filter (@{$self->{'ungrouped'}}) { + if ($filter->filter($resource)) { + return 1; + } + } -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: + return 0; +} - $ perl -i -p -e's/foo/bar/g' $(ack -f --php) +sub add { + my ( $self, $filter ) = @_; -=head2 Can I make ack recognize F<.xyz> files? + if (exists $filter->{'groupname'}) { + my $group = ($self->{groups}->{$filter->{groupname}} ||= $filter->create_group()); + $group->add($filter); + } + else { + push @{$self->{'ungrouped'}}, $filter; + } -Yes! Please see L. If you think -that F should recognize a type by default, please see -L. + return; +} -=head2 There's already a program/package called ack. +sub inspect { + my ( $self ) = @_; -Yes, I know. + return ref($self) . " - $self"; +} -=head2 Why is it called ack if it's called ack-grep? +sub to_string { + my ( $self ) = @_; -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. + my $ungrouped = $self->{'ungrouped'}; -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. + return join(', ', map { "($_)" } @{$ungrouped}); +} -To do that, run this with F or as root: +1; +package App::Ack::Filter::IsGroup; - ln -s /usr/bin/ack-grep /usr/bin/ack +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} -Alternatively, you could use a shell alias: +use File::Spec 3.00 (); - # bash/zsh - alias ack=ack-grep +sub new { + my ( $class ) = @_; - # csh - alias ack ack-grep + return bless { + data => {}, + }, $class; +} -=head2 What does F mean? +sub add { + my ( $self, $filter ) = @_; -Nothing. I wanted a name that was easy to type and that you could -pronounce as a single syllable. + $self->{data}->{ $filter->{filename} } = 1; -=head2 Can I do multi-line regexes? + return; +} -No, ack does not support regexes that match multiple lines. Doing -so would require reading in the entire file at a time. +sub filter { + my ( $self, $resource ) = @_; -If you want to see lines near your match, use the C<--A>, C<--B> -and C<--C> switches for displaying context. + my $data = $self->{'data'}; + my $base = $resource->basename; -=head2 Why is ack telling me I have an invalid option when searching for C<+foo>? + return exists $data->{$base}; +} -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!) +sub inspect { + my ( $self ) = @_; -=head2 Why does C<"ack '.{40000,}'"> fail? Isn't that a valid regex? + return ref($self) . " - $self"; +} -The Perl language limits the repetition quanitifier to 32K. You -can search for C<.{32767}> but not C<.{32768}>. +sub to_string { + my ( $self ) = @_; -=head1 ACKRC LOCATION SEMANTICS + return join(' ', keys %{$self->{data}}); +} -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) +1; +package App::Ack::Filter::ExtensionGroup; + +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} -=over 4 +sub new { + my ( $class ) = @_; -=item * + return bless { + data => {}, + }, $class; +} -Defaults are loaded from App::Ack::ConfigDefaults. This can be omitted -using C<--ignore-ack-defaults>. +sub add { + my ( $self, $filter ) = @_; -=item * Global ackrc + foreach my $ext (@{$filter->{extensions}}) { + $self->{data}->{lc $ext} = 1; + } -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>. + return; +} -=item * User ackrc +sub filter { + my ( $self, $resource ) = @_; -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 overridden with the C<$ACKRC> environment -variable. -This can be omitted using C<--noenv>. + if ($resource->name =~ /[.]([^.]*)$/) { + return exists $self->{'data'}->{lc $1}; + } -=item * Project ackrc + return 0; +} -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>. +sub inspect { + my ( $self ) = @_; -=item * --ackrc + return ref($self) . " - $self"; +} -The C<--ackrc> option may be included on the command line to specify an -ackrc file that can override all others. It is consulted even if C<--noenv> -is present. +sub to_string { + my ( $self ) = @_; -=item * ACK_OPTIONS + return join(' ', map { ".$_" } sort keys %{$self->{data}}); +} -Options are then loaded from the environment variable C. This can -be omitted using C<--noenv>. +1; +package App::Ack::Filter::MatchGroup; -=item * Command line +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} -Options are then loaded from the command line. +sub new { + my ( $class ) = @_; -=back + return bless { + matches => [], + big_re => undef, + }, $class; +} -=head1 DIFFERENCES BETWEEN ACK 1.X AND ACK 2.X +sub add { + my ( $self, $filter ) = @_; -A lot of changes were made for ack 2; here is a list of them. + push @{ $self->{matches} }, $filter->{regex}; -=head2 GENERAL CHANGES + my $re = join('|', map { "(?:$_)" } @{ $self->{matches} }); + $self->{big_re} = qr/$re/; -=over 4 + return; +} -=item * +sub filter { + my ( $self, $resource ) = @_; -When no selectors are specified, ack 1.x only searches through files that -it can map to a file type. ack 2.x, by contrast, will search through -every regular, non-binary file that is not explicitly ignored via -B<--ignore-file> or B<--ignore-dir>. This is similar to the behavior of the -B<-a/--all> option in ack 1.x. + my $re = $self->{big_re}; -=item * + return $resource->basename =~ /$re/; +} -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 inspect { + my ( $self ) = @_; +} -=item * +sub to_string { + my ( $self ) = @_; +} -ack now loads multiple ackrc files; see L for -details. +1; +package App::Ack::Filter::IsPath; -=item * +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} -ack's default filter definitions aren't special; you may tell ack to -completely disregard them if you don't like them. -=back +sub new { + my ( $class, $filename ) = @_; -=head2 REMOVED OPTIONS + return bless { + filename => $filename, + groupname => 'IsPathGroup', + }, $class; +} -=over 4 +sub create_group { + return App::Ack::Filter::IsPathGroup->new(); +} -=item * +sub filter { + my ( $self, $resource ) = @_; -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. + return $resource->name eq $self->{'filename'}; +} -=item * +sub inspect { + my ( $self ) = @_; -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 $filename = $self->{'filename'}; -=item * + return ref($self) . " - $filename"; +} -The B<--binary> option has been removed. +sub to_string { + my ( $self ) = @_; -=item * + my $filename = $self->{'filename'}; -The B<--skipped> option has been removed. + return $filename; +} -=item * +1; +package App::Ack::Filter::IsPathGroup; -The B<--text> option has been removed. +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} -=item * +sub new { + my ( $class ) = @_; -The B<--invert-file-match> option has been removed. Instead, you may -use B<-v> with B<-g>. + return bless { + data => {}, + }, $class; +} -=back +sub add { + my ( $self, $filter ) = @_; -=head2 CHANGED OPTIONS + $self->{data}->{ $filter->{filename} } = 1; -=over 4 + return; +} -=item * +sub filter { + my ( $self, $resource ) = @_; -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>. + my $data = $self->{'data'}; -=back + return exists $data->{$resource->name}; +} -=head2 ADDED OPTIONS +sub inspect { + my ( $self ) = @_; -=over 4 + return ref($self) . " - $self"; +} -=item * +sub to_string { + my ( $self ) = @_; -B<--files-from> was added so that a user may submit a list of filenames as -a list of files to search. + return join(' ', keys %{$self->{data}}); +} -=item * +1; +package File::Next; -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. +use strict; +use warnings; -=item * -B<-s> was added to tell ack to suppress error messages about non-existent or -unreadable files. +our $VERSION = '1.12'; -=item * -B<--ignore-directory> and B<--noignore-directory> were added as aliases for -B<--ignore-dir> and B<--noignore-dir> respectively. -=item * +use File::Spec (); -B<--ignore-file> was added so that users may specify patterns of files to -ignore (ex. /.*~$/). +our $name; # name of the current file +our $dir; # dir of the current file -=item * +our %files_defaults; +our %skip_dirs; -B<--dump> was added to allow users to easily find out which options are -set where. +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 * -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. +sub files { + die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__); -=item * + my ($parms,@queue) = _setup( \%files_defaults, @_ ); + my $filter = $parms->{file_filter}; -B<--type-del> was added to selectively remove file type definitions. + 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; + }; # iterator +} -=item * -B<--ignore-ack-defaults> was added so that users may ignore ack's default -options in favor of their own. -=item * -B<--bar> was added so ack users may consult Admiral Ackbar. -=back -=head1 AUTHOR +sub from_file { + die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__); -Andy Lester, C<< >> + my ($parms,@queue) = _setup( \%files_defaults, @_ ); + my $err = $parms->{error_handler}; + my $warn = $parms->{error_handler}; -=head1 BUGS + my $filename = $queue[1]; -Please report any bugs or feature requests to the issues list at -Github: L + if ( !defined($filename) ) { + $err->( 'Must pass a filename to from_file()' ); + return undef; + } -=head1 ENHANCEMENTS + my $fh; + if ( $filename eq '-' ) { + $fh = \*STDIN; + } + else { + if ( !open( $fh, '<', $filename ) ) { + $err->( "Unable to open $filename: $!" ); + return undef; + } + } + my $filter = $parms->{file_filter}; -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. + 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; + } -There is a list of enhancements I want to make to F in the ack -issues list at Github: L + 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; -Patches are always welcome, but patches with tests get the most -attention. + return; + }; # iterator +} -=head1 SUPPORT +sub _bad_invocation { + my $good = (caller(1))[3]; + my $bad = $good; + $bad =~ s/(.+)::/$1->/; + return "$good must not be invoked as $bad"; +} -Support for and information about F can be found at: +sub sort_standard($$) { return $_[0]->[1] cmp $_[1]->[1] } +sub sort_reverse($$) { return $_[1]->[1] cmp $_[0]->[1] } -=over 4 +sub reslash { + my $path = shift; -=item * The ack homepage + my @parts = split( /\//, $path ); -L + return $path if @parts < 2; -=item * The ack-users mailing list + return File::Spec->catfile( @parts ); +} -L -=item * The ack issues list at Github -L +sub _setup { + my $defaults = shift; + my $passed_parms = ref $_[0] eq 'HASH' ? {%{+shift}} : {}; # copy parm hash -=item * AnnoCPAN: Annotated CPAN documentation + my %passed_parms = %{$passed_parms}; -L + my $parms = {}; + for my $key ( keys %{$defaults} ) { + $parms->{$key} = + exists $passed_parms{$key} + ? delete $passed_parms{$key} + : $defaults->{$key}; + } -=item * CPAN Ratings + # 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" ); + } -L + # 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; -=item * Search CPAN + for ( @_ ) { + my $start = reslash( $_ ); + if (-d $start) { + push @queue, ($start,undef,$start); + } + else { + push @queue, (undef,$start,$start); + } + } -L + return ($parms,@queue); +} -=item * Git source repository -L +sub _candidate_files { + my $parms = shift; + my $dirname = shift; -=back + my $dh; + if ( !opendir $dh, $dirname ) { + $parms->{error_handler}->( "$dirname: $!" ); + return; + } -=head1 ACKNOWLEDGEMENTS + my @newfiles; + my $descend_filter = $parms->{descend_filter}; + my $follow_symlinks = $parms->{follow_symlinks}; + my $sort_sub = $parms->{sort_files}; -How appropriate to have Inowledgements! + for my $file ( grep { !exists $skip_dirs{$_} } readdir $dh ) { + my $has_stat; -Thanks to everyone who has contributed to ack in any way, including -Michael Beijen, -Alexandr Ciornii, -Christian Walde, -Charles Lee, -Joe McMahon, -John Warwick, -David Steinbrunner, -Kara Martens, -Volodymyr Medvid, -Ron Savage, -Konrad Borowski, -Dale Sedivic, -Michael McClimon, -Andrew Black, -Ralph Bodenner, -Shaun Patterson, -Ryan Olson, -Shlomi Fish, -Karen Etheridge, -Olivier Mengue, -Matthew Wild, -Scott Kyle, -Nick Hooey, -Bo Borgerson, -Mark Szymanski, -Marq Schneider, -Packy Anderson, -JR Boyens, -Dan Sully, -Ryan Niebur, -Kent Fredric, -Mike Morearty, -Ingmar Vanhassel, -Eric Van Dewoestine, -Sitaram Chamarty, -Adam James, -Richard Carlsson, -Pedro Melo, -AJ Schuster, -Phil Jackson, -Michael Schwern, -Jan Dubois, -Christopher J. Madsen, -Matthew Wickline, -David Dyck, -Jason Porritt, -Jjgod Jiang, -Thomas Klausner, -Uri Guttman, -Peter Lewis, -Kevin Riggle, -Ori Avtalion, -Torsten Blix, -Nigel Metheringham, -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. + # 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; + } -=head1 COPYRIGHT & LICENSE + 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; -Copyright 2005-2013 Andy Lester. + if ( $sort_sub ) { + return map { @{$_} } sort $sort_sub @newfiles; + } -This program is free software; you can redistribute it and/or modify -it under the terms of the Artistic License v2.0. + return @newfiles; +} -See http://www.perlfoundation.org/artistic_license_2_0 or the LICENSE.md -file that comes with the ack distribution. -=cut +1; # End of File::Next -- 2.43.0