From 986bf8e26d17b97324f1e126e82ba84294fdf9a7 Mon Sep 17 00:00:00 2001 From: Tony Duckles Date: Sun, 29 Sep 2013 12:41:24 +0200 Subject: [PATCH] ack v2.10 --- bin/ack | 7012 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 3677 insertions(+), 3335 deletions(-) diff --git a/bin/ack b/bin/ack index 2aa5941..66d807c 100755 --- a/bin/ack +++ b/bin/ack @@ -9,4583 +9,4925 @@ # that build ack. # +package File::Next; + use strict; use warnings; -use 5.008008; - - -# XXX Don't make this so brute force -# See also: https://github.com/petdance/ack2/issues/89 - -use Getopt::Long 2.35 (); - -use Carp 1.04 (); -our $VERSION = '2.04'; -# Check http://beyondgrep.com/ for updates +our $VERSION = '1.12'; -# These are all our globals. -MAIN: { - $App::Ack::orig_program_name = $0; - $0 = join(' ', 'ack', $0); - if ( $App::Ack::VERSION ne $main::VERSION ) { - App::Ack::die( "Program/library version mismatch\n\t$0 is $main::VERSION\n\t$INC{'App/Ack.pm'} is $App::Ack::VERSION" ); - } - # Do preliminary arg checking; - my $env_is_usable = 1; - for ( @ARGV ) { - last if ( $_ eq '--' ); +use File::Spec (); - # Get the --thpppt and --bar checking out of the way. - /^--th[pt]+t+$/ && App::Ack::_thpppt($_); - /^--bar$/ && App::Ack::_bar(); +our $name; # name of the current file +our $dir; # dir of the current file - # See if we want to ignore the environment. (Don't tell Al Gore.) - if ( /^--(no)?env$/ ) { - $env_is_usable = defined $1 ? 0 : 1; - } - } - if ( !$env_is_usable ) { - my @keys = ( 'ACKRC', grep { /^ACK_/ } keys %ENV ); - delete @ENV{@keys}; - } - load_colors(); +our %files_defaults; +our %skip_dirs; - 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; }, +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, ); - Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); - - if ( !@ARGV ) { - App::Ack::show_help(); - exit 1; - } - - main(); + %skip_dirs = map {($_,1)} (File::Spec->curdir, File::Spec->updir); } -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 files { + die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__); - my %ignore_dirs; + my ($parms,@queue) = _setup( \%files_defaults, @_ ); + my $filter = $parms->{file_filter}; - foreach my $idir (@{$idirs}) { - if ( $idir =~ /^(\w+):(.*)/ ) { - if ( $1 eq 'is') { - $ignore_dirs{$2} = 1; + 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; } - else { - Carp::croak( 'Non-is filters are not yet supported for --ignore-dir' ); + elsif ( -d _ ) { + unshift( @queue, _candidate_files( $parms, $fullpath ) ); } - } - else { - Carp::croak( qq{Invalid filter specification "$idir"} ); - } - } + } # while - return sub { - return !exists $ignore_dirs{$_} && !exists $ignore_dirs{$File::Next::dir}; - }; + return; + }; # iterator } -sub _compile_file_filter { - my ( $opt, $start ) = @_; - my $ifiles = $opt->{ifiles}; - $ifiles ||= []; - my @ifiles_filters = map { - my $filter; - if ( /^(\w+):(.+)/ ) { - my ($how,$what) = ($1,$2); - $filter = App::Ack::Filter->create_filter($how, split(/,/, $what)); - } - else { - Carp::croak( qq{Invalid filter specification "$_"} ); - } - $filter - } @{$ifiles}; - my $filters = $opt->{'filters'} || []; - my $inverse_filters = [ grep { $_->is_inverted() } @{$filters} ]; - @{$filters} = grep { !$_->is_inverted() } @{$filters}; - my %is_member_of_starting_set = map { (get_file_id($_) => 1) } @{$start}; +sub from_file { + die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__); - my $ignore_dir_list = $opt->{idirs}; - my $dont_ignore_dir_list = $opt->{no_ignore_dirs}; + my ($parms,@queue) = _setup( \%files_defaults, @_ ); + my $err = $parms->{error_handler}; + my $warn = $parms->{error_handler}; - my %ignore_dir_set; - my %dont_ignore_dir_set; + my $filename = $queue[1]; - 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"} ); - } + if ( !defined($filename) ) { + $err->( 'Must pass a filename to from_file()' ); + return undef; } - 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"} ); + + my $fh; + if ( $filename eq '-' ) { + $fh = \*STDIN; + } + else { + if ( !open( $fh, '<', $filename ) ) { + $err->( "Unable to open $filename: $!" ); + return undef; } } + my $filter = $parms->{file_filter}; return 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; - - 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; + 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; } - } - - # 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" ); + 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 0; - } - - my $resource = App::Ack::Resource::Basic->new($File::Next::name); - return 0 if ! $resource; - foreach my $filter (@ifiles_filters) { - return 0 if $filter->filter($resource); - } - my $match_found = 1; - if ( @{$filters} ) { - $match_found = 0; + return wantarray ? ($dirname,$file,$fullpath) : $fullpath; + } # while + close $fh; - foreach my $filter (@{$filters}) { - if ($filter->filter($resource)) { - $match_found = 1; - last; - } - } - } - # Don't bother invoking inverse filters unless we consider the current resource a match - if ( $match_found && @{$inverse_filters} ) { - foreach my $filter ( @{$inverse_filters} ) { - if ( not $filter->filter( $resource ) ) { - $match_found = 0; - last; - } - } - } - return $match_found; - }; + return; + }; # iterator } -sub show_types { - my $resource = shift; - my $ors = shift; +sub _bad_invocation { + my $good = (caller(1))[3]; + my $bad = $good; + $bad =~ s/(.+)::/$1->/; + return "$good must not be invoked as $bad"; +} - my @types = filetypes( $resource ); - my $types = join( ',', @types ); - my $arrow = @types ? ' => ' : ' =>'; - App::Ack::print( $resource->name, $arrow, join( ',', @types ), $ors ); +sub sort_standard($$) { return $_[0]->[1] cmp $_[1]->[1] } +sub sort_reverse($$) { return $_[1]->[1] cmp $_[0]->[1] } - return; -} +sub reslash { + my $path = shift; -# Set default colors, load Term::ANSIColor -sub load_colors { - eval 'use Term::ANSIColor 1.10 ()'; + my @parts = split( /\//, $path ); - $ENV{ACK_COLOR_MATCH} ||= 'black on_yellow'; - $ENV{ACK_COLOR_FILENAME} ||= 'bold green'; - $ENV{ACK_COLOR_LINENO} ||= 'bold yellow'; + return $path if @parts < 2; - return; + return File::Spec->catfile( @parts ); } -# inefficient, but functional -sub filetypes { - my ( $resource ) = @_; - my @matches; - foreach my $k (keys %App::Ack::mappings) { - my $filters = $App::Ack::mappings{$k}; +sub _setup { + my $defaults = shift; + my $passed_parms = ref $_[0] eq 'HASH' ? {%{+shift}} : {}; # copy parm hash - foreach my $filter (@{$filters}) { - # clone the resource - my $clone = $resource->clone; - if ( $filter->filter($clone) ) { - push @matches, $k; - last; - } - } - } + my %passed_parms = %{$passed_parms}; - return sort @matches; -} + my $parms = {}; + for my $key ( keys %{$defaults} ) { + $parms->{$key} = + exists $passed_parms{$key} + ? delete $passed_parms{$key} + : $defaults->{$key}; + } -# 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 ) = @_; + # Any leftover keys are bogus + for my $badkey ( keys %passed_parms ) { + my $sub = (caller(1))[3]; + $parms->{error_handler}->( "Invalid option passed to $sub(): $badkey" ); + } - if ( $App::Ack::is_windows ) { - return File::Next::reslash( $filename ); + # If it's not a code ref, assume standard sort + if ( $parms->{sort_files} && ( ref($parms->{sort_files}) ne 'CODE' ) ) { + $parms->{sort_files} = \&sort_standard; } - else { - # XXX is this the best method? it always hits the FS - if( my ( $dev, $inode ) = (stat($filename))[0, 1] ) { - return join(':', $dev, $inode); + my @queue; + + for ( @_ ) { + my $start = reslash( $_ ); + if (-d $start) { + push @queue, ($start,undef,$start); } else { - # XXX this could be better - return $filename; + push @queue, (undef,$start,$start); } } -} -# Returns a regex object based on a string and command-line options. -# Dies when the regex $str is undefinied (i.e. not given on command line). + return ($parms,@queue); +} -sub build_regex { - my $str = shift; - my $opt = shift; - defined $str or App::Ack::die( 'No regular expression found.' ); +sub _candidate_files { + my $parms = shift; + my $dirname = shift; - $str = quotemeta( $str ) if $opt->{Q}; - if ( $opt->{w} ) { - $str = "\\b$str" if $str =~ /^\w/; - $str = "$str\\b" if $str =~ /\w$/; + my $dh; + if ( !opendir $dh, $dirname ) { + $parms->{error_handler}->( "$dirname: $!" ); + return; } - my $regex_is_lc = $str eq lc $str; - if ( $opt->{i} || ($opt->{smart_case} && $regex_is_lc) ) { - $str = "(?i)$str"; - } + my @newfiles; + my $descend_filter = $parms->{descend_filter}; + my $follow_symlinks = $parms->{follow_symlinks}; + my $sort_sub = $parms->{sort_files}; - my $re = eval { qr/$str/ }; - if ( !$re ) { - die "Invalid regex '$str':\n $@"; + for my $file ( grep { !exists $skip_dirs{$_} } readdir $dh ) { + my $has_stat; + + # 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; + } + + 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; - return $re; + if ( $sort_sub ) { + return map { @{$_} } sort $sort_sub @newfiles; + } + return @newfiles; } -{ -my @before_ctx_lines; -my @after_ctx_lines; -my $is_iterating; +1; # End of File::Next +package App::Ack; -my $has_printed_something; +use warnings; +use strict; + +our $VERSION; +our $GIT_REVISION; +our $COPYRIGHT; BEGIN { - $has_printed_something = 0; + $VERSION = '2.10'; + $COPYRIGHT = 'Copyright 2005-2013 Andy Lester.'; + $GIT_REVISION = q{af91cce}; } -sub print_matches_in_resource { - my ( $resource, $opt ) = @_; +our $fh; - 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}; +BEGIN { + $fh = *STDOUT; +} - my $has_printed_for_this_resource = 0; - $is_iterating = 1; +our %types; +our %type_wanted; +our %mappings; +our %ignore_dirs; - local $opt->{before_context} = $opt->{output} ? 0 : $opt->{before_context}; - local $opt->{after_context} = $opt->{output} ? 0 : $opt->{after_context}; +our $is_filter_mode; +our $output_to_pipe; - my $n_before_ctx_lines = $opt->{before_context} || 0; - my $n_after_ctx_lines = $opt->{after_context} || 0; +our $dir_sep_chars; +our $is_cygwin; +our $is_windows; - @after_ctx_lines = @before_ctx_lines = (); +use File::Spec 1.00015 (); - my $fh = $resource->open(); - if ( !$fh ) { - if ( $App::Ack::report_bad_filenames ) { - App::Ack::warn( "$filename: $!" ); - } - return 0; - } +BEGIN { + # These have to be checked before any filehandle diddling. + $output_to_pipe = not -t *STDOUT; + $is_filter_mode = -p STDIN; - my $display_filename = $filename; - if ( $print_filename && $heading && $color ) { - $display_filename = Term::ANSIColor::colored($display_filename, $ENV{ACK_COLOR_FILENAME}); - } + $is_cygwin = ($^O eq 'cygwin'); + $is_windows = ($^O eq 'MSWin32'); + $dir_sep_chars = $is_windows ? quotemeta( '\\/' ) : quotemeta( File::Spec->catfile( '', '' ) ); +} - # check for context before the main loop, so we don't - # pay for it if we don't need it - if ( $n_before_ctx_lines || $n_after_ctx_lines ) { - my $current_line = <$fh>; # prime the first line of input - while ( defined $current_line ) { - while ( (@after_ctx_lines < $n_after_ctx_lines) && defined($_ = <$fh>) ) { - push @after_ctx_lines, $_; - } - local $_ = $current_line; - my $former_dot_period = $.; - $. -= @after_ctx_lines; +sub remove_dir_sep { + my $path = shift; + $path =~ s/[$dir_sep_chars]$//; - 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; + return $path; +} - # 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 $_; - 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; - } - } +sub warn { + return CORE::warn( _my_program(), ': ', @_, "\n" ); +} - $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 die { + return CORE::die( _my_program(), ': ', @_, "\n" ); } -sub print_line_with_options { - my ( $opt, $filename, $line, $line_no, $separator ) = @_; +sub _my_program { + require File::Basename; + return File::Basename::basename( $0 ); +} - $has_printed_something = 1; - 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}; - my @line_parts; +sub filetypes_supported { + return keys %mappings; +} - if( $color ) { - $filename = Term::ANSIColor::colored($filename, - $ENV{ACK_COLOR_FILENAME}); - $line_no = Term::ANSIColor::colored($line_no, - $ENV{ACK_COLOR_LINENO}); - } +sub _get_thpppt { + my $y = q{_ /|,\\'!.x',=(www)=, U }; + $y =~ tr/,x!w/\nOo_/; + return $y; +} - if($print_filename) { - if( $heading ) { - push @line_parts, $line_no; - } - else { - push @line_parts, $filename, $line_no; - } +sub _thpppt { + my $y = _get_thpppt(); + App::Ack::print( "$y ack $_[0]!\n" ); + exit 0; +} - if( $print_column ) { - push @line_parts, get_match_column(); - } - } - if( $output_expr ) { - while ( $line =~ /$opt->{regex}/og ) { - my $output = eval $output_expr; - App::Ack::print( join( $separator, @line_parts, $output ), $ors ); - } - } - else { - if ( $color ) { - my @capture_indices = get_capture_indices(); - if( @capture_indices ) { - my $offset = 0; # additional offset for when we add stuff +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 - foreach my $index_pair ( @capture_indices ) { - my ( $match_start, $match_end ) = @{$index_pair}; + App::Ack::__pic($x); +} + +sub _cathy { + my $x = <<'CATHY'; + 0+!--+! + 0|! "C!H!O!C!O!L!A!T!E!!! !|! + 0|! "C!H!O!C!O!L!A!T!E!!! !|! + 0|! "C!H!O!C!O!L!A!T!E!!! !|! + 0|! $A"C!K!!! $|! + 0+!--+! + 6\! 1:!,!.! ! + 7\! /.!M!~!Z!M!~! + 8\! /~!D! "M! ! + 4.! $\! /M!~!.!8! +.!M# 4 + 0,!.! (\! .~!M!N! ,+!I!.!M!.! 3 + /?!O!.!M!:! '\! .O!.! +~!Z!=!N!.! 4 + ..! !D!Z!.!Z!.! '\! 9=!M".! 6 + /.! !.!~!M".! '\! 8~! 9 + 4M!.! /.!7!N!M!.! F + 4.! &:!M! !N"M# !M"N!M! #D!M&=! = + :M!7!M#:! !~!M!7!,!$!M!:! #.! !O!N!.!M!:!M# ; + 8Z!M"~!N!$!D!.!N!?! !I!N!.! (?!M! !M!,!D!M".! 9 + (?!Z!M!N!:! )=!M!O!8!.!M!+!M! !M!,! !O!M! +,!M!.!M!~!Z!N!M!:! &:!~! 0 + &8!7!.!~!M"D!M!,! &M!?!=!8! !M!,!O! !M!+! !+!O!.!M! $M#~! !.!8!M!Z!.!M! !O!M"Z! %:!~!M!Z!M!Z!.! + + &:!M!7!,! *M!.!Z!M! !8"M!.!M!~! !.!M!.!=! #~!8!.!M! !7!M! "N!Z#I! !D!M!,!M!.! $."M!,! !M!.! * + 2$!O! "N! !.!M!I! !7" "M! "+!O! !~!M! !d!O!.!7!I!M!.! !.!O!=!M!.! !M",!M!.! %.!$!O!D! + + 1~!O! "M!+! !8!$! "M! "?!O! %Z!8!D!M!?!8!I!O!7!M! #M!.!M! "M",!M! 4 + 07!~! ".!8! !.!M! "I!+! !.!M! &Z!D!.!7!=!M! !:!.!M! #:!8"+! !.!+!8! !8! 3 + /~!M! #N! !~!M!$! !.!M! !.!M" &~!M! "~!M!O! "D! $M! !8! "M!,!M!+!D!.! 1 + #.! #?!M!N!.! #~!O! $M!.!7!$! "?" !?!~!M! '7!8!?!M!.!+!M"O! $?"$!D! !.!O! !$!7!I!.! 0 + $,!M!:!O!?! ".! !?!=! $=!:!O! !M! "M! !M! !+!$! (.! +.!M! !M!.! !8! !+"Z!~! $:!M!$! !.! ' + #.!8!.!I!$! $7!I! %M" !=!M! !~!M!D! "7!I! .I!O! %?!=!,!D! !,!M! !D!~!8!~! %D!M! ( + #.!M"?! $=!O! %=!N! "8!.! !Z!M! #M!~! (M!:! #.!M" &O! !M!.! !?!,! !8!.!N!~! $8!N!M!,!.! % + *$!O! &M!,! "O! !.!M!.! #M! (~!M( &O!.! !7! "M! !.!M!.!M!,! #.!M! !M! & + )=!8!.! $.!M!O!.! "$!.!I!N! !I!M# (7!M(I! %D"Z!M! "=!I! "M! !M!:! #~!D! ' + )D! &8!N!:! ".!O! !M!="M! "M! (7!M) %." !M!D!."M!.! !$!=! !M!,! + + (M! &+!.!M! #Z!7!O!M!.!~!8! +,!M#D!?!M#D! #.!Z!M#,!Z!?! !~!N! "N!.! !M! + + 'D!:! %$!D! !?! #M!Z! !8!.! !M"?!7!?!7! '+!I!D! !?!O!:!M!:! ":!M!:! !M!7".!M! "8!+! !:!D! !.!M! * + %.!O!:! $.!O!+! !D!.! #M! "M!.!+!N!I!Z! "7!M!N!M!N!?!I!7!Z!=!M'D"~! #M!.!8!$! !:! !.!M! "N!?! !,!O! ) + !.!?!M!:!M!I! %8!,! "M!.! #M! "N! !M!.! !M!.! !+!~! !.!M!.! ':!M! $M! $M!Z!$! !M!.! "D! "M! "?!M! ( + !7!8! !+!I! ".! "$!=! ":!$! "+! !M!.! !O! !M!I!M".! !=!~! ",!O! '=!M! $$!,! #N!:! ":!8!.! !D!~! !,!M!.! !:!M!.! & + !:!,!.! &Z" #D! !.!8!."M!.! !8!?!Z!M!.!M! #Z!~! !?!M!Z!.! %~!O!.!8!$!N!8!O!I!:!~! !+! #M!.! !.!M!.! !+!M! ".!~!M!+! $ + !.! 'D!I! #?!M!.!M!,! !.!Z! !.!8! #M&O!I!?! (~!I!M"." !M!Z!.! !M!N!.! "+!$!.! "M!.! !M!?!.! "8!M! $ + (O!8! $M! !M!.! ".!:! !+!=! #M! #.!M! !+" *$!M":!.! !M!~! "M!7! #M! #7!Z! "M"$!M!.! !.! # + '$!Z! #.!7!+!M! $.!,! !+!:! #N! #.!M!.!+!M! +D!M! #=!N! ":!O! #=!M! #Z!D! $M!I! % + $,! ".! $.!M" %$!.! !?!~! "+!7!." !.!M!,! !M! *,!N!M!.$M!?! "D!,! #M!.! #N! + + ,M!Z! &M! "I!,! "M! %I!M! !?!=!.! (Z!8!M! $:!M!.! !,!M! $D! #.!M!.! ) + +8!O! &.!8! "I!,! !~!M! &N!M! !M!D! '?!N!O!." $?!7! "?!~! #M!.! #I!D!.! ( + 3M!,! "N!.! !D" &.!+!M!.! !M":!.":!M!7!M!D! 'M!.! "M!.! "M!,! $I! ) + 3I! #M! "M!,! !:! &.!M" ".!,! !.!$!M!I! #.! !:! !.!M!?! "N!+! ".! / + 1M!,! #.!M!8!M!=!.! +~!N"O!Z"~! *+!M!.! "M! 2 + 0.!M! &M!.! 8:! %.!M!Z! "M!=! *O!,! % + 0?!$! &N! )." .,! %."M! ":!M!.! 0 + 0N!:! %?!O! #.! ..! &,! &.!D!,! "N!I! 0 +CATHY + App::Ack::__pic($x); +} + +sub __pic { + my($compressed) = @_; + $compressed =~ s/(.)(.)/$1x(ord($2)-32)/eg; + App::Ack::print( $compressed ); + exit 0; +} - 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 ); +sub show_help { + my $help_arg = shift || 0; - $offset += length( $substitution ) - length( $substring ); - } - } - else { - my $matched = 0; # flag; if matched, need to escape afterwards + return show_help_types() if $help_arg =~ /^types?/; - while ( $line =~ /$opt->{regex}/og ) { + App::Ack::print( <<"END_OF_HELP" ); +Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES] - $matched = 1; - my ( $match_start, $match_end ) = ($-[0], $+[0]); +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 "-". - my $substring = substr( $line, $match_start, - $match_end - $match_start ); - my $substitution = Term::ANSIColor::colored( $substring, - $ENV{ACK_COLOR_MATCH} ); +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. - substr( $line, $match_start, $match_end - $match_start, - $substitution ); +Example: ack -i select - pos($line) = $match_end + - (length( $substitution ) - length( $substring )); - } - $line .= "\033[0m\033[K" if $matched; - } - } +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 - push @line_parts, $line; - App::Ack::print( join( $separator, @line_parts ), $ors ); - } +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 - return; -} + -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. -sub iterate { - my ( $resource, $opt, $cb ) = @_; + --print0 Print null byte as separator between filenames, + only works with -f, -g, -l, -L or -c. - $is_iterating = 1; + -s Suppress error messages about nonexistent or + unreadable files. - local $opt->{before_context} = $opt->{output} ? 0 : $opt->{before_context}; - local $opt->{after_context} = $opt->{output} ? 0 : $opt->{after_context}; - my $n_before_ctx_lines = $opt->{before_context} || 0; - my $n_after_ctx_lines = $opt->{after_context} || 0; +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). - @after_ctx_lines = @before_ctx_lines = (); - my $fh = $resource->open(); - if ( !$fh ) { - if ( $App::Ack::report_bad_filenames ) { - # XXX direct access to filename - App::Ack::warn( "$resource->{filename}: $!" ); - } - return; - } +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. - # 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 +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. - while ( defined $current_line ) { - while ( (@after_ctx_lines < $n_after_ctx_lines) && defined($_ = <$fh>) ) { - push @after_ctx_lines, $_; - } + --type=X Include only X files, where X is a recognized + filetype. + --type=noX Exclude X files. + See "ack --help-types" for supported filetypes. - local $_ = $current_line; - my $former_dot_period = $.; - $. -= @after_ctx_lines; +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 unless $cb->(); - # I tried doing this with local(), but for some reason, - # $. continued to have its new value after the exit of the - # enclosing block. I'm guessing that $. has some extra - # magic associated with it or something. If someone can - # tell me why this happened, I would love to know! - $. = $former_dot_period; # XXX this won't happen on an exception +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! - 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 $_; - - while ( <$fh> ) { - last unless $cb->(); - } - } +Exit status is 0 if match, 1 if no match. - $is_iterating = 0; # XXX this won't happen on an exception - # then again, do we care? ack doesn't really - # handle exceptions anyway. +This is version $VERSION of ack. +END_OF_HELP return; -} - -sub get_context { - if ( not $is_iterating ) { - Carp::croak( 'get_context() called outside of iterate()' ); - } - - return ( - scalar(@before_ctx_lines) ? \@before_ctx_lines : undef, - scalar(@after_ctx_lines) ? \@after_ctx_lines : undef, - ); -} + } -} -{ -my $is_first_match; -my $previous_file_processed; -my $previous_line_printed; +sub show_help_types { + App::Ack::print( <<'END_OF_HELP' ); +Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES] -BEGIN { - $is_first_match = 1; - $previous_line_printed = -1; -} +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 print_line_with_context { - my ( $opt, $filename, $matching_line, $line_no ) = @_; +Note that some extensions may appear in multiple types. For example, +.pod files are both Perl and Parrot. - my $heading = $opt->{heading}; +END_OF_HELP - if( !defined($previous_file_processed) || - $previous_file_processed ne $filename ) { - $previous_file_processed = $filename; - $previous_line_printed = -1; + my @types = filetypes_supported(); + my $maxlen = 0; + for ( @types ) { + $maxlen = length if $maxlen < length; + } + for my $type ( sort @types ) { + next if $type =~ /^-/; # Stuff to not show + my $ext_list = $mappings{$type}; - if( $heading ) { - $is_first_match = 1; + 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 ) ); } - 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}; - - $matching_line =~ s/[\r\n]+$//g; - - my ( $before_context, $after_context ) = get_context(); + return; +} - if ( $before_context ) { - my $first_line = $. - @{$before_context}; +sub show_man { + require Pod::Usage; - 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}; + Pod::Usage::pod2usage({ + -input => $App::Ack::orig_program_name, + -verbose => 2, + -exitval => 0, + }); - 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; - } + return; +} - chomp $line; - print_line_with_options($opt, $filename, $line, $context_line_no, '-'); - $previous_line_printed = $context_line_no; - $offset--; - } - } - } - if ( $. > $previous_line_printed ) { - if( $is_tracking_context && !$is_first_match && $previous_line_printed != $. - 1 ) { - App::Ack::print('--', $ors); - } +sub get_version_statement { + require Config; - print_line_with_options($opt, $filename, $matching_line, $line_no, ':'); - $previous_line_printed = $.; + 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 ); - 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++; - } - } + my $git_revision = $GIT_REVISION ? " (git commit $GIT_REVISION)" : ''; - $is_first_match = 0; + return <<"END_OF_VERSION"; +ack ${VERSION}${git_revision} +Running under Perl $ver at $this_perl - return; -} +$copyright +This program is free software. You may modify or distribute it +under the terms of the Artistic License v2.0. +END_OF_VERSION } -{ - -my @capture_indices; -my $match_column_number; - -# does_match() MUST have an $opt->{regex} set. - -sub does_match { - my ( $opt, $line ) = @_; - - $match_column_number = undef; - @capture_indices = (); - 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; +sub print_version_statement { + App::Ack::print( get_version_statement() ); - if ( @- > 1 ) { - @capture_indices = map { - [ $-[$_], $+[$_] ] - } ( 1 .. $#- ); - } - return 1; - } - else { - return; - } - } + return; } -sub get_capture_indices { - return @capture_indices; -} -sub get_match_column { - return $match_column_number; +sub get_copyright { + return $COPYRIGHT; } -} -sub resource_has_match { - my ( $resource, $opt ) = @_; +# 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; - 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}: $!" ); - } + if ($show_filename) { + App::Ack::print( $filename ); + App::Ack::print( ':', $nmatches ) if $count; } else { - my $opt_v = $opt->{v}; - my $re = $opt->{regex}; - while ( <$fh> ) { - if (/$re/o xor $opt_v) { - $has_match = 1; - last; - } - } - close $fh; + App::Ack::print( $nmatches ) if $count; } + App::Ack::print( $ors ); - return $has_match; + return; } -sub count_matches_in_resource { - my ( $resource, $opt ) = @_; +sub print_count0 { + my $filename = shift; + my $ors = shift; + my $show_filename = shift; - 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}: $!" ); - } + if ($show_filename) { + App::Ack::print( $filename, ':0', $ors ); } else { - my $opt_v = $opt->{v}; - my $re = $opt->{regex}; - while ( <$fh> ) { - ++$nmatches if (/$re/o xor $opt_v); - } - close $fh; + App::Ack::print( '0', $ors ); } - return $nmatches; + return; } -sub main { - my @arg_sources = App::Ack::ConfigLoader::retrieve_arg_sources(); - - my $opt = App::Ack::ConfigLoader::process_args( @arg_sources ); +sub set_up_pager { + my $command = shift; - $App::Ack::report_bad_filenames = !$opt->{dont_report_bad_filenames}; + return if App::Ack::output_to_pipe(); - if ( $opt->{flush} ) { - $| = 1; - } - - if ( not defined $opt->{color} ) { - $opt->{color} = !App::Ack::output_to_pipe() && !$App::Ack::is_windows; - } - if ( not defined $opt->{heading} and not defined $opt->{break} ) { - $opt->{heading} = $opt->{break} = !App::Ack::output_to_pipe(); - } - - if ( defined($opt->{H}) || defined($opt->{h}) ) { - $opt->{show_filename}= $opt->{H} && !$opt->{h}; - } - - if ( my $output = $opt->{output} ) { - $output =~ s{\\}{\\\\}g; - $output =~ s{"}{\\"}g; - $opt->{output} = qq{"$output"}; + my $pager; + if ( not open( $pager, '|-', $command ) ) { + App::Ack::die( qq{Unable to pipe to pager "$command": $!} ); } + $fh = $pager; - 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; - } - } + return; +} - if ( defined $opt->{files_from} ) { - $resources = App::Ack::Resources->from_file( $opt, $opt->{files_from} ); - exit 1 unless $resources; - } - else { - @start = ('.') unless @start; - foreach my $target (@start) { - if ( !-e $target && $App::Ack::report_bad_filenames) { - App::Ack::warn( "$target: No such file or directory" ); - } - } - $opt->{file_filter} = _compile_file_filter($opt, \@start); - $opt->{descend_filter} = _compile_descend_filter($opt); +sub output_to_pipe { + return $output_to_pipe; +} - $resources = App::Ack::Resources->from_argv( $opt, \@start ); - } - } - App::Ack::set_up_pager( $opt->{pager} ) if defined $opt->{pager}; - my $print_filenames = $opt->{show_filename}; - my $max_count = $opt->{m}; - my $ors = $opt->{print0} ? "\0" : "\n"; - my $only_first = $opt->{1}; +sub exit_from_ack { + my $nmatches = shift; - my $nmatches = 0; - my $total_count = 0; -RESOURCES: - while ( my $resource = $resources->next ) { - # XXX this variable name combined with what we're trying - # to do makes no sense. + my $rc = $nmatches ? 0 : 1; + exit $rc; +} - # 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}; - 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; +1; # End of App::Ack +package App::Ack::Resource; - local $opt->{color} = 0; - iterate($resource, $opt, sub { - chomp; +use warnings; +use strict; +use overload + '""' => 'name'; - 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 ); +sub FAIL { + require Carp; + Carp::confess( 'Must be overloaded' ); +} - unless ( $opt->{show_filename} ) { - $total_count += $matches_for_this_file; - next RESOURCES; - } - 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 ); +sub new { + return FAIL(); +} - 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 name { + return FAIL(); +} - if ( $opt->{count} && !$opt->{show_filename} ) { - App::Ack::print( $total_count, "\n" ); - } - close $App::Ack::fh; - App::Ack::exit_from_ack( $nmatches ); +sub is_binary { + return FAIL(); } +sub open { + return FAIL(); +} -=head1 NAME -ack - grep-like text finder +sub needs_line_scan { + return FAIL(); +} -=head1 SYNOPSIS - ack [options] PATTERN [FILE...] - ack -f [options] [DIRECTORY...] +sub reset { + return FAIL(); +} -=head1 DESCRIPTION -Ack is designed as a replacement for 99% of the uses of F. +sub close { + return FAIL(); +} -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. +sub clone { + return FAIL(); +} -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 +sub firstliney { + return FAIL(); +} -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. +1; +package App::Ack::Resources; -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: +use warnings; +use strict; -=over 4 -=item * Backup files: Files matching F<#*#> or ending with F<~>. +sub from_argv { + my $class = shift; + my $opt = shift; + my $start = shift; -=item * Coredumps: Files matching F + my $self = bless {}, $class; -=item * Version control directories like F<.svn> and F<.git>. + my $file_filter = undef; + my $descend_filter = $opt->{descend_filter}; -=back + if( $opt->{n} ) { + $descend_filter = sub { + return 0; + }; + } -Run I with the C<--dump> option to see what settings are set. + $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} ); -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. + return $self; +} -=head1 DIRECTORY SELECTION -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. +sub from_file { + my $class = shift; + my $opt = shift; + my $file = shift; -For a complete list of directories that do not get searched, run -C. + 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; -=head1 WHEN TO USE GREP + return bless { + iter => $iter, + }, $class; +} -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. +# This is for reading input lines from STDIN, not the list of files from STDIN +sub from_stdin { + my $class = shift; + my $opt = shift; -E.g., searching through huge files looking for regexes that can be -expressed with I syntax should be quicker with I. + my $self = bless {}, $class; -If your script or parent program uses I C<--quiet> or C<--silent> -or needs exit 2 on IO error, use I. + my $has_been_called = 0; -=head1 OPTIONS + $self->{iter} = sub { + if ( !$has_been_called ) { + $has_been_called = 1; + return '-'; + } + return; + }; -=over 4 + return $self; +} -=item B<-A I>, B<--after-context=I> +sub next { + my $self = shift; -Print I lines of trailing context after matching lines. + my $file = $self->{iter}->() or return; -=item B<-B I>, B<--before-context=I> + return App::Ack::Resource::Basic->new( $file ); +} -Print I lines of leading context before matching lines. +1; +package App::Ack::Resource::Basic; -=item B<--[no]break> -Print a break between results from different files. On by default -when used interactively. +use warnings; +use strict; -=item B<-C [I]>, B<--context[=I]> +use Fcntl (); -Print I lines (default 2) of context around matching lines. +BEGIN { + our @ISA = 'App::Ack::Resource'; +} -=item B<-c>, B<--count> -Suppress normal output; instead print a count of matching lines for -each input file. If B<-l> is in effect, it will only show the -number of lines for each file that has lines matching. Without -B<-l>, some line counts may be zeroes. +sub new { + my $class = shift; + my $filename = shift; -If combined with B<-h> (B<--no-filename>) ack outputs only one total -count. + my $self = bless { + filename => $filename, + fh => undef, + opened => 0, + }, $class; -=item B<--[no]color>, B<--[no]colour> + if ( $self->{filename} eq '-' ) { + $self->{fh} = *STDIN; + $self->{opened} = 1; + } -B<--color> highlights the matching text. B<--nocolor> supresses -the color. This is on by default unless the output is redirected. + return $self; +} -On Windows, this option is off by default unless the -L module is installed or the C -environment variable is used. -=item B<--color-filename=I> +sub name { + return $_[0]->{filename}; +} -Sets the color to be used for filenames. -=item B<--color-match=I> -Sets the color to be used for matches. +sub needs_line_scan { + my $self = shift; + my $opt = shift; -=item B<--color-lineno=I> + return 1 if $opt->{v}; -Sets the color to be used for line numbers. + my $size = -s $self->{fh}; + if ( $size == 0 ) { + return 0; + } + elsif ( $size > 100_000 ) { + return 1; + } -=item B<--[no]column> + 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 ); -Show the column number of the first match. This is helpful for -editors that can place your cursor at a given position. + my $regex = $opt->{regex}; + return $buffer =~ /$regex/m; +} -=item B<--create-ackrc> -Dumps the default ack options to standard output. This is useful for -when you want to customize the defaults. +sub reset { + my $self = shift; -=item B<--dump> + # return if we haven't opened the file yet + if ( !defined($self->{fh}) ) { + return; + } -Writes the list of options loaded and where they came from to standard -output. Handy for debugging. + if( !seek( $self->{fh}, 0, 0 ) && $App::Ack::report_bad_filenames ) { + App::Ack::warn( "$self->{filename}: $!" ); + } -=item B<--[no]env> + return; +} -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. -=item B<--flush> +sub close { + my $self = shift; -B<--flush> flushes output immediately. This is off by default -unless ack is running interactively (when output goes to a pipe or -file). + # return if we haven't opened the file yet + if ( !defined($self->{fh}) ) { + return; + } -=item B<-f> + if ( !close($self->{fh}) && $App::Ack::report_bad_filenames ) { + App::Ack::warn( $self->name() . ": $!" ); + } -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. + $self->{opened} = 0; -=item B<--files-from=I> + return; +} -The list of files to be searched is specified in I. The list of -files are seperated by newlines. If I is C<->, the list is loaded -from standard input. -=item B<--[no]filter> +sub clone { + my ( $self ) = @_; -Forces ack to act as if it were recieving input via a pipe. + return __PACKAGE__->new($self->name); +} -=item B<--[no]follow> +sub firstliney { + my ( $self ) = @_; -Follow or don't follow symlinks, other than whatever starting files -or directories were specified on the command line. + my $fh = $self->open(); -This is off by default. + 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; + } -=item B<-g I> + $self->close; -Print files where the relative path + filename matches I. + return $self->{firstliney}; +} -=item B<--[no]group> +sub open { + my ( $self ) = @_; -B<--group> groups matches by file name. This is the default -when used interactively. + return $self->{fh} if $self->{opened}; -B<--nogroup> prints one result per line, like grep. This is the -default when output is redirected. + unless ( open $self->{fh}, '<', $self->{filename} ) { + return; + } -=item B<-H>, B<--with-filename> + $self->{opened} = 1; -Print the filename for each match. This is the default unless searching -a single explicitly specified file. + return $self->{fh}; +} -=item B<-h>, B<--no-filename> +1; +package App::Ack::ConfigDefault; -Suppress the prefixing of filenames on output when multiple files are -searched. +use warnings; +use strict; -=item B<--[no]heading> +sub options { + my @options = split( /\n/, _options_block() ); + @options = grep { /./ && !/^#/ } @options; -Print a filename heading above each file's results. This is the default -when used interactively. + return @options; +} -=item B<--help>, B<-?> +sub _options_block { + return <<'HERE'; +# This is the default ackrc for ack 2.0 -Print a short help statement. +# 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. -=item B<--help-types>, B<--help=types> -Print all known types. +### Directories to ignore -=item B<-i>, B<--ignore-case> +# Bazaar +--ignore-directory=is:.bzr -Ignore case distinctions in PATTERN +# Codeville +--ignore-directory=is:.cdv -=item B<--ignore-ack-defaults> +# Interface Builder +--ignore-directory=is:~.dep +--ignore-directory=is:~.dot +--ignore-directory=is:~.nib +--ignore-directory=is:~.plst -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. +# Git +--ignore-directory=is:.git -=item B<--[no]ignore-dir=I>, B<--[no]ignore-directory=I> +# Mercurial +--ignore-directory=is:.hg -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). +# quilt +--ignore-directory=is:.pc -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. +# Subversion +--ignore-directory=is:.svn -=item B<--ignore-file=I> +# Monotone +--ignore-directory=is:_MTN -Ignore files matching I. The filters are specified -identically to file type filters as seen in L. +# CVS +--ignore-directory=is:CVS -=item B<-k>, B<--known-types> +# RCS +--ignore-directory=is:RCS -Limit selected files to those with types that ack knows about. This is -equivalent to the default behavior found in ack 1. +# SCCS +--ignore-directory=is:SCCS -=item B<--lines=I> +# darcs +--ignore-directory=is:_darcs -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. +# Vault/Fortress +--ignore-directory=is:_sgbak -=item B<-l>, B<--files-with-matches> +# autoconf +--ignore-directory=is:autom4te.cache -Only print the filenames of matching files, instead of the matching text. +# Perl module building +--ignore-directory=is:blib +--ignore-directory=is:_build -=item B<-L>, B<--files-without-matches> +# Perl Devel::Cover module's output directory +--ignore-directory=is:cover_db -Only print the filenames of files that do I match. +# Node modules created by npm +--ignore-directory=is:node_modules -=item B<--match I> +# CMake cache +--ignore-directory=is:CMakeFiles -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. +### Files to ignore - # search for foo and bar in given files - ack file1 t/file* --match foo - ack file1 t/file* --match bar +# Backup files +--ignore-file=ext:bak +--ignore-file=match:/~$/ -=item B<-m=I>, B<--max-count=I> +# Emacs swap files +--ignore-file=match:/^#.+#$/ -Stop reading a file after I matches. +# vi/vim swap files +--ignore-file=match:/[._].*\.swp$/ -=item B<--man> +# core dumps +--ignore-file=match:/core\.\d+$/ -Print this manual page. +# minified Javascript +--ignore-file=match:/[.-]min[.]js$/ +--ignore-file=match:/[.]js[.]min$/ -=item B<-n>, B<--no-recurse> +# minified CSS +--ignore-file=match:/[.]min[.]css$/ +--ignore-file=match:/[.]css[.]min$/ -No descending into subdirectories. +# PDFs, because they pass Perl's -T detection +--ignore-file=ext:pdf -=item B<-o> +# Common graphics, just as an optimization +--ignore-file=ext:gif,jpg,jpeg,png -Show only the part of each line matching PATTERN (turns off text -highlighting) -=item B<--output=I> +### Filetypes defined -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">. +# Perl http://perl.org/ +--type-add=perl:ext:pl,pm,pod,t,psgi +--type-add=perl:firstlinematch:/^#!.*\bperl/ -=item B<--pager=I>, B<--nopager> +# Perl tests +--type-add=perltest:ext:t -B<--pager> directs ack's output through I. This can also be specified -via the C and C environment variables. +# 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 -Using --pager does not suppress grouping and coloring like piping -output on the command-line does. +# Rakefiles http://rake.rubyforge.org/ +--type-add=rake:is:Rakefile -B<--nopager> cancels any setting in ~/.ackrc, C or C. -No output will be sent through a pager. +# CMake http://www.cmake.org/ +--type-add=cmake:is:CMakeLists.txt +--type-add=cmake:ext:cmake -=item B<--passthru> +# Actionscript +--type-add=actionscript:ext:as,mxml -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: +# Ada http://www.adaic.org/ +--type-add=ada:ext:ada,adb,ads - # Watch a log file, and highlight a certain IP address - $ tail -f ~/access.log | ack --passthru 123.45.67.89 +# ASP http://msdn.microsoft.com/en-us/library/aa286483.aspx +--type-add=asp:ext:asp -=item B<--print0> +# ASP.Net http://www.asp.net/ +--type-add=aspx:ext:master,ascx,asmx,aspx,svc -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. +# Assembly +--type-add=asm:ext:asm,s - # remove all files of type html - ack -f --html --print0 | xargs -0 rm -f +# Batch +--type-add=batch:ext:bat,cmd -=item B<-Q>, B<--literal> +# ColdFusion http://en.wikipedia.org/wiki/ColdFusion +--type-add=cfmx:ext:cfc,cfm,cfml -Quote all metacharacters in PATTERN, it is treated as a literal. +# Clojure http://clojure.org/ +--type-add=clojure:ext:clj -=item B<-r>, B<-R>, B<--recurse> +# C +# .xs are Perl C files +--type-add=cc:ext:c,h,xs -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. +# C header files +--type-add=hh:ext:h -=item B<-s> +# CoffeeScript http://coffeescript.org/ +--type-add=coffeescript:ext:coffee -Suppress error messages about nonexistent or unreadable files. This is taken -from fgrep. +# C++ +--type-add=cpp:ext:cpp,cc,cxx,m,hpp,hh,h,hxx -=item B<--[no]smart-case>, B<--no-smart-case> +# C# +--type-add=csharp:ext:cs -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. +# CSS http://www.w3.org/Style/CSS/ +--type-add=css:ext:css -B<-i> always overrides this option. +# Dart http://www.dartlang.org/ +--type-add=dart:ext:dart -=item B<--sort-files> +# Delphi http://en.wikipedia.org/wiki/Embarcadero_Delphi +--type-add=delphi:ext:pas,int,dfm,nfm,dof,dpk,dproj,groupproj,bdsgroup,bdsproj -Sorts the found files lexicographically. Use this if you want your file -listings to be deterministic between runs of I. +# Elixir http://elixir-lang.org/ +--type-add=elixir:ext:ex,exs -=item B<--show-types> +# Emacs Lisp http://www.gnu.org/software/emacs +--type-add=elisp:ext:el -Outputs the filetypes that ack associates with each file. +# Erlang http://www.erlang.org/ +--type-add=erlang:ext:erl,hrl -Works with B<-f> and B<-g> options. +# Fortran http://en.wikipedia.org/wiki/Fortran +--type-add=fortran:ext:f,f77,f90,f95,f03,for,ftn,fpp -=item B<--type=[no]TYPE> +# Google Go http://golang.org/ +--type-add=go:ext:go -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>. +# Groovy http://groovy.codehaus.org/ +--type-add=groovy:ext:groovy,gtmpl,gpp,grunit,gradle -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. +# Haskell http://www.haskell.org/ +--type-add=haskell:ext:hs,lhs -Type specifications can be repeated and are ORed together. +# HTML +--type-add=html:ext:htm,html -See I for a list of valid types. +# Java http://www.oracle.com/technetwork/java/index.html +--type-add=java:ext:java,properties -=item B<--type-add I:I:I> +# JavaScript +--type-add=js:ext:js -Files with the given FILTERARGS applied to the given FILTER -are recognized as being of (the existing) type TYPE. -See also L. +# JSP http://www.oracle.com/technetwork/java/javaee/jsp/index.html +--type-add=jsp:ext:jsp,jspx,jhtm,jhtml +# JSON http://www.json.org/ +--type-add=json:ext:json -=item B<--type-set I:I:I> +# Less http://www.lesscss.org/ +--type-add=less:ext:less -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. +# Common Lisp http://common-lisp.net/ +--type-add=lisp:ext:lisp,lsp -=item B<--type-del I> +# Lua http://www.lua.org/ +--type-add=lua:ext:lua +--type-add=lua:firstlinematch:/^#!.*\blua(jit)?/ -The filters associated with TYPE are removed from Ack, and are no longer considered -for searches. +# Objective-C +--type-add=objc:ext:m,h -=item B<-v>, B<--invert-match> +# Objective-C++ +--type-add=objcpp:ext:mm,h -Invert match: select non-matching lines +# OCaml http://caml.inria.fr/ +--type-add=ocaml:ext:ml,mli -=item B<--version> +# Matlab http://en.wikipedia.org/wiki/MATLAB +--type-add=matlab:ext:m -Display version and copyright information. +# Parrot http://www.parrot.org/ +--type-add=parrot:ext:pir,pasm,pmc,ops,pod,pg,tg -=item B<-w>, B<--word-regexp> +# PHP http://www.php.net/ +--type-add=php:ext:php,phpt,php3,php4,php5,phtml +--type-add=php:firstlinematch:/^#!.*\bphp/ -Force PATTERN to match only whole words. The PATTERN is wrapped with -C<\b> metacharacters. +# Plone http://plone.org/ +--type-add=plone:ext:pt,cpt,metadata,cpy,py -=item B<-x> +# Python http://www.python.org/ +--type-add=python:ext:py +--type-add=python:firstlinematch:/^#!.*\bpython/ -An abbreviation for B<--files-from=->; the list of files to search are read -from standard input, with one line per file. +# R http://www.r-project.org/ +--type-add=rr:ext:R -=item B<-1> +# 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/ -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. +# Rust http://www.rust-lang.org/ +--type-add=rust:ext:rs -=item B<--thpppt> +# Sass http://sass-lang.com +--type-add=sass:ext:sass,scss -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. +# Scala http://www.scala-lang.org/ +--type-add=scala:ext:scala -=item B<--bar> +# Scheme http://groups.csail.mit.edu/mac/projects/scheme/ +--type-add=scheme:ext:scm,ss -Check with the admiral for traps. +# 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/ -=back +# Smalltalk http://www.smalltalk.org/ +--type-add=smalltalk:ext:st -=head1 THE .ackrc FILE +# SQL http://www.iso.org/iso/catalogue_detail.htm?csnumber=45498 +--type-add=sql:ext:sql,ctl -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: +# Tcl http://www.tcl.tk/ +--type-add=tcl:ext:tcl,itcl,itk - # Always sort the files - --sort-files +# LaTeX http://www.latex-project.org/ +--type-add=tex:ext:tex,cls,sty - # Always color, even if piping to a another program - --color +# Template Toolkit http://template-toolkit.org/ +--type-add=tt:ext:tt,tt2,ttml - # Use "less -r" as my pager - --pager=less -r +# Visual Basic +--type-add=vb:ext:bas,cls,frm,ctl,vb,resx -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>. +# Verilog +--type-add=verilog:ext:v,vh,sv -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. +# VHDL http://www.eda.org/twiki/bin/view.cgi/P1076/WebHome +--type-add=vhdl:ext:vhd,vhdl -=head1 Defining your own types +# Vim http://www.vim.org/ +--type-add=vim:ext:vim -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. +# XML http://www.w3.org/TR/REC-xml/ +--type-add=xml:ext:xml,dtd,xsl,xslt,ent +--type-add=xml:firstlinematch:/<[?]xml/ -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. +# YAML http://yaml.org/ +--type-add=yaml:ext:yaml,yml +HERE +} -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. +1; +package App::Ack::ConfigFinder; -When defining your own types in the F<.ackrc> file you have to use -the following: - --type-set=eiffel:ext:e,eiffel +use strict; +use warnings; -or writing on separate lines +use Cwd 3.00 (); +use File::Spec 3.00; - --type-set - eiffel:ext:e,eiffel +use if ($^O eq 'MSWin32'), 'Win32'; -The following does B work in the F<.ackrc> file: - --type-set eiffel:ext:e,eiffel +sub new { + my ( $class ) = @_; + return bless {}, $class; +} -In order to see all currently defined types, use I<--help-types>, e.g. -I +sub _remove_redundancies { + my ( @configs ) = @_; -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. + if ( $App::Ack::is_windows ) { + # inode stat always returns 0 on windows, so just check filenames. + my (%seen, @uniq); -=over 4 + foreach my $path (@configs) { + push @uniq, $path unless $seen{$path}; + $seen{$path} = 1; + } -=item is:I + return @uniq; + } -I filters match the target filename exactly. It takes exactly one -argument, which is the name of the file to match. + else { -Example: + my %dev_and_inode_seen; - --type-set make:is:Makefile + foreach my $path ( @configs ) { + my ( $dev, $inode ) = (stat $path)[0, 1]; -=item ext:I[,I[,...]] + if( defined($dev) ) { + if( $dev_and_inode_seen{"$dev:$inode"} ) { + undef $path; + } + else { + $dev_and_inode_seen{"$dev:$inode"} = 1; + } + } + } -I filters match the extension of the target file against a list -of extensions. No leading dot is needed for the extensions. + return grep { defined() } @configs; -Example: + } +} - --type-set perl:ext:pl,pm,t +sub _check_for_ackrc { + return unless defined $_[0]; -=item match:I + my @files = grep { -f } + map { File::Spec->catfile(@_, $_) } + qw(.ackrc _ackrc); -I filters match the target filename against a regular expression. -The regular expression is made case insensitive for the search. + die File::Spec->catdir(@_) . " contains both .ackrc and _ackrc.\n" . + "Please remove one of those files.\n" + if @files > 1; -Example: + return wantarray ? @files : $files[0]; +} # end _check_for_ackrc - --type-set make:match:/(gnu)?makefile/ -=item firstlinematch:I +sub find_config_files { + my @config_files; -I matches the first line of the target file against a -regular expression. Like I, the regular expression is made -case insensitive. + 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'; + } -Example: - --type-add perl:firstlinematch:/perl/ + if ( $ENV{'ACKRC'} && -f $ENV{'ACKRC'} ) { + push @config_files, $ENV{'ACKRC'}; + } + else { + push @config_files, _check_for_ackrc($ENV{'HOME'}); + } -=back + 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; + } -More filter types may be made available in the future. + # 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 ); +} -=head1 ENVIRONMENT VARIABLES -For commonly-used ack options, environment variables can make life -much easier. These variables are ignored if B<--noenv> is specified -on the command line. +sub read_rcfile { + my $file = shift; -=over 4 + return unless defined $file && -e $file; -=item ACKRC + my @lines; -Specifies the location of the user's F<.ackrc> file. If this file doesn't -exist, F looks in the default location. + 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 ACK_OPTIONS + next if $line eq ''; + next if $line =~ /^#/; -This variable specifies default options to be placed in front of -any explicit options on the command line. + push( @lines, $line ); + } + close $fh; -=item ACK_COLOR_FILENAME + return @lines; +} -Specifies the color of the filename when it's printed in B<--group> -mode. By default, it's "bold green". +1; +package App::Ack::ConfigLoader; -The recognized attributes are clear, reset, dark, bold, underline, -underscore, blink, reverse, concealed black, red, green, yellow, -blue, magenta, on_black, on_red, on_green, on_yellow, on_blue, -on_magenta, on_cyan, and on_white. Case is not significant. -Underline and underscore are equivalent, as are clear and reset. -The color alone sets the foreground color, and on_color sets the -background color. - -This option can also be set with B<--color-filename>. - -=item ACK_COLOR_MATCH - -Specifies the color of the matching text when printed in B<--color> -mode. By default, it's "black on_yellow". - -This option can also be set with B<--color-match>. - -See B for the color specifications. +use strict; +use warnings; -=item ACK_COLOR_LINENO +use Carp 1.04 (); +use Getopt::Long 2.35 (); +use Text::ParseWords 3.1 (); -Specifies the color of the line number when printed in B<--color> -mode. By default, it's "bold yellow". -This option can also be set with B<--color-lineno>. +my @INVALID_COMBINATIONS; -See B for the color specifications. +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 ); -=item ACK_PAGER + @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], + ); +} -Specifies a pager program, such as C, C or C, to which -ack will send its output. +sub process_filter_spec { + my ( $spec ) = @_; -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. + if ( $spec =~ /^(\w+):(\w+):(.*)/ ) { + my ( $type_name, $ext_type, $arguments ) = ( $1, $2, $3 ); -C overrides C if both are specified. + 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 ); -=item ACK_PAGER_COLOR + my @extensions = split(/,/, $extensions); + foreach my $extension ( @extensions ) { + $extension =~ s/^[.]//; + } -Specifies a pager program that understands ANSI color sequences. -Using C does not suppress grouping and coloring -like piping output on the command-line does. + return ( $type_name, App::Ack::Filter->create_filter('ext', @extensions) ); + } + else { + Carp::croak "invalid filter specification '$spec'"; + } +} -If you are not on Windows, you never need to use C. +sub uninvert_filter { + my ( $opt, @filters ) = @_; -=back + return unless defined $opt->{filters} && @filters; -=head1 ACK & OTHER TOOLS + # 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 Vim integration + # 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--; + } + } +} -F integrates easily with the Vim text editor. Set this in your -F<.vimrc> to use F instead of F: +sub process_filetypes { + my ( $opt, $arg_sources ) = @_; - set grepprg=ack\ -k + 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; -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 $add_spec = sub { + my ( undef, $spec ) = @_; - :grep Dumper perllib + my ( $name, $filter ) = process_filter_spec($spec); -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. + push @{ $App::Ack::mappings{$name} }, $filter; -L + $additional_specs{$name . '!'} = sub { + my ( undef, $value ) = @_; -=head2 Emacs integration + my @filters = @{ $App::Ack::mappings{$name} }; + if ( not $value ) { + @filters = map { $_->invert() } @filters; + } + else { + uninvert_filter( $opt, @filters ); + } -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." + push @{ $opt->{'filters'} }, @filters; + }; + }; -L + my $set_spec = sub { + my ( undef, $spec ) = @_; -=head2 TextMate integration + my ( $name, $filter ) = process_filter_spec($spec); -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" + $App::Ack::mappings{$name} = [ $filter ]; -=head2 Shell and Return Code + $additional_specs{$name . '!'} = sub { + my ( undef, $value ) = @_; -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 @filters = @{ $App::Ack::mappings{$name} }; + if ( not $value ) { + @filters = map { $_->invert() } @filters; + } -(Shell exit code 1 is C<$?=256> in perl with C or backticks.) + push @{ $opt->{'filters'} }, @filters; + }; + }; -The I code 2 for errors is not used. + my $delete_spec = sub { + my ( undef, $name ) = @_; -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. + delete $App::Ack::mappings{$name}; + delete $additional_specs{$name . '!'}; + }; -=cut + my %type_arg_specs = ( + 'type-add=s' => $add_spec, + 'type-set=s' => $set_spec, + 'type-del=s' => $delete_spec, + ); -=head1 DEBUGGING ACK PROBLEMS + for ( my $i = 0; $i < @{$arg_sources}; $i += 2) { + my ( $source_name, $args ) = @{$arg_sources}[ $i, $i + 1]; -If ack gives you output you're not expecting, start with a few simple steps. + 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); + } + } -=head2 Use B<--noenv> + $additional_specs{'k|known-types'} = sub { + my ( undef, $value ) = @_; -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 @filters = map { @{$_} } values(%App::Ack::mappings); -=head2 Use B<-f> to see what files have been selected + push @{ $opt->{'filters'} }, @filters; + }; -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. + return \%additional_specs; +} -=head2 Use B<--dump> +sub removed_option { + my ( $option, $explanation ) = @_; -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. + $explanation ||= ''; + return sub { + warn "Option '$option' is not valid in ack 2\n$explanation"; + exit 1; + }; +} -=head1 TIPS +sub get_arg_spec { + my ( $opt, $extra_specs ) = @_; -=head2 Use the F<.ackrc> file. + my $dash_a_explanation = < 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. + 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 ) = @_; -=head2 Use F<-f> for working with big codesets + 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 ) = @_; -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: + $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 ) = @_; - # Change all "this" to "that" in all Perl files in a tree. - ack -f --perl | xargs perl -p -i -e's/this/that/g' + $opt->{pager} = $value || $ENV{PAGER}; + }, + 'noignore-directory|noignore-dir=s' + => sub { + my ( undef, $dir ) = @_; -or if you prefer: + # 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'"); + } - perl -p -i -e's/this/that/g' $(ack -f --perl) + @{ $opt->{idirs} } = grep { + $_ ne $dir + } @{ $opt->{idirs} }; -=head2 Use F<-Q> when in doubt about metacharacters + 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 ) = @_; -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 $cb_value = 1; + if ( $value =~ s/^no// ) { + $cb_value = 0; + } -=head2 Use ack to watch log files + my $callback = $extra_specs->{ $value . '!' }; -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. + 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} = '-' }, - ack -Q aa.bb.cc.dd /path/to/access.log | ack -Q -B5 troublesome.gif + '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 +} -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. +sub process_other { + my ( $opt, $extra_specs, $arg_sources ) = @_; -=head2 Examples of F<--output> + 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', + ); -Following variables are useful in the expansion string: + my $argv_source; + my $is_help_types_active; -=over 4 + for ( my $i = 0; $i < @{$arg_sources}; $i += 2 ) { + my ( $source_name, $args ) = @{$arg_sources}[ $i, $i + 1 ]; -=item C<$&> + if ( $source_name eq 'ARGV' ) { + $argv_source = $args; + last; + } + } -The whole string matched by PATTERN. + if ( $argv_source ) { # This *should* always be true, but you never know... + my @copy = @{$argv_source}; + local @ARGV = @copy; -=item C<$1>, C<$2>, ... + Getopt::Long::Configure('pass_through'); -The contents of the 1st, 2nd ... bracketed group in PATTERN. + Getopt::Long::GetOptions( + 'help-types' => \$is_help_types_active, + ); -=item C<$`> + Getopt::Long::Configure('no_pass_through'); + } -The string before the match. + my $arg_specs = get_arg_spec($opt, $extra_specs); -=item C<$'> + for ( my $i = 0; $i < @{$arg_sources}; $i += 2) { + my ($source_name, $args) = @{$arg_sources}[$i, $i + 1]; -The string after the match. + 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" ); + } + } -=back + # XXX We need to check on a -- in the middle of a non-ARGV source -For more details and other variables see -L. + return; +} -This example shows how to add text around a particular pattern -(in this case adding _ around word with "e") +sub should_dump_options { + my ( $sources ) = @_; - 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 + 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; +} -This shows how to pick out particular parts of a match using ( ) within regular expression. +sub explode_sources { + my ( $sources ) = @_; - ack '=head(\d+)\s+(.*)' --output=' $1 : $2' - input file contains "=head1 NAME" - output "1 : NAME" + my @new_sources; -=head2 Share your knowledge + Getopt::Long::Configure('default', 'pass_through', 'no_auto_help', 'no_auto_version'); -Join the ack-users mailing list. Send me your tips and I may add -them here. + my %opt; + my $arg_spec = get_arg_spec(\%opt); -=head1 FAQ + my $add_type = sub { + my ( undef, $arg ) = @_; -=head2 Why isn't ack finding a match in (some file)? + # XXX refactor? + if ( $arg =~ /(\w+)=/) { + $arg_spec->{$1} = sub {}; + } + else { + ( $arg ) = split /:/, $arg; + $arg_spec->{$arg} = sub {}; + } + }; -Probably because it's of a type that ack doesn't recognize. ack's -searching behavior is driven by filetype. B + my $del_type = sub { + my ( undef, $arg ) = @_; -Use the C<-f> switch to see a list of files that ack will search -for you. + delete $arg_spec->{$arg}; + }; -If you want ack to search files that it doesn't recognize, use the -C<-a> switch. + 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--; -If you want ack to search every file, even ones that it always -ignores like coredumps and backup files, use the C<-u> switch. + 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} ); -=head2 Why does ack ignore unknown files by default? + push @new_sources, $name, \@copy; + } + } -ack is designed by a programmer, for programmers, for searching -large trees of code. Most codebases have a lot files in them which -aren't source files (like compiled object files, source control -metadata, etc), and grep wastes a lot of time searching through all -of those as well and returning matches from those files. + return \@new_sources; +} -That's why ack's behavior of not searching things it doesn't recognize -is one of its greatest strengths: the speed you get from only -searching the things that you want to be looking at. +sub compare_opts { + my ( $a, $b ) = @_; -=head2 Wouldn't it be great if F did search & replace? + my $first_a = $a->[0]; + my $first_b = $b->[0]; -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. + $first_a =~ s/^--?//; + $first_b =~ s/^--?//; -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 $first_a cmp $first_b; +} - $ perl -i -p -e's/foo/bar/g' $(ack -f --php) +sub dump_options { + my ( $sources ) = @_; -=head2 Can you make ack recognize F<.xyz> files? + $sources = explode_sources($sources); -Yes! Please see L. If you think -that F should recognize a type by default, please see -L. + my %opts_by_source; + my @source_names; -=head2 There's already a program/package called ack. + 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; + } -Yes, I know. + foreach my $name (@source_names) { + my $contents = $opts_by_source{$name}; -=head2 Why is it called ack if it's called ack-grep? + print $name, "\n"; + print '=' x length($name), "\n"; + print ' ', join(' ', @{$_}), "\n" foreach sort { compare_opts($a, $b) } @{$contents}; + } -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. + return; +} -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. +sub remove_default_options_if_needed { + my ( $sources ) = @_; -To do that, run this with F or as root: + my $default_index; - ln -s /usr/bin/ack-grep /usr/bin/ack + foreach my $index ( 0 .. $#$sources ) { + if ( $sources->[$index] eq 'Defaults' ) { + $default_index = $index; + last; + } + } -Alternatively, you could use a shell alias: + return $sources unless defined $default_index; - # bash/zsh - alias ack=ack-grep + my $should_remove = 0; - # csh - alias ack ack-grep + # 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', + ); -=head2 What does F mean? + foreach my $index ( $default_index + 2 .. $#$sources ) { + next if $index % 2 != 0; -Nothing. I wanted a name that was easy to type and that you could -pronounce as a single syllable. + my ( $name, $args ) = @{$sources}[ $index, $index + 1 ]; -=head2 Can I do multi-line regexes? + 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, + ); + } + } -No, ack does not support regexes that match multiple lines. Doing -so would require reading in the entire file at a time. + Getopt::Long::Configure('default'); + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); -If you want to see lines near your match, use the C<--A>, C<--B> -and C<--C> switches for displaying context. + return $sources unless $should_remove; -=head2 Why is ack telling me I have an invalid option when searching for C<+foo>? + my @copy = @{$sources}; + splice @copy, $default_index, 2; + return \@copy; +} -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 check_for_mutually_exclusive_options { + my ( $arg_sources ) = @_; -=head1 ACKRC LOCATION SEMANTICS + my %mutually_exclusive_with; + my @copy = @{$arg_sources}; -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) + for(my $i = 0; $i < @INVALID_COMBINATIONS; $i += 2) { + my ( $lhs, $rhs ) = @INVALID_COMBINATIONS[ $i, $i + 1 ]; -=over 4 + 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; -Defaults are loaded from App::Ack::ConfigDefaults. This can be omitted -using C<--ignore-ack-defaults>. + my ( $source_name, $args ) = splice @copy, 0, 2; + $args = ref($args) ? [ @{$args} ] : [ Text::ParseWords::shellwords($args) ]; -=item * Global ackrc + foreach my $opt ( @{$args} ) { + next unless $opt =~ /^[-+]/; + last if $opt eq '--'; -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>. + if( $opt =~ /^(.*)=/ ) { + $opt = $1; + } + elsif ( $opt =~ /^(-[^-]).+/ ) { + $opt = $1; + } -=item * User ackrc + $set_opts{ $opt } = 1; -Options are then loaded from the user's ackrc. This is located at -C<$HOME/.ackrc> on Unix-like systems, and -C. If a different -ackrc is desired, it may be overriden with the C<$ACKRC> environment -variable. -This can be omitted using C<--noenv>. + my $mutex_opts = $mutually_exclusive_with{ $opt }; -=item * Project ackrc + next unless $mutex_opts; -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>. + foreach my $mutex_opt ( @{$mutex_opts} ) { + if($set_opts{ $mutex_opt }) { + die "Options '$mutex_opt' and '$opt' are mutually exclusive\n"; + } + } + } + } +} -=item * ACK_OPTIONS +sub process_args { + my $arg_sources = \@_; -Options are then loaded from the enviroment variable C. This can -be omitted using C<--noenv>. + my %opt = ( + pager => $ENV{ACK_PAGER_COLOR} || $ENV{ACK_PAGER}, + ); -=item * Command line + check_for_mutually_exclusive_options($arg_sources); -Options are then loaded from the command line. + $arg_sources = remove_default_options_if_needed($arg_sources); -=back + if ( should_dump_options($arg_sources) ) { + dump_options($arg_sources); + exit(0); + } -=head1 DIFFERENCES BETWEEN ACK 1.X AND ACK 2.X + 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 ); -A lot of changes were made for ack 2; here is a list of them. + # 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} ||= []); -=head2 GENERAL CHANGES + # 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; +} -=over 4 -=item * +sub retrieve_arg_sources { + my @arg_sources; -When no selectors are specified, ack 1.x only searches through files that -it can map to a file type. ack 2.x, by constrast, will search through -every regular, non-binary file that is not explicitly ignored via -B<--ignore-file> or B<--ignore-dir>. This is similar to the behavior of the -B<-a/--all> option in ack 1.x. + my $noenv; + my $ackrc; -=item * + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); + Getopt::Long::Configure('pass_through'); + Getopt::Long::Configure('no_auto_abbrev'); -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. + Getopt::Long::GetOptions( + 'noenv' => \$noenv, + 'ackrc=s' => \$ackrc, + ); -=item * + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); -ack now loads multiple ackrc files; see L for -details. + my @files; -=item * + 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 ); + } -ack's default filter definitions aren't special; you may tell ack to -completely disregard them if you don't like them. + push @arg_sources, Defaults => [ App::Ack::ConfigDefault::options() ]; -=back + foreach my $file ( @files) { + my @lines = App::Ack::ConfigFinder::read_rcfile($file); + push ( @arg_sources, $file, \@lines ) if @lines; + } -=head2 REMOVED OPTIONS + if ( $ENV{ACK_OPTIONS} && !$noenv ) { + push( @arg_sources, 'ACK_OPTIONS' => $ENV{ACK_OPTIONS} ); + } -=over 4 + push( @arg_sources, 'ARGV' => [ @ARGV ] ); -=item * + return @arg_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. +1; # End of App::Ack::ConfigLoader +package App::Ack::Filter; -=item * +use strict; +use warnings; +use overload + '""' => 'to_string'; -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. +use Carp 1.04 (); -=item * +my %filter_types; -The B<--binary> option has been removed. -=item * +sub create_filter { + my ( undef, $type, @args ) = @_; -The B<--skipped> option has been removed. + if ( my $package = $filter_types{$type} ) { + return $package->new(@args); + } + Carp::croak "Unknown filter type '$type'"; +} -=item * -The B<--text> option has been removed. +sub register_filter { + my ( undef, $type, $package ) = @_; -=item * + $filter_types{$type} = $package; -The B<--invert-file-match> option has been removed. Instead, you may -use B<-v> with B<-g>. + return; +} -=back -=head2 CHANGED OPTIONS +sub invert { + my ( $self ) = @_; -=over 4 + return App::Ack::Filter::Inverse->new( $self ); +} -=item * -The options that modify the regular expression's behavior (B<-i>, B<-w>, -B<-Q>, and B<-v>) may now be used with B<-g>. +sub is_inverted { + return 0; +} -=back -=head2 ADDED OPTIONS +sub to_string { + my ( $self ) = @_; -=over 4 + return '(unimplemented to_string)'; +} -=item * -B<--files-from> was added so that a user may submit a list of filenames as -a list of files to search. +sub inspect { + my ( $self ) = @_; -=item * + return ref($self); +} -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. +1; +package App::Ack::Filter::Extension; -=item * +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} -B<-s> was added to tell ack to suppress error messages about non-existent or -unreadable files. -=item * +sub new { + my ( $class, @extensions ) = @_; -B<--ignore-directory> and B<--noignore-directory> were added as aliases for -B<--ignore-dir> and B<--noignore-dir> respectively. + my $exts = join('|', map { "\Q$_\E"} @extensions); + my $re = qr/[.](?:$exts)$/i; -=item * + return bless { + extensions => \@extensions, + regex => $re, + groupname => 'ExtensionGroup', + }, $class; +} -B<--ignore-file> was added so that users may specify patterns of files to -ignore (ex. /.*~$/). +sub create_group { + return App::Ack::Filter::ExtensionGroup->new(); +} -=item * +sub filter { + my ( $self, $resource ) = @_; -B<--dump> was added to allow users to easily find out which options are -set where. + my $re = $self->{'regex'}; -=item * + return $resource->name =~ /$re/; +} -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 inspect { + my ( $self ) = @_; -=item * + my $re = $self->{'regex'}; -B<--type-del> was added to selectively remove file type definitions. + return ref($self) . " - $re"; +} -=item * +sub to_string { + my ( $self ) = @_; -B<--ignore-ack-defaults> was added so that users may ignore ack's default -options in favor of their own. + my $exts = $self->{'extensions'}; -=item * + return join(' ', map { ".$_" } @{$exts}); +} -B<--bar> was added so ack users may consult Admiral Ackbar. +BEGIN { + App::Ack::Filter->register_filter(ext => __PACKAGE__); +} -=back +1; +package App::Ack::Filter::FirstLineMatch; -=head1 AUTHOR +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} -Andy Lester, C<< >> +sub new { + my ( $class, $re ) = @_; -=head1 BUGS + $re =~ s{^/|/$}{}g; # XXX validate? + $re = qr{$re}i; -Please report any bugs or feature requests to the issues list at -Github: L + return bless { + regex => $re, + }, $class; +} -=head1 ENHANCEMENTS +# 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. -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. +sub filter { + my ( $self, $resource ) = @_; -There is a list of enhancements I want to make to F in the ack -issues list at Github: L + my $re = $self->{'regex'}; -Patches are always welcome, but patches with tests get the most -attention. + my $line = $resource->firstliney; -=head1 SUPPORT + return $line =~ /$re/; +} -Support for and information about F can be found at: +sub inspect { + my ( $self ) = @_; -=over 4 + my $re = $self->{'regex'}; -=item * The ack homepage + return ref($self) . " - $re"; +} -L +sub to_string { + my ( $self ) = @_; -=item * The ack-users mailing list + (my $re = $self->{regex}) =~ s{\([^:]*:(.*)\)$}{$1}; -L + return "first line matches /$re/"; +} -=item * The ack issues list at Github +BEGIN { + App::Ack::Filter->register_filter(firstlinematch => __PACKAGE__); +} -L +1; +package App::Ack::Filter::Is; -=item * AnnoCPAN: Annotated CPAN documentation +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} -L +use File::Spec 3.00 (); -=item * CPAN Ratings +sub new { + my ( $class, $filename ) = @_; -L + return bless { + filename => $filename, + groupname => 'IsGroup', + }, $class; +} -=item * Search CPAN +sub create_group { + return App::Ack::Filter::IsGroup->new(); +} -L +sub filter { + my ( $self, $resource ) = @_; -=item * Git source repository + my $filename = $self->{'filename'}; + my $base = (File::Spec->splitpath($resource->name))[2]; -L + return $base eq $filename; +} -=back +sub inspect { + my ( $self ) = @_; -=head1 ACKNOWLEDGEMENTS + my $filename = $self->{'filename'}; -How appropriate to have Inowledgements! + return ref($self) . " - $filename"; +} -Thanks to everyone who has contributed to ack in any way, including -Dale Sedivic, -Michael McClimon, -Andrew Black, -Ralph Bodenner, -Shaun Patterson, -Ryan Olson, -Shlomi Fish, -Karen Etheridge, -Olivier Mengue, -Matthew Wild, -Scott Kyle, -Nick Hooey, -Bo Borgerson, -Mark Szymanski, -Marq Schneider, -Packy Anderson, -JR Boyens, -Dan Sully, -Ryan Niebur, -Kent Fredric, -Mike Morearty, -Ingmar Vanhassel, -Eric Van Dewoestine, -Sitaram Chamarty, -Adam James, -Richard Carlsson, -Pedro Melo, -AJ Schuster, -Phil Jackson, -Michael Schwern, -Jan Dubois, -Christopher J. Madsen, -Matthew Wickline, -David Dyck, -Jason Porritt, -Jjgod Jiang, -Thomas Klausner, -Uri Guttman, -Peter Lewis, -Kevin Riggle, -Ori Avtalion, -Torsten Blix, -Nigel Metheringham, -GEbor SzabE, -Tod Hagan, -Michael Hendricks, -Evar ArnfjErE Bjarmason, -Piers Cawley, -Stephen Steneker, -Elias Lutfallah, -Mark Leighton Fisher, -Matt Diephouse, -Christian Jaeger, -Bill Sully, -Bill Ricker, -David Golden, -Nilson Santos F. Jr, -Elliot Shank, -Merijn Broeren, -Uwe Voelker, -Rick Scott, -Ask BjErn Hansen, -Jerry Gay, -Will Coleda, -Mike O'Regan, -Slaven ReziE<0x107>, -Mark Stosberg, -David Alan Pisoni, -Adriano Ferreira, -James Keenan, -Leland Johnson, -Ricardo Signes, -Pete Krawczyk and -Rob Hoelz. - -=head1 COPYRIGHT & LICENSE +sub to_string { + my ( $self ) = @_; -Copyright 2005-2013 Andy Lester. + my $filename = $self->{'filename'}; -This program is free software; you can redistribute it and/or modify -it under the terms of the Artistic License v2.0. + return $filename; +} -See http://www.perlfoundation.org/artistic_license_2_0 or the LICENSE.md -file that comes with the ack distribution. +BEGIN { + App::Ack::Filter->register_filter(is => __PACKAGE__); +} -=cut -package File::Next; +1; +package App::Ack::Filter::Match; use strict; use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} +use File::Spec 3.00; -our $VERSION = '1.12'; - +sub new { + my ( $class, $re ) = @_; + $re =~ s{^/|/$}{}g; # XXX validate? + $re = qr/$re/i; -use File::Spec (); + return bless { + regex => $re, + }, $class; +} -our $name; # name of the current file -our $dir; # dir of the current file +sub filter { + my ( $self, $resource ) = @_; -our %files_defaults; -our %skip_dirs; + my $re = $self->{'regex'}; + my $base = (File::Spec->splitpath($resource->name))[2]; -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); + return $base =~ /$re/; } +sub inspect { + my ( $self ) = @_; -sub files { - die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__); + my $re = $self->{'regex'}; - my ($parms,@queue) = _setup( \%files_defaults, @_ ); - my $filter = $parms->{file_filter}; + print ref($self) . " - $re"; +} - 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 +sub to_string { + my ( $self ) = @_; - return; - }; # iterator + my $re = $self->{'regex'}; + + return "filename matches $re"; +} + +BEGIN { + App::Ack::Filter->register_filter(match => __PACKAGE__); } +1; +package App::Ack::Filter::Default; + +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} +sub new { + my ( $class ) = @_; + return bless {}, $class; +} +sub filter { + my ( $self, $resource ) = @_; + return -T $resource->name; +} -sub from_file { - die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__); +1; +package App::Ack::Filter::Inverse; - my ($parms,@queue) = _setup( \%files_defaults, @_ ); - my $err = $parms->{error_handler}; - my $warn = $parms->{error_handler}; +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} - my $filename = $queue[1]; +sub new { + my ( $class, $filter ) = @_; - if ( !defined($filename) ) { - $err->( 'Must pass a filename to from_file()' ); - return undef; - } + return bless { + filter => $filter, + }, $class; +} - my $fh; - if ( $filename eq '-' ) { - $fh = \*STDIN; - } - else { - if ( !open( $fh, '<', $filename ) ) { - $err->( "Unable to open $filename: $!" ); - return undef; - } - } - my $filter = $parms->{file_filter}; +sub filter { + my ( $self, $resource ) = @_; - 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; - } + my $filter = $self->{'filter'}; + return !$filter->filter( $resource ); +} - 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; +sub invert { + my $self = shift; - return; - }; # iterator + return $self->{'filter'}; } -sub _bad_invocation { - my $good = (caller(1))[3]; - my $bad = $good; - $bad =~ s/(.+)::/$1->/; - return "$good must not be invoked as $bad"; +sub is_inverted { + return 1; } -sub sort_standard($$) { return $_[0]->[1] cmp $_[1]->[1] } -sub sort_reverse($$) { return $_[1]->[1] cmp $_[0]->[1] } +sub inspect { + my ( $self ) = @_; -sub reslash { - my $path = shift; + my $filter = $self->{'filter'}; - my @parts = split( /\//, $path ); + return "!$filter"; +} - return $path if @parts < 2; +1; +package App::Ack::Filter::Collection; - return File::Spec->catfile( @parts ); +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; } +use File::Spec 3.00 (); +sub new { + my ( $class ) = @_; -sub _setup { - my $defaults = shift; - my $passed_parms = ref $_[0] eq 'HASH' ? {%{+shift}} : {}; # copy parm hash + return bless { + groups => {}, + ungrouped => [], + }, $class; +} - my %passed_parms = %{$passed_parms}; +sub filter { + my ( $self, $resource ) = @_; - my $parms = {}; - for my $key ( keys %{$defaults} ) { - $parms->{$key} = - exists $passed_parms{$key} - ? delete $passed_parms{$key} - : $defaults->{$key}; + for my $group (values %{$self->{'groups'}}) { + if ($group->filter($resource)) { + return 1; + } } - # 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" ); + for my $filter (@{$self->{'ungrouped'}}) { + if ($filter->filter($resource)) { + return 1; + } } - # 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; + return 0; +} - for ( @_ ) { - my $start = reslash( $_ ); - if (-d $start) { - push @queue, ($start,undef,$start); +sub add { + my ( $self, $filter ) = @_; + + if (exists $filter->{'groupname'}) { + my $groups = $self->{'groups'}; + my $group_name = $filter->{'groupname'}; + + my $group; + if (exists $groups->{$group_name}) { + $group = $groups->{$group_name}; } else { - push @queue, (undef,$start,$start); + $group = $groups->{$group_name} = $filter->create_group(); } + + $group->add($filter); + } + else { + push @{$self->{'ungrouped'}}, $filter; } - return ($parms,@queue); + return; } +sub inspect { + my ( $self ) = @_; -sub _candidate_files { - my $parms = shift; - my $dirname = shift; + return ref($self) . " - $self"; +} - my $dh; - if ( !opendir $dh, $dirname ) { - $parms->{error_handler}->( "$dirname: $!" ); - return; - } +sub to_string { + my ( $self ) = @_; - my @newfiles; - my $descend_filter = $parms->{descend_filter}; - my $follow_symlinks = $parms->{follow_symlinks}; - my $sort_sub = $parms->{sort_files}; + my $ungrouped = $self->{'ungrouped'}; - for my $file ( grep { !exists $skip_dirs{$_} } readdir $dh ) { - my $has_stat; + return join(', ', map { "($_)" } @{$ungrouped}); +} - # 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; - } +1; +package App::Ack::Filter::IsGroup; - 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; +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} - if ( $sort_sub ) { - return map { @{$_} } sort $sort_sub @newfiles; - } +use File::Spec 3.00 (); - return @newfiles; +sub new { + my ( $class ) = @_; + + return bless { + data => {}, + }, $class; } +sub add { + my ( $self, $filter ) = @_; -1; # End of File::Next -package App::Ack; + $self->{data}->{ $filter->{filename} } = 1; +} -use warnings; -use strict; +sub filter { + my ( $self, $resource ) = @_; + my $data = $self->{'data'}; + my $base = (File::Spec->splitpath($resource->name))[2]; -our $VERSION; -our $GIT_REVISION; -our $COPYRIGHT; -BEGIN { - $VERSION = '2.04'; - $COPYRIGHT = 'Copyright 2005-2013 Andy Lester.'; - $GIT_REVISION = q{8f405b7}; + return exists $data->{$base}; } -our $fh; +sub inspect { + my ( $self ) = @_; -BEGIN { - $fh = *STDOUT; + return ref($self) . " - $self"; } +sub to_string { + my ( $self ) = @_; -our %types; -our %type_wanted; -our %mappings; -our %ignore_dirs; + return join(' ', keys %{$self->{data}}); +} -our $is_filter_mode; -our $output_to_pipe; +1; +package App::Ack::Filter::ExtensionGroup; -our $dir_sep_chars; -our $is_cygwin; -our $is_windows; +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} -use File::Spec 1.00015 (); +use File::Spec 3.00 (); -BEGIN { - # These have to be checked before any filehandle diddling. - $output_to_pipe = not -t *STDOUT; - $is_filter_mode = -p STDIN; +sub new { + my ( $class ) = @_; - $is_cygwin = ($^O eq 'cygwin'); - $is_windows = ($^O =~ /MSWin32/); - $dir_sep_chars = $is_windows ? quotemeta( '\\/' ) : quotemeta( File::Spec->catfile( '', '' ) ); + return bless { + data => {}, + }, $class; } +sub add { + my ( $self, $filter ) = @_; + my $data = $self->{'data'}; + my $extensions = $filter->{'extensions'}; -sub remove_dir_sep { - my $path = shift; - $path =~ s/[$dir_sep_chars]$//; - - return $path; + foreach my $ext (@{$extensions}) { + $data->{lc $ext} = 1; + } } +sub filter { + my ( $self, $resource ) = @_; + if ($resource->name =~ /[.]([^.]*)$/) { + return exists $self->{'data'}->{lc $1}; + } -sub warn { - return CORE::warn( _my_program(), ': ', @_, "\n" ); + return 0; } +sub inspect { + my ( $self ) = @_; -sub die { - return CORE::die( _my_program(), ': ', @_, "\n" ); + return ref($self) . " - $self"; } -sub _my_program { - require File::Basename; - return File::Basename::basename( $0 ); +sub to_string { + my ( $self ) = @_; + + my $data = $self->{'data'}; + + return join(' ', map { ".$_" } (keys %$data)); } +1; +package main; +use strict; +use warnings; -sub filetypes_supported { - return keys %mappings; -} +use 5.008008; -sub _get_thpppt { - my $y = q{_ /|,\\'!.x',=(www)=, U }; - $y =~ tr/,x!w/\nOo_/; - return $y; + +# XXX Don't make this so brute force +# See also: https://github.com/petdance/ack2/issues/89 + +use Getopt::Long 2.35 (); + +use Carp 1.04 (); + +our $VERSION = '2.10'; +# Check http://beyondgrep.com/ for updates + +# These are all our globals. + +MAIN: { + $App::Ack::orig_program_name = $0; + $0 = join(' ', 'ack', $0); + if ( $App::Ack::VERSION ne $main::VERSION ) { + App::Ack::die( "Program/library version mismatch\n\t$0 is $main::VERSION\n\t$INC{'App/Ack.pm'} is $App::Ack::VERSION" ); + } + + # Do preliminary arg checking; + my $env_is_usable = 1; + for my $arg ( @ARGV ) { + last if ( $arg eq '--' ); + + # Get the --thpppt, --bar, --cathy checking out of the way. + $arg =~ /^--th[pt]+t+$/ and App::Ack::_thpppt($arg); + $arg eq '--bar' and App::Ack::_bar(); + $arg eq '--cathy' and App::Ack::_cathy(); + + # See if we want to ignore the environment. (Don't tell Al Gore.) + $arg eq '--env' and $env_is_usable = 1; + $arg eq '--noenv' and $env_is_usable = 0; + } + + if ( !$env_is_usable ) { + my @keys = ( 'ACKRC', grep { /^ACK_/ } keys %ENV ); + delete @ENV{@keys}; + } + load_colors(); + + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); + Getopt::Long::Configure('pass_through', 'no_auto_abbrev'); + Getopt::Long::GetOptions( + 'help' => sub { App::Ack::show_help(); exit; }, + 'version' => sub { App::Ack::print_version_statement(); exit; }, + 'man' => sub { App::Ack::show_man(); exit; }, + ); + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); + + if ( !@ARGV ) { + App::Ack::show_help(); + exit 1; + } + + main(); } -sub _thpppt { - my $y = _get_thpppt(); - App::Ack::print( "$y ack $_[0]!\n" ); - exit 0; +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}; + + my %ignore_dirs; + + 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"} ); + } + } + + return sub { + return !exists $ignore_dirs{$_} && !exists $ignore_dirs{$File::Next::dir}; + }; } -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 +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"} ); + } + } + + my $filters = $opt->{'filters'} || []; + my $direct_filters = App::Ack::Filter::Collection->new(); + my $inverse_filters = App::Ack::Filter::Collection->new(); + + foreach my $filter (@{$filters}) { + if ($filter->is_inverted()) { + # We want to check if files match the uninverted filters + $inverse_filters->add($filter->invert()); + } + else { + $direct_filters->add($filter); + } + } + + my %is_member_of_starting_set = map { (get_file_id($_) => 1) } @{$start}; + + my $ignore_dir_list = $opt->{idirs}; + my $dont_ignore_dir_list = $opt->{no_ignore_dirs}; + + my %ignore_dir_set; + my %dont_ignore_dir_set; + + 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"} ); + } + } + 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"} ); + } + } + + 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; + + 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; + } + } + + # 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; + } + + my $resource = App::Ack::Resource::Basic->new($File::Next::name); + return 0 if ! $resource; + if ( $ifiles_filters->filter($resource) ) { + return 0; + } + + my $match_found = $direct_filters->filter($resource); + + # Don't bother invoking inverse filters unless we consider the current resource a match + if ( $match_found && $inverse_filters->filter( $resource ) ) { + $match_found = 0; + } + return $match_found; + }; +} + +sub show_types { + my $resource = shift; + my $ors = shift; + + my @types = filetypes( $resource ); + my $types = join( ',', @types ); + my $arrow = @types ? ' => ' : ' =>'; + App::Ack::print( $resource->name, $arrow, join( ',', @types ), $ors ); + + return; +} + +# Set default colors, load Term::ANSIColor +sub load_colors { + eval 'use Term::ANSIColor 1.10 ()'; + eval 'use Win32::Console::ANSI' if $App::Ack::is_windows; + + $ENV{ACK_COLOR_MATCH} ||= 'black on_yellow'; + $ENV{ACK_COLOR_FILENAME} ||= 'bold green'; + $ENV{ACK_COLOR_LINENO} ||= 'bold yellow'; + + return; +} + +sub filetypes { + my ( $resource ) = @_; + + my @matches; + + foreach my $k (keys %App::Ack::mappings) { + my $filters = $App::Ack::mappings{$k}; + + foreach my $filter (@{$filters}) { + # clone the resource + my $clone = $resource->clone; + if ( $filter->filter($clone) ) { + push @matches, $k; + last; + } + } + } + + # http://search.cpan.org/dist/Perl-Critic/lib/Perl/Critic/Policy/Subroutines/ProhibitReturnSort.pm + @matches = sort @matches; + return @matches; +} + +# Returns a (fairly) unique identifier for a file. +# Use this function to compare two files to see if they're +# equal (ie. the same file, but with a different path/links/etc). +sub get_file_id { + my ( $filename ) = @_; + + if ( $App::Ack::is_windows ) { + return File::Next::reslash( $filename ); + } + else { + # XXX is this the best method? it always hits the FS + if( my ( $dev, $inode ) = (stat($filename))[0, 1] ) { + return join(':', $dev, $inode); + } + else { + # XXX this could be better + return $filename; + } + } +} + +# Returns a regex object based on a string and command-line options. +# Dies when the regex $str is undefined (i.e. not given on command line). + +sub build_regex { + my $str = shift; + my $opt = shift; + + defined $str or App::Ack::die( 'No regular expression found.' ); + + $str = quotemeta( $str ) if $opt->{Q}; + if ( $opt->{w} ) { + $str = "\\b$str" if $str =~ /^\w/; + $str = "$str\\b" if $str =~ /\w$/; + } + + my $regex_is_lc = $str eq lc $str; + if ( $opt->{i} || ($opt->{smart_case} && $regex_is_lc) ) { + $str = "(?i)$str"; + } + + my $re = eval { qr/$str/ }; + if ( !$re ) { + die "Invalid regex '$str':\n $@"; + } + + return $re; + +} + +{ + +my @before_ctx_lines; +my @after_ctx_lines; +my $is_iterating; + +my $has_printed_something; + +BEGIN { + $has_printed_something = 0; +} + +sub print_matches_in_resource { + my ( $resource, $opt ) = @_; + + my $passthru = $opt->{passthru}; + my $max_count = $opt->{m} || -1; + my $nmatches = 0; + my $filename = $resource->name; + my $break = $opt->{break}; + my $heading = $opt->{heading}; + my $ors = $opt->{print0} ? "\0" : "\n"; + my $color = $opt->{color}; + my $print_filename = $opt->{show_filename}; + + my $has_printed_for_this_resource = 0; + + $is_iterating = 1; + + local $opt->{before_context} = $opt->{output} ? 0 : $opt->{before_context}; + local $opt->{after_context} = $opt->{output} ? 0 : $opt->{after_context}; + + my $n_before_ctx_lines = $opt->{before_context} || 0; + my $n_after_ctx_lines = $opt->{after_context} || 0; + + @after_ctx_lines = @before_ctx_lines = (); + + my $fh = $resource->open(); + if ( !$fh ) { + if ( $App::Ack::report_bad_filenames ) { + App::Ack::warn( "$filename: $!" ); + } + return 0; + } + + my $display_filename = $filename; + if ( $print_filename && $heading && $color ) { + $display_filename = Term::ANSIColor::colored($display_filename, $ENV{ACK_COLOR_FILENAME}); + } + + # check for context before the main loop, so we don't + # pay for it if we don't need it + if ( $n_before_ctx_lines || $n_after_ctx_lines ) { + my $current_line = <$fh>; # prime the first line of input + + while ( defined $current_line ) { + while ( (@after_ctx_lines < $n_after_ctx_lines) && defined($_ = <$fh>) ) { + push @after_ctx_lines, $_; + } + + local $_ = $current_line; + my $former_dot_period = $.; + $. -= @after_ctx_lines; + + if ( does_match($opt, $_) ) { + if ( !$has_printed_for_this_resource ) { + if ( $break && $has_printed_something ) { + App::Ack::print_blank_line(); + } + if ( $print_filename && $heading ) { + App::Ack::print_filename( $display_filename, $ors ); + } + } + 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 $_; + + 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; + } + } + + $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; + + 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}; + + my @line_parts; + + if( $color ) { + $filename = Term::ANSIColor::colored($filename, + $ENV{ACK_COLOR_FILENAME}); + $line_no = Term::ANSIColor::colored($line_no, + $ENV{ACK_COLOR_LINENO}); + } + + if($print_filename) { + if( $heading ) { + push @line_parts, $line_no; + } + else { + push @line_parts, $filename, $line_no; + } + + 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 + + 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; + } + } - $x =~ s/(.)(.)/$1x(ord($2)-32)/eg; - App::Ack::print( $x ); - exit 0; + push @line_parts, $line; + App::Ack::print( join( $separator, @line_parts ), $ors ); + } + + return; } +sub iterate { + my ( $resource, $opt, $cb ) = @_; -sub show_help { - my $help_arg = shift || 0; + $is_iterating = 1; - return show_help_types() if $help_arg =~ /^types?/; + local $opt->{before_context} = $opt->{output} ? 0 : $opt->{before_context}; + local $opt->{after_context} = $opt->{output} ? 0 : $opt->{after_context}; - App::Ack::print( <<"END_OF_HELP" ); -Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES] + my $n_before_ctx_lines = $opt->{before_context} || 0; + my $n_after_ctx_lines = $opt->{after_context} || 0; -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 "-". + @after_ctx_lines = @before_ctx_lines = (); -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 $fh = $resource->open(); + if ( !$fh ) { + if ( $App::Ack::report_bad_filenames ) { + # XXX direct access to filename + App::Ack::warn( "$resource->{filename}: $!" ); + } + return; + } -Example: ack -i select + # 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 -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 + while ( defined $current_line ) { + while ( (@after_ctx_lines < $n_after_ctx_lines) && defined($_ = <$fh>) ) { + push @after_ctx_lines, $_; + } -Search output: - --lines=NUM Only print line(s) NUM of each file - -l, --files-with-matches Only print filenames containing matches - -L, --files-without-matches Only print filenames with no matches - --output=expr Output the evaluation of expr for each line - (turns off text highlighting) - -o Show only the part of a line matching PATTERN - Same as --output='\$&' - --passthru Print all lines, whether matching or not - --match PATTERN Specify PATTERN explicitly. - -m, --max-count=NUM Stop searching in each file after NUM matches - -1 Stop searching after one match of any kind - -H, --with-filename Print the filename for each match (default: - on unless explicitly searching a single file) - -h, --no-filename Suppress the prefixing filename on output - -c, --count Show number of lines matching per file - --[no]column Show the column number of the first match + local $_ = $current_line; + my $former_dot_period = $.; + $. -= @after_ctx_lines; - -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. + last unless $cb->(); - --print0 Print null byte as separator between filenames, - only works with -f, -g, -l, -L or -c. + # 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 - -s Suppress error messages about nonexistent or - unreadable files. + 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 $_; + while ( <$fh> ) { + last unless $cb->(); + } + } -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). + $is_iterating = 0; # XXX this won't happen on an exception + # then again, do we care? ack doesn't really + # handle exceptions anyway. + return; +} -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 get_context { + if ( not $is_iterating ) { + Carp::croak( 'get_context() called outside of iterate()' ); + } -File inclusion/exclusion: - --[no]ignore-dir=name Add/Remove directory from the list of ignored dirs - --[no]ignore-directory=name Synonym for ignore-dir - --ignore-file=filter Add filter for ignoring files - -r, -R, --recurse Recurse into subdirectories (ack's default behavior) - -n, --no-recurse No descending into subdirectories - --[no]follow Follow symlinks. Default is off. - -k, --known-types Include only files with types that ack recognizes. + return ( + scalar(@before_ctx_lines) ? \@before_ctx_lines : undef, + scalar(@after_ctx_lines) ? \@after_ctx_lines : undef, + ); +} - --type=X Include only X files, where X is a recognized filetype. - --type=noX Exclude X files. - See "ack --help-types" for supported filetypes. +} -File type specification: - --type-set TYPE:FILTER:FILTERARGS - Files with the given FILTERARGS applied to the given - FILTER are recognized as being of type TYPE. This - replaces an existing definition for type TYPE. - --type-add TYPE:FILTER:FILTERARGS - Files with the given FILTERARGS applied to the given - FILTER are recognized as being of type TYPE. - --type-del TYPE Removes all filters associated with TYPE. +{ +my $is_first_match; +my $previous_file_processed; +my $previous_line_printed; -Miscellaneous: - --[no]env Ignore environment variables and global ackrc files. --env is legal but redundant. - --ackrc=filename Specify an ackrc file to use - --ignore-ack-defaults Ignore the default definitions that ack includes. - --create-ackrc Outputs a default ackrc for your customization to standard output. - --help, -? This help - --help-types Display all known types - --dump Dump information on which options are loaded from which RC files - --[no]filter Force ack to treat standard input as a pipe (--filter) or tty (--nofilter) - --man Man page - --version Display version & copyright - --thpppt Bill the Cat - --bar The warning admiral +BEGIN { + $is_first_match = 1; + $previous_line_printed = -1; +} -Exit status is 0 if match, 1 if no match. +sub print_line_with_context { + my ( $opt, $filename, $matching_line, $line_no ) = @_; -This is version $VERSION of ack. -END_OF_HELP + my $heading = $opt->{heading}; - return; - } + if( !defined($previous_file_processed) || + $previous_file_processed ne $filename ) { + $previous_file_processed = $filename; + $previous_line_printed = -1; + if( $heading ) { + $is_first_match = 1; + } + } + 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}; -sub show_help_types { - App::Ack::print( <<'END_OF_HELP' ); -Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES] + $matching_line =~ s/[\r\n]+$//g; -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. + my ( $before_context, $after_context ) = get_context(); -Note that some extensions may appear in multiple types. For example, -.pod files are both Perl and Parrot. + if ( $before_context ) { + my $first_line = $. - @{$before_context}; -END_OF_HELP + 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}; - my @types = filetypes_supported(); - my $maxlen = 0; - for ( @types ) { - $maxlen = length if $maxlen < length; - } - for my $type ( sort @types ) { - next if $type =~ /^-/; # Stuff to not show - my $ext_list = $mappings{$type}; + if( !$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; + } - if ( ref $ext_list ) { - $ext_list = join( '; ', map { $_->to_string } @{$ext_list} ); + chomp $line; + print_line_with_options($opt, $filename, $line, $context_line_no, '-'); + $previous_line_printed = $context_line_no; + $offset--; + } } - App::Ack::print( sprintf( " --[no]%-*.*s %s\n", $maxlen, $maxlen, $type, $ext_list ) ); } - return; -} + if ( $. > $previous_line_printed ) { + if( $is_tracking_context && !$is_first_match && $previous_line_printed != $. - 1 ) { + App::Ack::print('--', $ors); + } -sub show_man { - require Pod::Usage; + print_line_with_options($opt, $filename, $matching_line, $line_no, ':'); + $previous_line_printed = $.; + } - Pod::Usage::pod2usage({ - -input => $App::Ack::orig_program_name, - -verbose => 2, - -exitval => 0, - }); + 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++; + } + } + + $is_first_match = 0; return; } +} -sub get_version_statement { - require Config; +{ - my $copyright = get_copyright(); - my $this_perl = $Config::Config{perlpath}; - if ($^O ne 'VMS') { - my $ext = $Config::Config{_exe}; - $this_perl .= $ext unless $this_perl =~ m/$ext$/i; - } - my $ver = sprintf( '%vd', $^V ); +my $match_column_number; - my $git_revision = $GIT_REVISION ? " (git commit $GIT_REVISION)" : ''; +# does_match() MUST have an $opt->{regex} set. - return <<"END_OF_VERSION"; -ack ${VERSION}${git_revision} -Running under Perl $ver at $this_perl +sub does_match { + my ( $opt, $line ) = @_; -$copyright + $match_column_number = undef; -This program is free software. You may modify or distribute it -under the terms of the Artistic License v2.0. -END_OF_VERSION + 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 print_version_statement { - App::Ack::print( get_version_statement() ); - - return; +sub get_match_column { + return $match_column_number; } - -sub get_copyright { - return $COPYRIGHT; } +sub resource_has_match { + my ( $resource, $opt ) = @_; -# 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; - - if ($show_filename) { - App::Ack::print( $filename ); - App::Ack::print( ':', $nmatches ) if $count; + 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 { - App::Ack::print( $nmatches ) if $count; + my $opt_v = $opt->{v}; + my $re = $opt->{regex}; + while ( <$fh> ) { + if (/$re/o xor $opt_v) { + $has_match = 1; + last; + } + } + close $fh; } - App::Ack::print( $ors ); - return; + return $has_match; } -sub print_count0 { - my $filename = shift; - my $ors = shift; - my $show_filename = shift; +sub count_matches_in_resource { + my ( $resource, $opt ) = @_; - if ($show_filename) { - App::Ack::print( $filename, ':0', $ors ); + 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 { - App::Ack::print( '0', $ors ); - } - - return; -} - -sub set_up_pager { - my $command = shift; - - return if App::Ack::output_to_pipe(); - - my $pager; - if ( not open( $pager, '|-', $command ) ) { - App::Ack::die( qq{Unable to pipe to pager "$command": $!} ); + my $opt_v = $opt->{v}; + my $re = $opt->{regex}; + while ( <$fh> ) { + ++$nmatches if (/$re/o xor $opt_v); + } + close $fh; } - $fh = $pager; - - return; -} - -sub output_to_pipe { - return $output_to_pipe; + return $nmatches; } +sub main { + my @arg_sources = App::Ack::ConfigLoader::retrieve_arg_sources(); -sub exit_from_ack { - my $nmatches = shift; - - my $rc = $nmatches ? 0 : 1; - exit $rc; -} - + my $opt = App::Ack::ConfigLoader::process_args( @arg_sources ); + $App::Ack::report_bad_filenames = !$opt->{dont_report_bad_filenames}; -1; # End of App::Ack -package App::Ack::Resource; + if ( $opt->{flush} ) { + $| = 1; + } + 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(); + } -use warnings; -use strict; -use overload - '""' => 'name'; + if ( defined($opt->{H}) || defined($opt->{h}) ) { + $opt->{show_filename}= $opt->{H} && !$opt->{h}; + } -sub FAIL { - require Carp; - Carp::confess( 'Must be overloaded' ); -} + 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 ); + 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; + } + } -sub new { - return FAIL(); -} + if ( defined $opt->{files_from} ) { + $resources = App::Ack::Resources->from_file( $opt, $opt->{files_from} ); + exit 1 unless $resources; + } + else { + @start = ('.') unless @start; + foreach my $target (@start) { + if ( !-e $target && $App::Ack::report_bad_filenames) { + App::Ack::warn( "$target: No such file or directory" ); + } + } + $opt->{file_filter} = _compile_file_filter($opt, \@start); + $opt->{descend_filter} = _compile_descend_filter($opt); -sub name { - return FAIL(); -} + $resources = App::Ack::Resources->from_argv( $opt, \@start ); + } + } + App::Ack::set_up_pager( $opt->{pager} ) if defined $opt->{pager}; + my $print_filenames = $opt->{show_filename}; + my $max_count = $opt->{m}; + my $ors = $opt->{print0} ? "\0" : "\n"; + my $only_first = $opt->{1}; -sub is_binary { - return FAIL(); -} + 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. + # 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}; -sub open { - return FAIL(); -} + 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; -sub needs_line_scan { - return FAIL(); -} + local $opt->{color} = 0; + iterate($resource, $opt, sub { + chomp; -sub reset { - return FAIL(); -} + 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 close { - return FAIL(); -} + 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 ); + if ( $opt->{L} ? !$is_match : $is_match ) { + App::Ack::print( $resource->name, $ors ); + ++$nmatches; -sub clone { - return FAIL(); -} + 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; + } + } + } + if ( $opt->{count} && !$opt->{show_filename} ) { + App::Ack::print( $total_count, "\n" ); + } -sub firstliney { - return FAIL(); + close $App::Ack::fh; + App::Ack::exit_from_ack( $nmatches ); } -1; -package App::Ack::Resources; +=head1 NAME -use warnings; -use strict; - +ack - grep-like text finder -sub from_argv { - my $class = shift; - my $opt = shift; - my $start = shift; +=head1 SYNOPSIS - my $self = bless {}, $class; + ack [options] PATTERN [FILE...] + ack -f [options] [DIRECTORY...] - my $file_filter = undef; - my $descend_filter = $opt->{descend_filter}; +=head1 DESCRIPTION - if( $opt->{n} ) { - $descend_filter = sub { - return 0; - }; - } +Ack is designed as a replacement for 99% of the uses of F. - $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} ); +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. - return $self; -} +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. -sub from_file { - my $class = shift; - my $opt = shift; - my $file = shift; +=head1 FILE SELECTION - 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; +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. - return bless { - iter => $iter, - }, $class; -} +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. -# This is for reading input lines from STDIN, not the list of files from STDIN -sub from_stdin { - my $class = shift; - my $opt = shift; +With no file selection, I searches through regular files that +are not explicitly excluded by B<--ignore-dir> and B<--ignore-file> +options, either present in F files or on the command line. - my $self = bless {}, $class; +The default options for I ignore certain files and directories. These +include: - my $has_been_called = 0; +=over 4 - $self->{iter} = sub { - if ( !$has_been_called ) { - $has_been_called = 1; - return '-'; - } - return; - }; +=item * Backup files: Files matching F<#*#> or ending with F<~>. - return $self; -} +=item * Coredumps: Files matching F -sub next { - my $self = shift; +=item * Version control directories like F<.svn> and F<.git>. - my $file = $self->{iter}->() or return; +=back - return App::Ack::Resource::Basic->new( $file ); -} +Run I with the C<--dump> option to see what settings are set. -1; -package App::Ack::Resource::Basic; +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. +=head1 DIRECTORY SELECTION -use warnings; -use strict; +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 Fcntl (); +For a complete list of directories that do not get searched, run +C. -BEGIN { - our @ISA = 'App::Ack::Resource'; -} +=head1 WHEN TO USE GREP +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. -sub new { - my $class = shift; - my $filename = shift; +E.g., searching through huge files looking for regexes that can be +expressed with I syntax should be quicker with I. - my $self = bless { - filename => $filename, - fh => undef, - opened => 0, - }, $class; +If your script or parent program uses I C<--quiet> or C<--silent> +or needs exit 2 on IO error, use I. - if ( $self->{filename} eq '-' ) { - $self->{fh} = *STDIN; - $self->{opened} = 1; - } +=head1 OPTIONS - return $self; -} +=over 4 +=item B<--ackrc> -sub name { - return $_[0]->{filename}; -} +Specifies an ackrc file to load after all others; see L. +=item B<-A I>, B<--after-context=I> +Print I lines of trailing context after matching lines. -sub needs_line_scan { - my $self = shift; - my $opt = shift; +=item B<-B I>, B<--before-context=I> - return 1 if $opt->{v}; +Print I lines of leading context before matching lines. - my $size = -s $self->{fh}; - if ( $size == 0 ) { - return 0; - } - elsif ( $size > 100_000 ) { - return 1; - } +=item B<--[no]break> - 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 ); +Print a break between results from different files. On by default +when used interactively. - my $regex = $opt->{regex}; - return $buffer =~ /$regex/m; -} +=item B<-C [I]>, B<--context[=I]> +Print I lines (default 2) of context around matching lines. -sub reset { - my $self = shift; +=item B<-c>, B<--count> - # return if we haven't opened the file yet - if ( !defined($self->{fh}) ) { - return; - } +Suppress normal output; instead print a count of matching lines for +each input file. If B<-l> is in effect, it will only show the +number of lines for each file that has lines matching. Without +B<-l>, some line counts may be zeroes. - if( !seek( $self->{fh}, 0, 0 ) && $App::Ack::report_bad_filenames ) { - App::Ack::warn( "$self->{filename}: $!" ); - } +If combined with B<-h> (B<--no-filename>) ack outputs only one total +count. - return; -} +=item B<--[no]color>, B<--[no]colour> +B<--color> highlights the matching text. B<--nocolor> suppresses +the color. This is on by default unless the output is redirected. -sub close { - my $self = shift; +On Windows, this option is off by default unless the +L module is installed or the C +environment variable is used. - # return if we haven't opened the file yet - if ( !defined($self->{fh}) ) { - return; - } +=item B<--color-filename=I> - if ( !close($self->{fh}) && $App::Ack::report_bad_filenames ) { - App::Ack::warn( $self->name() . ": $!" ); - } +Sets the color to be used for filenames. - $self->{opened} = 0; +=item B<--color-match=I> - return; -} +Sets the color to be used for matches. +=item B<--color-lineno=I> -sub clone { - my ( $self ) = @_; +Sets the color to be used for line numbers. - return __PACKAGE__->new($self->name); -} +=item B<--[no]column> -sub firstliney { - my ( $self ) = @_; +Show the column number of the first match. This is helpful for +editors that can place your cursor at a given position. - my $fh = $self->open(); +=item B<--create-ackrc> - 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; - } +Dumps the default ack options to standard output. This is useful for +when you want to customize the defaults. - $self->close; +=item B<--dump> - return $self->{firstliney}; -} +Writes the list of options loaded and where they came from to standard +output. Handy for debugging. -sub open { - my ( $self ) = @_; +=item B<--[no]env> - return $self->{fh} if $self->{opened}; +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. - unless ( open $self->{fh}, '<', $self->{filename} ) { - return; - } +=item B<--flush> - $self->{opened} = 1; +B<--flush> flushes output immediately. This is off by default +unless ack is running interactively (when output goes to a pipe or +file). - return $self->{fh}; -} +=item B<-f> -1; -package App::Ack::Filter; +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. -use strict; -use warnings; -use overload - '""' => 'to_string'; +=item B<--files-from=I> -use Carp 1.04 (); +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. -my %filter_types; +=item B<--[no]filter> +Forces ack to act as if it were receiving input via a pipe. -sub create_filter { - my ( undef, $type, @args ) = @_; +=item B<--[no]follow> - if ( my $package = $filter_types{$type} ) { - return $package->new(@args); - } - Carp::croak "Unknown filter type '$type'"; -} +Follow or don't follow symlinks, other than whatever starting files +or directories were specified on the command line. +This is off by default. -sub register_filter { - my ( undef, $type, $package ) = @_; +=item B<-g I> - $filter_types{$type} = $package; +Print files where the relative path + filename matches I. - return; -} +=item B<--[no]group> +B<--group> groups matches by file name. This is the default +when used interactively. -sub invert { - my ( $self ) = @_; +B<--nogroup> prints one result per line, like grep. This is the +default when output is redirected. - return App::Ack::Filter::Inverse->new( $self ); -} +=item B<-H>, B<--with-filename> +Print the filename for each match. This is the default unless searching +a single explicitly specified file. -sub is_inverted { - return 0; -} +=item B<-h>, B<--no-filename> +Suppress the prefixing of filenames on output when multiple files are +searched. -sub to_string { - my ( $self ) = @_; +=item B<--[no]heading> - return '(unimplemented to_string)'; -} +Print a filename heading above each file's results. This is the default +when used interactively. +=item B<--help>, B<-?> -sub inspect { - my ( $self ) = @_; +Print a short help statement. - return ref($self); -} +=item B<--help-types>, B<--help=types> -1; -package App::Ack::Filter::Extension; +Print all known types. -use strict; -use warnings; -BEGIN { - our @ISA = 'App::Ack::Filter'; -} +=item B<-i>, B<--ignore-case> -sub new { - my ( $class, @extensions ) = @_; +Ignore case distinctions in PATTERN - my $exts = join('|', map { "\Q$_\E"} @extensions); - my $re = qr/[.](?:$exts)$/i; +=item B<--ignore-ack-defaults> - return bless { - extensions => \@extensions, - regex => $re, - }, $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. -sub filter { - my ( $self, $resource ) = @_; +=item B<--[no]ignore-dir=I>, B<--[no]ignore-directory=I> - my $re = $self->{'regex'}; +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). - return $resource->name =~ /$re/; -} +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. -sub inspect { - my ( $self ) = @_; +=item B<--ignore-file=I> - my $re = $self->{'regex'}; +Ignore files matching I. The filters are specified +identically to file type filters as seen in L. - return ref($self) . " - $re"; -} +=item B<-k>, B<--known-types> -sub to_string { - my ( $self ) = @_; +Limit selected files to those with types that ack knows about. This is +equivalent to the default behavior found in ack 1. - my $exts = $self->{'extensions'}; +=item B<--lines=I> - return join(' ', map { ".$_" } @{$exts}); -} +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. -BEGIN { - App::Ack::Filter->register_filter(ext => __PACKAGE__); -} +=item B<-l>, B<--files-with-matches> -1; -package App::Ack::Filter::FirstLineMatch; +Only print the filenames of matching files, instead of the matching text. -use strict; -use warnings; -BEGIN { - our @ISA = 'App::Ack::Filter'; -} +=item B<-L>, B<--files-without-matches> -sub new { - my ( $class, $re ) = @_; +Only print the filenames of files that do I match. - $re =~ s{^/|/$}{}g; # XXX validate? - $re = qr{$re}i; +=item B<--match I> - return bless { - regex => $re, - }, $class; -} +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. -# 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. + # search for foo and bar in given files + ack file1 t/file* --match foo + ack file1 t/file* --match bar -sub filter { - my ( $self, $resource ) = @_; +=item B<-m=I>, B<--max-count=I> - my $re = $self->{'regex'}; +Stop reading a file after I matches. - my $line = $resource->firstliney; +=item B<--man> - return $line =~ /$re/; -} +Print this manual page. -sub inspect { - my ( $self ) = @_; +=item B<-n>, B<--no-recurse> - my $re = $self->{'regex'}; +No descending into subdirectories. - return ref($self) . " - $re"; -} +=item B<-o> -sub to_string { - my ( $self ) = @_; +Show only the part of each line matching PATTERN (turns off text +highlighting) - (my $re = $self->{regex}) =~ s{\([^:]*:(.*)\)$}{$1}; +=item B<--output=I> - return "first line matches /$re/"; -} +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">. -BEGIN { - App::Ack::Filter->register_filter(firstlinematch => __PACKAGE__); -} +=item B<--pager=I>, B<--nopager> -1; -package App::Ack::Filter::Is; +B<--pager> directs ack's output through I. This can also be specified +via the C and C environment variables. -use strict; -use warnings; -BEGIN { - our @ISA = 'App::Ack::Filter'; -} +Using --pager does not suppress grouping and coloring like piping +output on the command-line does. -use File::Spec 3.00 (); +B<--nopager> cancels any setting in ~/.ackrc, C or C. +No output will be sent through a pager. -sub new { - my ( $class, $filename ) = @_; +=item B<--passthru> - return bless { - filename => $filename, - }, $class; -} +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: -sub filter { - my ( $self, $resource ) = @_; + # Watch a log file, and highlight a certain IP address + $ tail -f ~/access.log | ack --passthru 123.45.67.89 - my $filename = $self->{'filename'}; - my $base = (File::Spec->splitpath($resource->name))[2]; +=item B<--print0> - return $base eq $filename; -} +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. -sub inspect { - my ( $self ) = @_; + # remove all files of type html + ack -f --html --print0 | xargs -0 rm -f - my $filename = $self->{'filename'}; +=item B<-Q>, B<--literal> - return ref($self) . " - $filename"; -} +Quote all metacharacters in PATTERN, it is treated as a literal. -sub to_string { - my ( $self ) = @_; +=item B<-r>, B<-R>, B<--recurse> - my $filename = $self->{'filename'}; -} +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. -BEGIN { - App::Ack::Filter->register_filter(is => __PACKAGE__); -} +=item B<-s> -1; -package App::Ack::Filter::Match; +Suppress error messages about nonexistent or unreadable files. This is taken +from fgrep. -use strict; -use warnings; -BEGIN { - our @ISA = 'App::Ack::Filter'; -} +=item B<--[no]smart-case>, B<--no-smart-case> -use File::Spec 3.00; +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. -sub new { - my ( $class, $re ) = @_; +B<-i> always overrides this option. - $re =~ s{^/|/$}{}g; # XXX validate? - $re = qr/$re/i; +=item B<--sort-files> - return bless { - regex => $re, - }, $class; -} +Sorts the found files lexicographically. Use this if you want your file +listings to be deterministic between runs of I. -sub filter { - my ( $self, $resource ) = @_; +=item B<--show-types> - my $re = $self->{'regex'}; - my $base = (File::Spec->splitpath($resource->name))[2]; +Outputs the filetypes that ack associates with each file. - return $base =~ /$re/; -} +Works with B<-f> and B<-g> options. -sub inspect { - my ( $self ) = @_; +=item B<--type=[no]TYPE> - my $re = $self->{'regex'}; +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>. - print ref($self) . " - $re"; -} +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. -sub to_string { - my ( $self ) = @_; +Type specifications can be repeated and are ORed together. - my $re = $self->{'regex'}; +See I for a list of valid types. - return "filename matches $re"; -} +=item B<--type-add I:I:I> -BEGIN { - App::Ack::Filter->register_filter(match => __PACKAGE__); -} +Files with the given FILTERARGS applied to the given FILTER +are recognized as being of (the existing) type TYPE. +See also L. -1; -package App::Ack::Filter::Default; -use strict; -use warnings; -BEGIN { - our @ISA = 'App::Ack::Filter'; -} +=item B<--type-set I:I:I> -sub new { - my ( $class ) = @_; +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 bless {}, $class; -} +=item B<--type-del I> -sub filter { - my ( $self, $resource ) = @_; +The filters associated with TYPE are removed from Ack, and are no longer considered +for searches. - return -T $resource->name; -} +=item B<-v>, B<--invert-match> -1; -package App::Ack::Filter::Inverse; +Invert match: select non-matching lines -use strict; -use warnings; -BEGIN { - our @ISA = 'App::Ack::Filter'; -} +=item B<--version> -sub new { - my ( $class, $filter ) = @_; +Display version and copyright information. - return bless { - filter => $filter, - }, $class; -} +=item B<-w>, B<--word-regexp> -sub filter { - my ( $self, $resource ) = @_; +Force PATTERN to match only whole words. The PATTERN is wrapped with +C<\b> metacharacters. - my $filter = $self->{'filter'}; - return !$filter->filter( $resource ); -} +=item B<-x> -sub invert { - my $self = shift; +An abbreviation for B<--files-from=->; the list of files to search are read +from standard input, with one line per file. - return $self->{'filter'}; -} +=item B<-1> -sub is_inverted { - return 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. -sub inspect { - my ( $self ) = @_; +=item B<--thpppt> - my $filter = $self->{'filter'}; +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. - return "!$filter"; -} +=item B<--bar> -1; -package App::Ack::ConfigFinder; +Check with the admiral for traps. +=item B<--cathy> -use strict; -use warnings; +Chocolate, Chocolate, Chocolate! -use Cwd 3.00 (); -use File::Spec 3.00; +=back -use if ($^O =~ /MSWin32/ ? 1 : 0), "Win32"; +=head1 THE .ackrc FILE +The F<.ackrc> file contains command-line options that are prepended +to the command line before processing. Multiple options may live +on multiple lines. Lines beginning with a # are ignored. A F<.ackrc> +might look like this: -our $is_win = 0; + # Always sort the files + --sort-files -sub new { - my ( $class ) = @_; + # Always color, even if piping to a another program + --color - $is_win = $^O =~ /MSWin32/, + # Use "less -r" as my pager + --pager=less -r - return bless {}, $class; -} +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 _remove_redundancies { - my ( @configs ) = @_; +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. - if ( $is_win ) { - # inode stat always returns 0 on windows, - # so just check filenames - my (%seen, @uniq); +=head1 Defining your own types - foreach my $path (@configs) { - push @uniq, $path unless $seen{$path}; - $seen{$path} = 1; - } +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. - return @uniq; - } +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. - else { +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 %dev_and_inode_seen; +When defining your own types in the F<.ackrc> file you have to use +the following: - foreach my $path ( @configs ) { - my ( $dev, $inode ) = (stat $path)[0, 1]; + --type-set=eiffel:ext:e,eiffel - if( defined($dev) ) { - if( $dev_and_inode_seen{"$dev:$inode"} ) { - undef $path; - } - else { - $dev_and_inode_seen{"$dev:$inode"} = 1; - } - } - } +or writing on separate lines - return grep { defined() } @configs; + --type-set + eiffel:ext:e,eiffel - } -} +The following does B work in the F<.ackrc> file: -sub _check_for_ackrc { - return unless defined $_[0]; + --type-set eiffel:ext:e,eiffel - my @files = grep { -f } - map { File::Spec->catfile(@_, $_) } - qw(.ackrc _ackrc); - die File::Spec->catdir(@_) . " contains both .ackrc and _ackrc.\n" . - "Please remove one of those files.\n" - if @files > 1; +In order to see all currently defined types, use I<--help-types>, e.g. +I - return wantarray ? @files : $files[0]; -} # end _check_for_ackrc +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. +=over 4 -sub find_config_files { - my @config_files; +=item is:I - if( $is_win ) { - 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'; - } +I filters match the target filename exactly. It takes exactly one +argument, which is the name of the file to match. +Example: - if ( $ENV{'ACKRC'} && -f $ENV{'ACKRC'} ) { - push @config_files, $ENV{'ACKRC'}; - } - else { - push @config_files, _check_for_ackrc($ENV{'HOME'}); - } + --type-set make:is:Makefile - 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; - } +=item ext:I[,I[,...]] - # 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 ); -} +I filters match the extension of the target file against a list +of extensions. No leading dot is needed for the extensions. +Example: -sub read_rcfile { - my $file = shift; + --type-set perl:ext:pl,pm,t - return unless defined $file && -e $file; +=item match:I - my @lines; +I filters match the target filename against a regular expression. +The regular expression is made case insensitive for the search. - open( my $fh, '<', $file ) or App::Ack::die( "Unable to read $file: $!" ); - while ( my $line = <$fh> ) { - chomp $line; - $line =~ s/^\s+//; - $line =~ s/\s+$//; +Example: - next if $line eq ''; - next if $line =~ /^#/; + --type-set make:match:/(gnu)?makefile/ - push( @lines, $line ); - } - close $fh; +=item firstlinematch:I - return @lines; -} +I matches the first line of the target file against a +regular expression. Like I, the regular expression is made +case insensitive. -1; -package App::Ack::ConfigLoader; +Example: -use strict; -use warnings; + --type-add perl:firstlinematch:/perl/ -use Carp 1.04 (); -use Getopt::Long 2.35 (); -use Text::ParseWords 3.1 (); +=back +More filter types may be made available in the future. -my @INVALID_COMBINATIONS; +=head1 ENVIRONMENT VARIABLES -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 ); +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. - @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], - ); -} +=over 4 -sub process_filter_spec { - my ( $spec ) = @_; +=item ACKRC - if ( $spec =~ /^(\w+):(\w+):(.*)/ ) { - my ( $type_name, $ext_type, $arguments ) = ( $1, $2, $3 ); +Specifies the location of the user's F<.ackrc> file. If this file doesn't +exist, F looks in the default location. - 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 ); +=item ACK_OPTIONS - my @extensions = split(/,/, $extensions); - foreach my $extension ( @extensions ) { - $extension =~ s/^[.]//; - } +This variable specifies default options to be placed in front of +any explicit options on the command line. - return ( $type_name, App::Ack::Filter->create_filter('ext', @extensions) ); - } - else { - Carp::croak "invalid filter specification '$spec'"; - } -} +=item ACK_COLOR_FILENAME -sub process_filetypes { - my ( $opt, $arg_sources ) = @_; +Specifies the color of the filename when it's printed in B<--group> +mode. By default, it's "bold green". - 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; +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. - my $add_spec = sub { - my ( undef, $spec ) = @_; +This option can also be set with B<--color-filename>. - my ( $name, $filter ) = process_filter_spec($spec); +=item ACK_COLOR_MATCH - push @{ $App::Ack::mappings{$name} }, $filter; +Specifies the color of the matching text when printed in B<--color> +mode. By default, it's "black on_yellow". - $additional_specs{$name . '!'} = sub { - my ( undef, $value ) = @_; +This option can also be set with B<--color-match>. - my @filters = @{ $App::Ack::mappings{$name} }; - if ( not $value ) { - @filters = map { $_->invert() } @filters; - } +See B for the color specifications. - push @{ $opt->{'filters'} }, @filters; - }; - }; +=item ACK_COLOR_LINENO - my $set_spec = sub { - my ( undef, $spec ) = @_; +Specifies the color of the line number when printed in B<--color> +mode. By default, it's "bold yellow". - my ( $name, $filter ) = process_filter_spec($spec); +This option can also be set with B<--color-lineno>. - $App::Ack::mappings{$name} = [ $filter ]; +See B for the color specifications. - $additional_specs{$name . '!'} = sub { - my ( undef, $value ) = @_; +=item ACK_PAGER - my @filters = @{ $App::Ack::mappings{$name} }; - if ( not $value ) { - @filters = map { $_->invert() } @filters; - } +Specifies a pager program, such as C, C or C, to which +ack will send its output. - push @{ $opt->{'filters'} }, @filters; - }; - }; +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. - my $delete_spec = sub { - my ( undef, $name ) = @_; +C overrides C if both are specified. - delete $App::Ack::mappings{$name}; - delete $additional_specs{$name . '!'}; - }; +=item ACK_PAGER_COLOR - my %type_arg_specs = ( - 'type-add=s' => $add_spec, - 'type-set=s' => $set_spec, - 'type-del=s' => $delete_spec, - ); +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. - for ( my $i = 0; $i < @{$arg_sources}; $i += 2) { - my ( $source_name, $args ) = @{$arg_sources}[ $i, $i + 1]; +If you are not on Windows, you never need to use C. - 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); - } - } +=back - $additional_specs{'k|known-types'} = sub { - my ( undef, $value ) = @_; +=head1 ACK & OTHER TOOLS - my @filters = map { @{$_} } values(%App::Ack::mappings); +=head2 Vim integration - push @{ $opt->{'filters'} }, @filters; - }; +F integrates easily with the Vim text editor. Set this in your +F<.vimrc> to use F instead of F: - return \%additional_specs; -} + set grepprg=ack\ -k -sub removed_option { - my ( $option, $explanation ) = @_; +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: - $explanation ||= ''; - return sub { - warn "Option '$option' is not valid in ack 2\n$explanation"; - exit 1; - }; -} + :grep Dumper perllib -sub get_arg_spec { - my ( $opt, $extra_specs ) = @_; +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. - my $dash_a_explanation = < - 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 ) = @_; +=head2 Emacs integration - 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 ) = @_; +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." - $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' => \$opt->{pager}, - 'noignore-directory|noignore-dir=s' - => sub { - my ( undef, $dir ) = @_; +L - # 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'"); - } +=head2 TextMate integration - @{ $opt->{idirs} } = grep { - $_ ne $dir - } @{ $opt->{idirs} }; +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" - 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 ) = @_; +=head2 Shell and Return Code - my $cb_value = 1; - if ( $value =~ s/^no// ) { - $cb_value = 0; - } +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 $callback = $extra_specs->{ $value . '!' }; +(Shell exit code 1 is C<$?=256> in perl with C or backticks.) - 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} = '-' }, +The I code 2 for errors is not used. - '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 -} +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 process_other { - my ( $opt, $extra_specs, $arg_sources ) = @_; +=cut - 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', - ); +=head1 DEBUGGING ACK PROBLEMS - my $argv_source; - my $is_help_types_active; +If ack gives you output you're not expecting, start with a few simple steps. - for ( my $i = 0; $i < @{$arg_sources}; $i += 2 ) { - my ( $source_name, $args ) = @{$arg_sources}[ $i, $i + 1 ]; +=head2 Use B<--noenv> - if ( $source_name eq 'ARGV' ) { - $argv_source = $args; - last; - } - } +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>. - if ( $argv_source ) { # this *should* always be true, but you never know... - my @copy = @{$argv_source}; - local @ARGV = @copy; +=head2 Use B<-f> to see what files have been selected - Getopt::Long::Configure('pass_through'); +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. - Getopt::Long::GetOptions( - 'help-types' => \$is_help_types_active, - ); +=head2 Use B<--dump> - Getopt::Long::Configure('no_pass_through'); - } +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. - my $arg_specs = get_arg_spec($opt, $extra_specs); +=head1 TIPS - for ( my $i = 0; $i < @{$arg_sources}; $i += 2) { - my ($source_name, $args) = @{$arg_sources}[$i, $i + 1]; +=head2 Use the F<.ackrc> file. - 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 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. - # XXX We need to check on a -- in the middle of a non-ARGV source +=head2 Use F<-f> for working with big codesets - return; -} +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: -sub should_dump_options { - my ( $sources ) = @_; + # Change all "this" to "that" in all Perl files in a tree. + ack -f --perl | xargs perl -p -i -e's/this/that/g' - 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; -} +or if you prefer: -sub explode_sources { - my ( $sources ) = @_; + perl -p -i -e's/this/that/g' $(ack -f --perl) - my @new_sources; +=head2 Use F<-Q> when in doubt about metacharacters - Getopt::Long::Configure('default', 'pass_through', 'no_auto_help', 'no_auto_version'); +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 %opt; - my $arg_spec = get_arg_spec(\%opt); +=head2 Use ack to watch log files - my $add_type = sub { - my ( undef, $arg ) = @_; +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. - # XXX refactor? - if ( $arg =~ /(\w+)=/) { - $arg_spec->{$1} = sub {}; - } - else { - ( $arg ) = split /:/, $arg; - $arg_spec->{$arg} = sub {}; - } - }; + ack -Q aa.bb.cc.dd /path/to/access.log | ack -Q -B5 troublesome.gif - my $del_type = sub { - my ( undef, $arg ) = @_; +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. - delete $arg_spec->{$arg}; - }; +=head2 Examples of F<--output> - 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--; +Following variables are useful in the expansion string: - 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} ); +=over 4 - push @new_sources, $name, \@copy; - } - } +=item C<$&> - return \@new_sources; -} +The whole string matched by PATTERN. -sub compare_opts { - my ( $a, $b ) = @_; +=item C<$1>, C<$2>, ... - my $first_a = $a->[0]; - my $first_b = $b->[0]; +The contents of the 1st, 2nd ... bracketed group in PATTERN. - $first_a =~ s/^--?//; - $first_b =~ s/^--?//; +=item C<$`> - return $first_a cmp $first_b; -} +The string before the match. -sub dump_options { - my ( $sources ) = @_; +=item C<$'> - $sources = explode_sources($sources); +The string after the match. - my %opts_by_source; - my @source_names; +=back - 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; - } +For more details and other variables see +L. - foreach my $name (@source_names) { - my $contents = $opts_by_source{$name}; +This example shows how to add text around a particular pattern +(in this case adding _ around word with "e") - print $name, "\n"; - print '=' x length($name), "\n"; - print ' ', join(' ', @{$_}), "\n" foreach sort { compare_opts($a, $b) } @{$contents}; - } + 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; -} +This shows how to pick out particular parts of a match using ( ) within regular expression. -sub remove_default_options_if_needed { - my ( $sources ) = @_; + ack '=head(\d+)\s+(.*)' --output=' $1 : $2' + input file contains "=head1 NAME" + output "1 : NAME" - my $default_index; +=head2 Share your knowledge - foreach my $index ( 0 .. $#$sources ) { - if ( $sources->[$index] eq 'Defaults' ) { - $default_index = $index; - last; - } - } +Join the ack-users mailing list. Send me your tips and I may add +them here. - return $sources unless defined $default_index; +=head1 FAQ - my $should_remove = 0; +=head2 Why isn't ack finding a match in (some file)? - 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', - ); +Probably because it's of a type that ack doesn't recognize. ack's +searching behavior is driven by filetype. B - foreach my $index ( $default_index + 2 .. $#$sources ) { - next if $index % 2 != 0; +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. - my ( $name, $args ) = @{$sources}[ $index, $index + 1 ]; +=head2 Wouldn't it be great if F did search & replace? - 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, - ); - } - } +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. - Getopt::Long::Configure('default'); - Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); +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 $sources unless $should_remove; + $ perl -i -p -e's/foo/bar/g' $(ack -f --php) - my @copy = @{$sources}; - splice @copy, $default_index, 2; - return \@copy; -} +=head2 Can I make ack recognize F<.xyz> files? -sub check_for_mutually_exclusive_options { - my ( $arg_sources ) = @_; +Yes! Please see L. If you think +that F should recognize a type by default, please see +L. - my %mutually_exclusive_with; - my @copy = @{$arg_sources}; +=head2 There's already a program/package called ack. - for(my $i = 0; $i < @INVALID_COMBINATIONS; $i += 2) { - my ( $lhs, $rhs ) = @INVALID_COMBINATIONS[ $i, $i + 1 ]; +Yes, I know. - 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; - } - } - } +=head2 Why is it called ack if it's called ack-grep? - while( @copy ) { - my %set_opts; +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 ( $source_name, $args ) = splice @copy, 0, 2; - $args = ref($args) ? [ @{$args} ] : [ Text::ParseWords::shellwords($args) ]; +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. - foreach my $opt ( @{$args} ) { - next unless $opt =~ /^[-+]/; - last if $opt eq '--'; +To do that, run this with F or as root: - if( $opt =~ /^(.*)=/ ) { - $opt = $1; - } - elsif ( $opt =~ /^(-[^-]).+/ ) { - $opt = $1; - } + ln -s /usr/bin/ack-grep /usr/bin/ack - $set_opts{ $opt } = 1; +Alternatively, you could use a shell alias: - my $mutex_opts = $mutually_exclusive_with{ $opt }; + # bash/zsh + alias ack=ack-grep + + # csh + alias ack ack-grep + +=head2 What does F mean? + +Nothing. I wanted a name that was easy to type and that you could +pronounce as a single syllable. + +=head2 Can I do multi-line regexes? + +No, ack does not support regexes that match multiple lines. Doing +so would require reading in the entire file at a time. - next unless $mutex_opts; +If you want to see lines near your match, use the C<--A>, C<--B> +and C<--C> switches for displaying context. - foreach my $mutex_opt ( @{$mutex_opts} ) { - if($set_opts{ $mutex_opt }) { - die "Options '$mutex_opt' and '$opt' are mutually exclusive\n"; - } - } - } - } -} +=head2 Why is ack telling me I have an invalid option when searching for C<+foo>? -sub process_args { - my $arg_sources = \@_; +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!) - my %opt; +=head2 Why does C<"ack '.{40000,}'"> fail? Isn't that a valid regex? - check_for_mutually_exclusive_options($arg_sources); +The Perl language limits the repetition quanitifier to 32K. You +can search for C<.{32767}> but not C<.{32768}>. - $arg_sources = remove_default_options_if_needed($arg_sources); +=head1 ACKRC LOCATION SEMANTICS - if ( should_dump_options($arg_sources) ) { - dump_options($arg_sources); - exit(0); - } +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) - 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 ); +=over 4 - # 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} ||= []); +=item * - # 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; -} +Defaults are loaded from App::Ack::ConfigDefaults. This can be omitted +using C<--ignore-ack-defaults>. +=item * Global ackrc -sub retrieve_arg_sources { - my @arg_sources; +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>. - my $noenv; - my $ackrc; +=item * User ackrc - Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); - Getopt::Long::Configure('pass_through'); - Getopt::Long::Configure('no_auto_abbrev'); +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>. - Getopt::Long::GetOptions( - 'noenv' => \$noenv, - 'ackrc=s' => \$ackrc, - ); +=item * Project ackrc - Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); +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>. - my @files; +=item * --ackrc - 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 ); - } +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. - push @arg_sources, Defaults => [ App::Ack::ConfigDefault::options() ]; +=item * ACK_OPTIONS - foreach my $file ( @files) { - my @lines = App::Ack::ConfigFinder::read_rcfile($file); - push ( @arg_sources, $file, \@lines ) if @lines; - } +Options are then loaded from the environment variable C. This can +be omitted using C<--noenv>. - if ( $ENV{ACK_OPTIONS} && !$noenv ) { - push( @arg_sources, 'ACK_OPTIONS' => $ENV{ACK_OPTIONS} ); - } +=item * Command line - push( @arg_sources, 'ARGV' => [ @ARGV ] ); +Options are then loaded from the command line. - return @arg_sources; -} +=back -1; # End of App::Ack::ConfigLoader -package App::Ack::ConfigDefault; +=head1 DIFFERENCES BETWEEN ACK 1.X AND ACK 2.X -use warnings; -use strict; +A lot of changes were made for ack 2; here is a list of them. -sub options { - my @options = split( /\n/, _options_block() ); - @options = grep { /./ && !/^#/ } @options; +=head2 GENERAL CHANGES - return @options; -} +=over 4 -sub _options_block { - return <<'HERE'; -# This is the default ackrc for ack 2.0 +=item * -# 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. +When no selectors are specified, ack 1.x only searches through files that +it can map to a file type. ack 2.x, by contrast, will search through +every regular, non-binary file that is not explicitly ignored via +B<--ignore-file> or B<--ignore-dir>. This is similar to the behavior of the +B<-a/--all> option in ack 1.x. +=item * -# Directories to ignore -# Bazaar ---ignore-directory=is:.bzr +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. -# Codeville ---ignore-directory=is:.cdv +=item * -# Interface Builder ---ignore-directory=is:~.dep ---ignore-directory=is:~.dot ---ignore-directory=is:~.nib ---ignore-directory=is:~.plst +ack now loads multiple ackrc files; see L for +details. -# Git ---ignore-directory=is:.git +=item * -# Mercurial ---ignore-directory=is:.hg +ack's default filter definitions aren't special; you may tell ack to +completely disregard them if you don't like them. -# quilt ---ignore-directory=is:.pc +=back -# Subversion ---ignore-directory=is:.svn +=head2 REMOVED OPTIONS -# Monotone ---ignore-directory=is:_MTN +=over 4 -# CVS ---ignore-directory=is:CVS +=item * -# RCS ---ignore-directory=is:RCS +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. -# SCCS ---ignore-directory=is:SCCS +=item * -# darcs ---ignore-directory=is:_darcs +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. -# Vault/Fortress ---ignore-directory=is:_sgbak +=item * -# autoconf ---ignore-directory=is:autom4te.cache +The B<--binary> option has been removed. -# Perl module building ---ignore-directory=is:blib ---ignore-directory=is:_build +=item * -# Perl Devel::Cover module's output directory ---ignore-directory=is:cover_db +The B<--skipped> option has been removed. +=item * +The B<--text> option has been removed. -# Files to ignore -# Backup files ---ignore-file=ext:bak ---ignore-file=match:/~$/ +=item * -# Emacs swap files ---ignore-file=match:/^#.+#$/ +The B<--invert-file-match> option has been removed. Instead, you may +use B<-v> with B<-g>. -# vi/vim swap files ---ignore-file=match:/[._].*\.swp$/ +=back -# core dumps ---ignore-file=match:/core\.\d+$/ +=head2 CHANGED OPTIONS -# minified Javascript ---ignore-file=match:/[.]min[.]js$/ ---ignore-file=match:/[.]js[.]min$/ +=over 4 -# minified CSS ---ignore-file=match:/[.]min[.]css$/ ---ignore-file=match:/[.]css[.]min$/ +=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>. -# Filetypes defined +=back -# Perl http://perl.org/ ---type-add=perl:ext:pl,pm,pod,t ---type-add=perl:firstlinematch:/^#!.*\bperl/ +=head2 ADDED OPTIONS -# 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 * -# CMake http://www.cmake.org/ ---type-add=cmake:is:CMakeLists.txt ---type-add=cmake:ext:cmake +B<--files-from> was added so that a user may submit a list of filenames as +a list of files to search. -# Actionscript ---type-add=actionscript:ext:as,mxml +=item * -# Ada http://www.adaic.org/ ---type-add=ada:ext:ada,adb,ads +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. -# ASP http://msdn.microsoft.com/en-us/library/aa286483.aspx ---type-add=asp:ext:asp +=item * -# ASP.Net http://www.asp.net/ ---type-add=aspx:ext:master,ascx,asmx,aspx,svc +B<-s> was added to tell ack to suppress error messages about non-existent or +unreadable files. -# Assembly ---type-add=asm:ext:asm,s +=item * -# Batch ---type-add=batch:ext:bat,cmd +B<--ignore-directory> and B<--noignore-directory> were added as aliases for +B<--ignore-dir> and B<--noignore-dir> respectively. -# ColdFusion http://en.wikipedia.org/wiki/ColdFusion ---type-add=cfmx:ext:cfc,cfm,cfml +=item * -# Clojure http://clojure.org/ ---type-add=clojure:ext:clj +B<--ignore-file> was added so that users may specify patterns of files to +ignore (ex. /.*~$/). -# C -# .xs are Perl C files ---type-add=cc:ext:c,h,xs +=item * -# C header files ---type-add=hh:ext:h +B<--dump> was added to allow users to easily find out which options are +set where. -# C++ ---type-add=cpp:ext:cpp,cc,cxx,m,hpp,hh,h,hxx +=item * -# C# ---type-add=csharp:ext:cs +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. -# CSS http://www.w3.org/Style/CSS/ ---type-add=css:ext:css +=item * -# Dart http://www.dartlang.org/ ---type-add=dart:ext:dart +B<--type-del> was added to selectively remove file type definitions. -# Delphi http://en.wikipedia.org/wiki/Embarcadero_Delphi ---type-add=delphi:ext:pas,int,dfm,nfm,dof,dpk,dproj,groupproj,bdsgroup,bdsproj +=item * -# Emacs Lisp http://www.gnu.org/software/emacs ---type-add=elisp:ext:el +B<--ignore-ack-defaults> was added so that users may ignore ack's default +options in favor of their own. -# Erlang http://www.erlang.org/ ---type-add=erlang:ext:erl,hrl +=item * -# Fortran http://en.wikipedia.org/wiki/Fortran ---type-add=fortran:ext:f,f77,f90,f95,f03,for,ftn,fpp +B<--bar> was added so ack users may consult Admiral Ackbar. -# Google Go http://golang.org/ ---type-add=go:ext:go +=back -# Groovy http://groovy.codehaus.org/ ---type-add=groovy:ext:groovy,gtmpl,gpp,grunit,gradle +=head1 AUTHOR -# Haskell http://www.haskell.org/ ---type-add=haskell:ext:hs,lhs +Andy Lester, C<< >> -# HTML ---type-add=html:ext:htm,html +=head1 BUGS -# Java http://www.oracle.com/technetwork/java/index.html ---type-add=java:ext:java,properties +Please report any bugs or feature requests to the issues list at +Github: L -# JavaScript ---type-add=js:ext:js +=head1 ENHANCEMENTS -# JSP http://www.oracle.com/technetwork/java/javaee/jsp/index.html ---type-add=jsp:ext:jsp,jspx,jhtm,jhtml +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. -# Common Lisp http://common-lisp.net/ ---type-add=lisp:ext:lisp,lsp +There is a list of enhancements I want to make to F in the ack +issues list at Github: L -# Lua http://www.lua.org/ ---type-add=lua:ext:lua +Patches are always welcome, but patches with tests get the most +attention. -# Objective-C ---type-add=objc:ext:m,h +=head1 SUPPORT -# Objective-C++ ---type-add=objcpp:ext:mm,h +Support for and information about F can be found at: -# OCaml http://caml.inria.fr/ ---type-add=ocaml:ext:ml,mli +=over 4 -# Parrot http://www.parrot.org/ ---type-add=parrot:ext:pir,pasm,pmc,ops,pod,pg,tg +=item * The ack homepage -# PHP http://www.php.net/ ---type-add=php:ext:php,phpt,php3,php4,php5,phtml ---type-add=php:firstlinematch:/^#!.*\bphp/ +L -# Plone http://plone.org/ ---type-add=plone:ext:pt,cpt,metadata,cpy,py +=item * The ack-users mailing list -# Python http://www.python.org/ ---type-add=python:ext:py ---type-add=python:firstlinematch:/^#!.*\bpython/ +L -# R http://www.r-project.org/ ---type-add=rr:ext:R +=item * The ack issues list at Github -# 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/ +L -# Rust http://www.rust-lang.org/ ---type-add=rust:ext:rs +=item * AnnoCPAN: Annotated CPAN documentation -# Scala http://www.scala-lang.org/ ---type-add=scala:ext:scala +L -# Scheme http://groups.csail.mit.edu/mac/projects/scheme/ ---type-add=scheme:ext:scm,ss +=item * CPAN Ratings -# 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/ +L -# Smalltalk http://www.smalltalk.org/ ---type-add=smalltalk:ext:st +=item * Search CPAN -# SQL http://www.iso.org/iso/catalogue_detail.htm?csnumber=45498 ---type-add=sql:ext:sql,ctl +L -# Tcl http://www.tcl.tk/ ---type-add=tcl:ext:tcl,itcl,itk +=item * Git source repository -# LaTeX http://www.latex-project.org/ ---type-add=tex:ext:tex,cls,sty +L -# Template Toolkit http://template-toolkit.org/ ---type-add=tt:ext:tt,tt2,ttml +=back -# Visual Basic ---type-add=vb:ext:bas,cls,frm,ctl,vb,resx +=head1 ACKNOWLEDGEMENTS -# Verilog ---type-add=verilog:ext:v,vh,sv +How appropriate to have Inowledgements! -# VHDL http://www.eda.org/twiki/bin/view.cgi/P1076/WebHome ---type-add=vhdl:ext:vhd,vhdl +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. -# Vim http://www.vim.org/ ---type-add=vim:ext:vim +=head1 COPYRIGHT & LICENSE -# XML http://www.w3.org/TR/REC-xml/ ---type-add=xml:ext:xml,dtd,xsl,xslt,ent ---type-add=xml:firstlinematch:/<[?]xml/ +Copyright 2005-2013 Andy Lester. -# YAML http://yaml.org/ ---type-add=yaml:ext:yaml,yml -HERE -} +This program is free software; you can redistribute it and/or modify +it under the terms of the Artistic License v2.0. -1; +See http://www.perlfoundation.org/artistic_license_2_0 or the LICENSE.md +file that comes with the ack distribution. + +=cut -- 2.43.0