# that build ack.
#
+package File::Next;
+
use strict;
use warnings;
-use 5.008;
-
-
-# XXX Don't make this so brute force
-# See also: https://github.com/petdance/ack2/issues/89
-
-use Getopt::Long 2.36 ();
-
-use Carp 1.10 ();
-our $VERSION = '2.00b06';
-# Check http://betterthangrep.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};
- }
- App::Ack::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};
- 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 "$_"} );
- }
- }
+ } # 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 { (App::Ack::get_file_id($_) => 1) } @{$start};
+sub from_file {
+ die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__);
- 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{ App::Ack::get_file_id($File::Next::name) };
+ my ($parms,@queue) = _setup( \%files_defaults, @_ );
+ my $err = $parms->{error_handler};
+ my $warn = $parms->{error_handler};
- # 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;
+ my $filename = $queue[1];
+
+ if ( !defined($filename) ) {
+ $err->( 'Must pass a filename to from_file()' );
+ return undef;
+ }
- foreach my $filter (@ifiles_filters) {
- my $resource = App::Ack::Resource::Basic->new($File::Next::name);
- return 0 if ! $resource || $filter->filter($resource);
+ my $fh;
+ if ( $filename eq '-' ) {
+ $fh = \*STDIN;
+ }
+ else {
+ if ( !open( $fh, '<', $filename ) ) {
+ $err->( "Unable to open $filename: $!" );
+ return undef;
}
- my $match_found = 1;
- if ( @{$filters} ) {
- $match_found = 0;
+ }
+ my $filter = $parms->{file_filter};
- foreach my $filter (@{$filters}) {
- my $resource = App::Ack::Resource::Basic->new($File::Next::name);
- return 0 if ! $resource;
- if ($filter->filter($resource)) {
- $match_found = 1;
- last;
- }
+ 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;
}
- }
- # Don't bother invoking inverse filters unless we consider the current resource a match
- if ( $match_found && @{$inverse_filters} ) {
- foreach my $filter ( @{$inverse_filters} ) {
- my $resource = App::Ack::Resource::Basic->new($File::Next::name);
- return 0 if ! $resource;
- if ( not $filter->filter( $resource ) ) {
- $match_found = 0;
- last;
- }
+
+ 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 $match_found;
- };
+ return wantarray ? ($dirname,$file,$fullpath) : $fullpath;
+ } # while
+ close $fh;
+
+ 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 = App::Ack::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;
+
+ my @parts = split( /\//, $path );
+
+ return $path if @parts < 2;
+
+ return File::Spec->catfile( @parts );
}
-sub main {
- my @arg_sources = App::Ack::retrieve_arg_sources();
- my $opt = App::Ack::ConfigLoader::process_args( @arg_sources );
- $App::Ack::report_bad_filenames = !$opt->{dont_report_bad_filenames};
+sub _setup {
+ my $defaults = shift;
+ my $passed_parms = ref $_[0] eq 'HASH' ? {%{+shift}} : {}; # copy parm hash
- if ( $opt->{flush} ) {
- $| = 1;
- }
+ my %passed_parms = %{$passed_parms};
- if ( not defined $opt->{color} ) {
- $opt->{color} = !App::Ack::output_to_pipe() && !$App::Ack::is_windows;
- }
- if ( not defined $opt->{heading} and not defined $opt->{break} ) {
- $opt->{heading} = $opt->{break} = !App::Ack::output_to_pipe();
+ my $parms = {};
+ for my $key ( keys %{$defaults} ) {
+ $parms->{$key} =
+ exists $passed_parms{$key}
+ ? delete $passed_parms{$key}
+ : $defaults->{$key};
}
- if ( defined($opt->{H}) || defined($opt->{h}) ) {
- $opt->{show_filename}= $opt->{H} && !$opt->{h};
+ # 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 ( my $output = $opt->{output} ) {
- $output =~ s{\\}{\\\\}g;
- $output =~ s{"}{\\"}g;
- $opt->{output} = qq{"$output"};
+ # If it's not a code ref, assume standard sort
+ if ( $parms->{sort_files} && ( ref($parms->{sort_files}) ne 'CODE' ) ) {
+ $parms->{sort_files} = \&sort_standard;
}
+ my @queue;
- my $resources;
- if ( $App::Ack::is_filter_mode && !$opt->{files_from} ) { # probably -x
- $resources = App::Ack::Resources->from_stdin( $opt );
- my $regex = $opt->{regex};
- $regex = shift @ARGV if not defined $regex;
- $opt->{regex} = App::Ack::build_regex( $regex, $opt );
- }
- 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
- }
+ for ( @_ ) {
+ my $start = reslash( $_ );
+ if (-d $start) {
+ push @queue, ($start,undef,$start);
}
else {
- my $regex = $opt->{regex};
- $regex = shift @ARGV if not defined $regex;
- $opt->{regex} = App::Ack::build_regex( $regex, $opt );
- }
- my @start;
- if ( not defined $opt->{files_from} ) {
- @start = @ARGV;
- }
- if ( !exists($opt->{show_filename}) ) {
- unless(@start == 1 && !(-d $start[0])) {
- $opt->{show_filename} = 1;
- }
+ push @queue, (undef,$start,$start);
}
+ }
- if ( defined $opt->{files_from} ) {
- $resources = App::Ack::Resources->from_file( $opt, $opt->{files_from} );
- exit 1 unless $resources;
- }
- else {
- @start = ('.') unless @start;
- foreach my $target (@start) {
- if ( !-e $target && $App::Ack::report_bad_filenames) {
- App::Ack::warn( "$target: No such file or directory" );
- }
- }
-
- $opt->{file_filter} = _compile_file_filter($opt, \@start);
- $opt->{descend_filter} = _compile_descend_filter($opt);
-
- $resources = App::Ack::Resources->from_argv( $opt, \@start );
- }
- }
- App::Ack::set_up_pager( $opt->{pager} ) if defined $opt->{pager};
-
- my $print_filenames = $opt->{show_filename};
- my $max_count = $opt->{m};
- my $ors = $opt->{print0} ? "\0" : "\n";
- my $only_first = $opt->{1};
-
- my $nmatches = 0;
- my $total_count = 0;
-RESOURCES:
- while ( my $resource = $resources->next ) {
- # XXX this variable name combined with what we're trying
- # to do makes no sense.
+ return ($parms,@queue);
+}
- # 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;
- }
+sub _candidate_files {
+ my $parms = shift;
+ my $dirname = shift;
- my $filename = $resource->name;
+ my $dh;
+ if ( !opendir $dh, $dirname ) {
+ $parms->{error_handler}->( "$dirname: $!" );
+ return;
+ }
- local $opt->{color} = 0;
+ my @newfiles;
+ my $descend_filter = $parms->{descend_filter};
+ my $follow_symlinks = $parms->{follow_symlinks};
+ my $sort_sub = $parms->{sort_files};
- App::Ack::iterate($resource, $opt, sub {
- chomp;
+ for my $file ( grep { !exists $skip_dirs{$_} } readdir $dh ) {
+ my $has_stat;
- if ( $line_numbers{$.} ) {
- App::Ack::print_line_with_context($opt, $filename, $_, $.);
- }
- elsif ( $passthru ) {
- App::Ack::print_line_with_options($opt, $filename, $_, $., ':');
- }
- return 1;
- });
+ # 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;
}
- elsif ( $opt->{count} ) {
- my $matches_for_this_file = App::Ack::count_matches_in_resource( $resource, $opt );
-
- 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 );
- }
+ if ( $descend_filter ) {
+ if ( $has_stat ? (-d _) : (-d $fullpath) ) {
+ local $File::Next::dir = $fullpath;
+ local $_ = $file;
+ next if not $descend_filter->();
}
}
- elsif ( $opt->{l} || $opt->{L} ) {
- my $is_match = App::Ack::resource_has_match( $resource, $opt );
-
- if ( $opt->{L} ? !$is_match : $is_match ) {
- App::Ack::print( $resource->name, $ors );
- ++$nmatches;
-
- last RESOURCES if $only_first;
- last RESOURCES if defined($max_count) && $nmatches >= $max_count;
- }
+ if ( $sort_sub ) {
+ push( @newfiles, [ $dirname, $file, $fullpath ] );
}
else {
- $nmatches += App::Ack::print_matches_in_resource( $resource, $opt );
- if ( $nmatches && $only_first ) {
- last RESOURCES;
- }
+ push( @newfiles, $dirname, $file, $fullpath );
}
}
+ closedir $dh;
- if ( $opt->{count} && !$opt->{show_filename} ) {
- App::Ack::print( $total_count, "\n" );
+ if ( $sort_sub ) {
+ return map { @{$_} } sort $sort_sub @newfiles;
}
- close $App::Ack::fh;
- App::Ack::exit_from_ack( $nmatches );
+ return @newfiles;
}
-=head1 NAME
-
-ack - grep-like text finder
-
-=head1 SYNOPSIS
-
- ack [options] PATTERN [FILE...]
- ack -f [options] [DIRECTORY...]
-
-=head1 DESCRIPTION
+1; # End of File::Next
+package App::Ack;
-Ack is designed as a replacement for 99% of the uses of F<grep>.
+use warnings;
+use strict;
-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<http://perldoc.perl.org/perlreref.html|perlreref>. If you don't know
-how to use regular expression but are interested in learning, you may
-consult L<http://perldoc.perl.org/perlretut.html|perlretut>. If you do not
-need or want ack to use regular expressions, please see the
-C<-Q>/C<--literal> option.
+our $VERSION;
+our $GIT_REVISION;
+our $COPYRIGHT;
+BEGIN {
+ $VERSION = '2.10';
+ $COPYRIGHT = 'Copyright 2005-2013 Andy Lester.';
+ $GIT_REVISION = q{af91cce};
+}
-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.
+our $fh;
-=head1 FILE SELECTION
+BEGIN {
+ $fh = *STDOUT;
+}
-If files are not specified for searching, either on the command
-line or piped in with the C<-x> option, I<ack> delves into
-subdirectories selecting files for searching.
-I<ack> is intelligent about the files it searches. It knows about
-certain file types, based on both the extension on the file and,
-in some cases, the contents of the file. These selections can be
-made with the B<--type> option.
+our %types;
+our %type_wanted;
+our %mappings;
+our %ignore_dirs;
-With no file selection, I<ack> searches through regular files that
-are not explicitly excluded by B<--ignore-dir> and B<--ignore-file>
-options, either present in F<ackrc> files or on the command line.
+our $is_filter_mode;
+our $output_to_pipe;
-The default options for I<ack> ignore certain files and directories. These
-include:
+our $dir_sep_chars;
+our $is_cygwin;
+our $is_windows;
-=over 4
+use File::Spec 1.00015 ();
-=item * Backup files: Files matching F<#*#> or ending with F<~>.
+BEGIN {
+ # These have to be checked before any filehandle diddling.
+ $output_to_pipe = not -t *STDOUT;
+ $is_filter_mode = -p STDIN;
-=item * Coredumps: Files matching F<core.\d+>
+ $is_cygwin = ($^O eq 'cygwin');
+ $is_windows = ($^O eq 'MSWin32');
+ $dir_sep_chars = $is_windows ? quotemeta( '\\/' ) : quotemeta( File::Spec->catfile( '', '' ) );
+}
-=item * Version control directories like F<.svn> and F<.git>.
-=back
-Run I<ack> with the C<--dump> option to see what settings are set.
+sub remove_dir_sep {
+ my $path = shift;
+ $path =~ s/[$dir_sep_chars]$//;
-However, I<ack> always searches the files given on the command line,
-no matter what type. If you tell I<ack> to search in a coredump,
-it will search in a coredump.
+ return $path;
+}
-=head1 DIRECTORY SELECTION
-I<ack> descends through the directory tree of the starting directories
-specified. If no directories are specified, the current working directory is
-used. However, it will ignore the shadow directories used by
-many version control systems, and the build directories used by the
-Perl MakeMaker system. You may add or remove a directory from this
-list with the B<--[no]ignore-dir> option. The option may be repeated
-to add/remove multiple directories from the ignore list.
-For a complete list of directories that do not get searched, run
-C<ack --dump>.
+sub warn {
+ return CORE::warn( _my_program(), ': ', @_, "\n" );
+}
-=head1 WHEN TO USE GREP
-I<ack> trumps I<grep> as an everyday tool 99% of the time, but don't
-throw I<grep> away, because there are times you'll still need it.
+sub die {
+ return CORE::die( _my_program(), ': ', @_, "\n" );
+}
-E.g., searching through huge files looking for regexes that can be
-expressed with I<grep> syntax should be quicker with I<grep>.
+sub _my_program {
+ require File::Basename;
+ return File::Basename::basename( $0 );
+}
-If your script or parent program uses I<grep> C<--quiet> or C<--silent>
-or needs exit 2 on IO error, use I<grep>.
-=head1 OPTIONS
-=over 4
+sub filetypes_supported {
+ return keys %mappings;
+}
-=item B<-A I<NUM>>, B<--after-context=I<NUM>>
+sub _get_thpppt {
+ my $y = q{_ /|,\\'!.x',=(www)=, U };
+ $y =~ tr/,x!w/\nOo_/;
+ return $y;
+}
-Print I<NUM> lines of trailing context after matching lines.
+sub _thpppt {
+ my $y = _get_thpppt();
+ App::Ack::print( "$y ack $_[0]!\n" );
+ exit 0;
+}
-=item B<-B I<NUM>>, B<--before-context=I<NUM>>
-
-Print I<NUM> lines of leading context before matching lines.
-
-=item B<--[no]break>
-
-Print a break between results from different files. On by default
-when used interactively.
-
-=item B<-C [I<NUM>]>, B<--context[=I<NUM>]>
+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
-Print I<NUM> lines (default 2) of context around matching lines.
+ 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;
+}
-=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 show_help {
+ my $help_arg = shift || 0;
-If combined with B<-h> (B<--no-filename>) ack outputs only one total
-count.
+ return show_help_types() if $help_arg =~ /^types?/;
-=item B<--color>, B<--nocolor>, B<--colour>, B<--nocolour>
+ App::Ack::print( <<"END_OF_HELP" );
+Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES]
-B<--color> highlights the matching text. B<--nocolor> supresses
-the color. This is on by default unless the output is redirected.
+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 "-".
-On Windows, this option is off by default unless the
-L<Win32::Console::ANSI> module is installed or the C<ACK_PAGER_COLOR>
-environment variable is used.
+Default switches may be specified in ACK_OPTIONS environment variable or
+an .ackrc file. If you want no dependency on the environment, turn it
+off with --noenv.
-=item B<--color-filename=I<color>>
+Example: ack -i select
-Sets the color to be used for filenames.
+Searching:
+ -i, --ignore-case Ignore case distinctions in PATTERN
+ --[no]smart-case Ignore case distinctions in PATTERN,
+ only if PATTERN contains no upper case.
+ Ignored if -i is specified
+ -v, --invert-match Invert match: select non-matching lines
+ -w, --word-regexp Force PATTERN to match only whole words
+ -Q, --literal Quote all metacharacters; PATTERN is literal
-=item B<--color-match=I<color>>
+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
-Sets the color to be used for matches.
+ -A NUM, --after-context=NUM Print NUM lines of trailing context after
+ matching lines.
+ -B NUM, --before-context=NUM Print NUM lines of leading context before
+ matching lines.
+ -C [NUM], --context[=NUM] Print NUM lines (default 2) of output context.
-=item B<--color-lineno=I<color>>
+ --print0 Print null byte as separator between filenames,
+ only works with -f, -g, -l, -L or -c.
-Sets the color to be used for line numbers.
+ -s Suppress error messages about nonexistent or
+ unreadable files.
-=item B<--[no]column>
-Show the column number of the first match. This is helpful for
-editors that can place your cursor at a given position.
+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).
-=item B<--create-ackrc>
-Dumps the default ack options to standard output. This is useful for
-when you want to customize the defaults.
+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.
-=item B<--dump>
+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.
-Writes the list of options loaded and where they came from to standard
-output. Handy for debugging.
+ --type=X Include only X files, where X is a recognized
+ filetype.
+ --type=noX Exclude X files.
+ See "ack --help-types" for supported filetypes.
-=item B<--env>, B<--noenv>
+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.
-B<--noenv> disables all environment processing. No F<.ackrc> is
-read and all environment variables are ignored. By default, F<ack>
-considers F<.ackrc> and settings in the environment.
-=item B<--flush>
+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!
-B<--flush> flushes output immediately. This is off by default
-unless ack is running interactively (when output goes to a pipe or
-file).
+Exit status is 0 if match, 1 if no match.
-=item B<-f>
+This is version $VERSION of ack.
+END_OF_HELP
-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.
+ return;
+ }
-=item B<--files-from=I<FILE>>
-The list of files to be searched is specified in I<FILE>. The list of
-files are seperated by newlines. If I<FILE> is C<->, the list is loaded
-from standard input.
-=item B<--[no]filter>
+sub show_help_types {
+ App::Ack::print( <<'END_OF_HELP' );
+Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES]
-Forces ack to act as if it were recieving input via a pipe.
+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.
-=item B<--follow>, B<--nofollow>
+Note that some extensions may appear in multiple types. For example,
+.pod files are both Perl and Parrot.
-Follow or don't follow symlinks, other than whatever starting files
-or directories were specified on the command line.
+END_OF_HELP
-This is off by default.
+ 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};
-=item B<-g I<REGEX>>
+ 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 ) );
+ }
-Print files where the relative path + filename matches I<REGEX>.
+ return;
+}
-=item B<--group>, B<--nogroup>
+sub show_man {
+ require Pod::Usage;
-B<--group> groups matches by file name. This is the default
-when used interactively.
+ Pod::Usage::pod2usage({
+ -input => $App::Ack::orig_program_name,
+ -verbose => 2,
+ -exitval => 0,
+ });
-B<--nogroup> prints one result per line, like grep. This is the
-default when output is redirected.
+ return;
+}
-=item B<-H>, B<--with-filename>
-Print the filename for each match.
+sub get_version_statement {
+ require Config;
-=item B<-h>, B<--no-filename>
+ 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 );
-Suppress the prefixing of filenames on output when multiple files are
-searched.
+ my $git_revision = $GIT_REVISION ? " (git commit $GIT_REVISION)" : '';
-=item B<--[no]heading>
+ return <<"END_OF_VERSION";
+ack ${VERSION}${git_revision}
+Running under Perl $ver at $this_perl
-Print a filename heading above each file's results. This is the default
-when used interactively.
+$copyright
-=item B<--help>, B<-?>
+This program is free software. You may modify or distribute it
+under the terms of the Artistic License v2.0.
+END_OF_VERSION
+}
-Print a short help statement.
-=item B<--help-types>, B<--help=types>
+sub print_version_statement {
+ App::Ack::print( get_version_statement() );
-Print all known types.
+ return;
+}
-=item B<-i>, B<--ignore-case>
-Ignore case in the search strings.
+sub get_copyright {
+ return $COPYRIGHT;
+}
-=item B<--ignore-ack-defaults>
-Tells ack to completely ignore the default definitions provided with ack.
-This is useful in combination with B<--create-ackrc> if you I<really> want
-to customize ack.
-
-=item B<--[no]ignore-dir=I<DIRNAME>>, B<--[no]ignore-directory=I<DIRNAME>>
+# 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;
-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).
+ if ($show_filename) {
+ App::Ack::print( $filename );
+ App::Ack::print( ':', $nmatches ) if $count;
+ }
+ else {
+ App::Ack::print( $nmatches ) if $count;
+ }
+ App::Ack::print( $ors );
-The I<DIRNAME> must always be a simple directory name. Nested
-directories like F<foo/bar> are NOT supported. You would need to
-specify B<--ignore-dir=foo> and then no files from any foo directory
-are taken into account by ack unless given explicitly on the command
-line.
+ return;
+}
-=item B<--ignore-file=I<FILTERTYPE:FILTERARGS>>
+sub print_count0 {
+ my $filename = shift;
+ my $ors = shift;
+ my $show_filename = shift;
-Ignore files matching I<FILTERTYPE:FILTERARGS>. The filters are specified
-identically to file type filters as seen in L</"Defining your own types">.
+ if ($show_filename) {
+ App::Ack::print( $filename, ':0', $ors );
+ }
+ else {
+ App::Ack::print( '0', $ors );
+ }
-=item B<-k>, B<--known-types>
+ return;
+}
-Limit selected files to those with types that ack knows about. This is
-equivalent to the default behavior found in ack 1.
+sub set_up_pager {
+ my $command = shift;
-=item B<--lines=I<NUM>>
+ return if App::Ack::output_to_pipe();
-Only print line I<NUM> of each file. Multiple lines can be given with multiple
-B<--lines> options or as a comma separated list (B<--lines=3,5,7>). B<--lines=4-7>
-also works. The lines are always output in ascending order, no matter the
-order given on the command line.
+ my $pager;
+ if ( not open( $pager, '|-', $command ) ) {
+ App::Ack::die( qq{Unable to pipe to pager "$command": $!} );
+ }
+ $fh = $pager;
-=item B<-l>, B<--files-with-matches>
+ return;
+}
-Only print the filenames of matching files, instead of the matching text.
-=item B<-L>, B<--files-without-matches>
+sub output_to_pipe {
+ return $output_to_pipe;
+}
-Only print the filenames of files that do I<NOT> match.
-=item B<--match I<REGEX>>
+sub exit_from_ack {
+ my $nmatches = shift;
-Specify the I<REGEX> 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.
+ my $rc = $nmatches ? 0 : 1;
+ exit $rc;
+}
- # search for foo and bar in given files
- ack file1 t/file* --match foo
- ack file1 t/file* --match bar
-=item B<-m=I<NUM>>, B<--max-count=I<NUM>>
-Stop reading a file after I<NUM> matches.
+1; # End of App::Ack
+package App::Ack::Resource;
-=item B<--man>
-Print this manual page.
+use warnings;
+use strict;
+use overload
+ '""' => 'name';
-=item B<-n>, B<--no-recurse>
+sub FAIL {
+ require Carp;
+ Carp::confess( 'Must be overloaded' );
+}
-No descending into subdirectories.
-=item B<-o>
+sub new {
+ return FAIL();
+}
-Show only the part of each line matching PATTERN (turns off text
-highlighting)
-=item B<--output=I<expr>>
+sub name {
+ return FAIL();
+}
-Output the evaluation of I<expr> for each line (turns off text
-highlighting)
-If PATTERN matches more than once then a line is output for each non-overlapping match.
-For more information please see the section L</"Examples of F<--output>">.
-=item B<--pager=I<program>>, B<--nopager>
+sub is_binary {
+ return FAIL();
+}
-B<--pager> directs ack's output through I<program>. This can also be specified
-via the C<ACK_PAGER> and C<ACK_PAGER_COLOR> environment variables.
-Using --pager does not suppress grouping and coloring like piping
-output on the command-line does.
+sub open {
+ return FAIL();
+}
-B<--nopager> cancels any setting in ~/.ackrc, C<ACK_PAGER> or C<ACK_PAGER_COLOR>.
-No output will be sent through a pager.
-=item B<--passthru>
+sub needs_line_scan {
+ return FAIL();
+}
-Prints all lines, whether or not they match the expression. Highlighting
-will still work, though, so it can be used to highlight matches while
-still seeing the entire file, as in:
- # Watch a log file, and highlight a certain IP address
- $ tail -f ~/access.log | ack --passthru 123.45.67.89
+sub reset {
+ return FAIL();
+}
-=item B<--print0>
-Only works in conjunction with -f, -g, -l or -c (filename output). The filenames
-are output separated with a null byte instead of the usual newline. This is
-helpful when dealing with filenames that contain whitespace, e.g.
+sub close {
+ return FAIL();
+}
- # remove all files of type html
- ack -f --html --print0 | xargs -0 rm -f
-=item B<-Q>, B<--literal>
+sub clone {
+ return FAIL();
+}
-Quote all metacharacters in PATTERN, it is treated as a literal.
-=item B<-r>, B<-R>, B<--recurse>
+sub firstliney {
+ return FAIL();
+}
-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.
+1;
+package App::Ack::Resources;
-=item B<-s>
-Suppress error messages about nonexistent or unreadable files. This is taken
-from fgrep.
-=item B<--[no]smart-case>, B<--no-smart-case>
+use warnings;
+use strict;
-Ignore case in the search strings if PATTERN contains no uppercase
-characters. This is similar to C<smartcase> in vim. This option is
-off by default.
-B<-i> always overrides this option.
+sub from_argv {
+ my $class = shift;
+ my $opt = shift;
+ my $start = shift;
-=item B<--sort-files>
+ my $self = bless {}, $class;
-Sorts the found files lexicographically. Use this if you want your file
-listings to be deterministic between runs of I<ack>.
+ my $file_filter = undef;
+ my $descend_filter = $opt->{descend_filter};
-=item B<--show-types>
+ if( $opt->{n} ) {
+ $descend_filter = sub {
+ return 0;
+ };
+ }
-Outputs the filetypes that ack associates with each file.
+ $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} );
-Works with B<-f> and B<-g> options.
+ return $self;
+}
-=item B<--type=TYPE>, B<--type=noTYPE>
-Specify the types of files to include or exclude from a search.
-TYPE is a filetype, like I<perl> or I<xml>. B<--type=perl> can
-also be specified as B<--perl>, and B<--type=noperl> can be done
-as B<--noperl>.
+sub from_file {
+ my $class = shift;
+ my $opt = shift;
+ my $file = shift;
-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.
+ 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;
-Type specifications can be repeated and are ORed together.
+ return bless {
+ iter => $iter,
+ }, $class;
+}
-See I<ack --help=types> for a list of valid types.
+# This is for reading input lines from STDIN, not the list of files from STDIN
+sub from_stdin {
+ my $class = shift;
+ my $opt = shift;
-=item B<--type-add I<TYPE>:I<FILTER>:I<FILTERARGS>>
+ my $self = bless {}, $class;
-Files with the given FILTERARGS applied to the given FILTER
-are recognized as being of (the existing) type TYPE.
-See also L</"Defining your own types">.
+ my $has_been_called = 0;
+ $self->{iter} = sub {
+ if ( !$has_been_called ) {
+ $has_been_called = 1;
+ return '-';
+ }
+ return;
+ };
-=item B<--type-set I<TYPE>:I<FILTER>:I<FILTERARGS>>
+ return $self;
+}
-Files with the given FILTERARGS applied to the given FILTER are recognized as
-being of type TYPE. This replaces an existing definition for type TYPE. See
-also L</"Defining your own types">.
+sub next {
+ my $self = shift;
-=item B<--type-del I<TYPE>>
+ my $file = $self->{iter}->() or return;
-The filters associated with TYPE are removed from Ack, and are no longer considered
-for searches.
+ return App::Ack::Resource::Basic->new( $file );
+}
-=item B<-v>, B<--invert-match>
-
-Invert match: select non-matching lines
+1;
+package App::Ack::Resource::Basic;
-=item B<--version>
-Display version and copyright information.
+use warnings;
+use strict;
-=item B<-w>, B<--word-regexp>
+use Fcntl ();
-Force PATTERN to match only whole words. The PATTERN is wrapped with
-C<\b> metacharacters.
+BEGIN {
+ our @ISA = 'App::Ack::Resource';
+}
-=item B<-x>
-An abbreviation for B<--files-from=->; the list of files to search are read
-from standard input, with one line per file.
+sub new {
+ my $class = shift;
+ my $filename = shift;
-=item B<-1>
+ my $self = bless {
+ filename => $filename,
+ fh => undef,
+ opened => 0,
+ }, $class;
-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.
+ if ( $self->{filename} eq '-' ) {
+ $self->{fh} = *STDIN;
+ $self->{opened} = 1;
+ }
-=item B<--thpppt>
+ return $self;
+}
-Display the all-important Bill The Cat logo. Note that the exact
-spelling of B<--thpppppt> is not important. It's checked against
-a regular expression.
-=item B<--bar>
+sub name {
+ return $_[0]->{filename};
+}
-Check with the admiral for traps.
-=back
-=head1 THE .ackrc FILE
+sub needs_line_scan {
+ my $self = shift;
+ my $opt = shift;
-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:
+ return 1 if $opt->{v};
- # Always sort the files
- --sort-files
+ my $size = -s $self->{fh};
+ if ( $size == 0 ) {
+ return 0;
+ }
+ elsif ( $size > 100_000 ) {
+ return 1;
+ }
- # Always color, even if piping to a another program
- --color
+ 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 );
- # Use "less -r" as my pager
- --pager=less -r
+ my $regex = $opt->{regex};
+ return $buffer =~ /$regex/m;
+}
-Note that arguments with spaces in them do not need to be quoted,
-as they are not interpreted by the shell. Basically, each I<line>
-in the F<.ackrc> file is interpreted as one element of C<@ARGV>.
-F<ack> looks in several locations for F<.ackrc> files; the searching
-process is detailed in L</"ACKRC LOCATION SEMANTICS">. These
-files are not considered if B<--noenv> is specified on the command line.
+sub reset {
+ my $self = shift;
-=head1 Defining your own types
+ # return if we haven't opened the file yet
+ if ( !defined($self->{fh}) ) {
+ return;
+ }
-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.
+ if( !seek( $self->{fh}, 0, 0 ) && $App::Ack::report_bad_filenames ) {
+ App::Ack::warn( "$self->{filename}: $!" );
+ }
-I<ack --perl foo> searches for foo in all perl files. I<ack --help=types>
-tells you, that perl files are files ending
-in .pl, .pm, .pod or .t. So what if you would like to include .xs
-files as well when searching for --perl files? I<ack --type-add perl:ext:xs --perl foo>
-does this for you. B<--type-add> appends
-additional extensions to an existing type.
+ return;
+}
-If you want to define a new type, or completely redefine an existing
-type, then use B<--type-set>. I<ack --type-set eiffel:ext:e,eiffel> defines
-the type I<eiffel> to include files with
-the extensions .e or .eiffel. So to search for all eiffel files
-containing the word Bertrand use I<ack --type-set eiffel:ext:e,eiffel --eiffel Bertrand>.
-As usual, you can also write B<--type=eiffel>
-instead of B<--eiffel>. Negation also works, so B<--noeiffel> excludes
-all eiffel files from a search. Redefining also works: I<ack --type-set cc:ext:c,h>
-and I<.xs> files no longer belong to the type I<cc>.
-When defining your own types in the F<.ackrc> file you have to use
-the following:
+sub close {
+ my $self = shift;
- --type-set=eiffel:ext:e,eiffel
+ # return if we haven't opened the file yet
+ if ( !defined($self->{fh}) ) {
+ return;
+ }
-or writing on separate lines
+ if ( !close($self->{fh}) && $App::Ack::report_bad_filenames ) {
+ App::Ack::warn( $self->name() . ": $!" );
+ }
- --type-set
- eiffel:ext:e,eiffel
+ $self->{opened} = 0;
-The following does B<NOT> work in the F<.ackrc> file:
+ return;
+}
- --type-set eiffel:ext:e,eiffel
+sub clone {
+ my ( $self ) = @_;
-In order to see all currently defined types, use I<--help-types>, e.g.
-I<ack --type-set backup:ext:bak --type-add perl:ext:perl --help-types>
+ return __PACKAGE__->new($self->name);
+}
-In addition to filtering based on extension (like ack 1.x allowed), ack 2
-offers additional filter types. The generic syntax is
-I<--type-set TYPE:FILTER:FILTERARGS>; I<FILTERARGS> depends on the value
-of I<FILTER>.
+sub firstliney {
+ my ( $self ) = @_;
-=over 4
+ my $fh = $self->open();
-=item is:I<FILENAME>
+ 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;
+ }
-I<is> filters match the target filename exactly. It takes exactly one
-argument, which is the name of the file to match.
+ $self->close;
-Example:
+ return $self->{firstliney};
+}
- --type-set make:is:Makefile
+sub open {
+ my ( $self ) = @_;
-=item ext:I<EXTENSION>[,I<EXTENSION2>[,...]]
+ return $self->{fh} if $self->{opened};
-I<ext> filters match the extension of the target file against a list
-of extensions. No leading dot is needed for the extensions.
+ unless ( open $self->{fh}, '<', $self->{filename} ) {
+ return;
+ }
-Example:
+ $self->{opened} = 1;
- --type-set perl:ext:pl,pm,t
+ return $self->{fh};
+}
-=item match:I<REGEX>
+1;
+package App::Ack::ConfigDefault;
-I<match> filters match the target filename against a regular expression.
-The regular expression is made case insensitive for the search.
+use warnings;
+use strict;
-Example:
+sub options {
+ my @options = split( /\n/, _options_block() );
+ @options = grep { /./ && !/^#/ } @options;
- --type-set make:match:/(gnu)?makefile/
+ return @options;
+}
-=item firstlinematch:I<REGEX>
+sub _options_block {
+ return <<'HERE';
+# This is the default ackrc for ack 2.0
-I<firstlinematch> matches the first line of the target file against a
-regular expression. Like I<match>, the regular expression is made
-case insensitive.
+# 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.
-Example:
- --type-add perl:firstlinematch:/perl/
+### Directories to ignore
-=back
+# Bazaar
+--ignore-directory=is:.bzr
-More filter types may be made available in the future.
+# Codeville
+--ignore-directory=is:.cdv
-=head1 ENVIRONMENT VARIABLES
+# Interface Builder
+--ignore-directory=is:~.dep
+--ignore-directory=is:~.dot
+--ignore-directory=is:~.nib
+--ignore-directory=is:~.plst
-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.
+# Git
+--ignore-directory=is:.git
-=over 4
+# Mercurial
+--ignore-directory=is:.hg
-=item ACKRC
+# quilt
+--ignore-directory=is:.pc
-Specifies the location of the user's F<.ackrc> file. If this file doesn't
-exist, F<ack> looks in the default location.
+# Subversion
+--ignore-directory=is:.svn
-=item ACK_OPTIONS
+# Monotone
+--ignore-directory=is:_MTN
-This variable specifies default options to be placed in front of
-any explicit options on the command line.
+# CVS
+--ignore-directory=is:CVS
-=item ACK_COLOR_FILENAME
+# RCS
+--ignore-directory=is:RCS
-Specifies the color of the filename when it's printed in B<--group>
-mode. By default, it's "bold green".
+# SCCS
+--ignore-directory=is:SCCS
-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.
+# darcs
+--ignore-directory=is:_darcs
-This option can also be set with B<--color-filename>.
+# Vault/Fortress
+--ignore-directory=is:_sgbak
-=item ACK_COLOR_MATCH
+# autoconf
+--ignore-directory=is:autom4te.cache
-Specifies the color of the matching text when printed in B<--color>
-mode. By default, it's "black on_yellow".
+# Perl module building
+--ignore-directory=is:blib
+--ignore-directory=is:_build
-This option can also be set with B<--color-match>.
+# Perl Devel::Cover module's output directory
+--ignore-directory=is:cover_db
-See B<ACK_COLOR_FILENAME> for the color specifications.
+# Node modules created by npm
+--ignore-directory=is:node_modules
-=item ACK_COLOR_LINENO
+# CMake cache
+--ignore-directory=is:CMakeFiles
-Specifies the color of the line number when printed in B<--color>
-mode. By default, it's "bold yellow".
+### Files to ignore
-This option can also be set with B<--color-lineno>.
+# Backup files
+--ignore-file=ext:bak
+--ignore-file=match:/~$/
-See B<ACK_COLOR_FILENAME> for the color specifications.
+# Emacs swap files
+--ignore-file=match:/^#.+#$/
-=item ACK_PAGER
+# vi/vim swap files
+--ignore-file=match:/[._].*\.swp$/
-Specifies a pager program, such as C<more>, C<less> or C<most>, to which
-ack will send its output.
+# core dumps
+--ignore-file=match:/core\.\d+$/
-Using C<ACK_PAGER> does not suppress grouping and coloring like
-piping output on the command-line does, except that on Windows
-ack will assume that C<ACK_PAGER> does not support color.
+# minified Javascript
+--ignore-file=match:/[.-]min[.]js$/
+--ignore-file=match:/[.]js[.]min$/
-C<ACK_PAGER_COLOR> overrides C<ACK_PAGER> if both are specified.
+# minified CSS
+--ignore-file=match:/[.]min[.]css$/
+--ignore-file=match:/[.]css[.]min$/
-=item ACK_PAGER_COLOR
+# PDFs, because they pass Perl's -T detection
+--ignore-file=ext:pdf
-Specifies a pager program that understands ANSI color sequences.
-Using C<ACK_PAGER_COLOR> does not suppress grouping and coloring
-like piping output on the command-line does.
+# Common graphics, just as an optimization
+--ignore-file=ext:gif,jpg,jpeg,png
-If you are not on Windows, you never need to use C<ACK_PAGER_COLOR>.
-=back
+### Filetypes defined
-=head1 ACK & OTHER TOOLS
+# Perl http://perl.org/
+--type-add=perl:ext:pl,pm,pod,t,psgi
+--type-add=perl:firstlinematch:/^#!.*\bperl/
-=head2 Vim integration
+# Perl tests
+--type-add=perltest:ext:t
-F<ack> integrates easily with the Vim text editor. Set this in your
-F<.vimrc> to use F<ack> instead of F<grep>:
+# 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
- set grepprg=ack\ -k
+# Rakefiles http://rake.rubyforge.org/
+--type-add=rake:is:Rakefile
-That example uses C<-k> to search through only files of the types ack
-knows about, but you may use other default flags. Now you can search
-with F<ack> and easily step through the results in Vim:
+# CMake http://www.cmake.org/
+--type-add=cmake:is:CMakeLists.txt
+--type-add=cmake:ext:cmake
- :grep Dumper perllib
+# Actionscript
+--type-add=actionscript:ext:as,mxml
-=head2 Emacs integration
+# Ada http://www.adaic.org/
+--type-add=ada:ext:ada,adb,ads
-Phil Jackson put together an F<ack.el> extension that "provides a
-simple compilation mode ... has the ability to guess what files you
-want to search for based on the major-mode."
+# ASP http://msdn.microsoft.com/en-us/library/aa286483.aspx
+--type-add=asp:ext:asp
-L<http://www.shellarchive.co.uk/content/emacs.html>
+# ASP.Net http://www.asp.net/
+--type-add=aspx:ext:master,ascx,asmx,aspx,svc
-=head2 TextMate integration
+# Assembly
+--type-add=asm:ext:asm,s
-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<http://www.simplicidade.org/notes/archives/2008/03/search_in_proje.html>"
+# Batch
+--type-add=batch:ext:bat,cmd
-=head2 Shell and Return Code
+# ColdFusion http://en.wikipedia.org/wiki/ColdFusion
+--type-add=cfmx:ext:cfc,cfm,cfml
-For greater compatibility with I<grep>, I<ack> in normal use returns
-shell return or exit code of 0 only if something is found and 1 if
-no match is found.
+# Clojure http://clojure.org/
+--type-add=clojure:ext:clj
-(Shell exit code 1 is C<$?=256> in perl with C<system> or backticks.)
+# C
+# .xs are Perl C files
+--type-add=cc:ext:c,h,xs
-The I<grep> code 2 for errors is not used.
+# C header files
+--type-add=hh:ext:h
-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.
+# CoffeeScript http://coffeescript.org/
+--type-add=coffeescript:ext:coffee
-=cut
+# C++
+--type-add=cpp:ext:cpp,cc,cxx,m,hpp,hh,h,hxx
-=head1 DEBUGGING ACK PROBLEMS
+# C#
+--type-add=csharp:ext:cs
-If ack gives you output you're not expecting, start with a few simple steps.
+# CSS http://www.w3.org/Style/CSS/
+--type-add=css:ext:css
-=head2 Use B<--noenv>
+# Dart http://www.dartlang.org/
+--type-add=dart:ext:dart
-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>.
+# Delphi http://en.wikipedia.org/wiki/Embarcadero_Delphi
+--type-add=delphi:ext:pas,int,dfm,nfm,dof,dpk,dproj,groupproj,bdsgroup,bdsproj
-=head2 Use B<-f> to see what files have been selected
+# Elixir http://elixir-lang.org/
+--type-add=elixir:ext:ex,exs
-Ack's B<-f> was originally added as a debugging tool. If ack is
-not finding matches you think it should find, run F<ack -f> to see
-what files have been selected. You can also add the C<--show-types>
-options to show the type of each file selected.
+# Emacs Lisp http://www.gnu.org/software/emacs
+--type-add=elisp:ext:el
-=head2 Use B<--dump>
+# Erlang http://www.erlang.org/
+--type-add=erlang:ext:erl,hrl
-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.
+# Fortran http://en.wikipedia.org/wiki/Fortran
+--type-add=fortran:ext:f,f77,f90,f95,f03,for,ftn,fpp
-=head1 TIPS
+# Google Go http://golang.org/
+--type-add=go:ext:go
-=head2 Use the F<.ackrc> file.
+# Groovy http://groovy.codehaus.org/
+--type-add=groovy:ext:groovy,gtmpl,gpp,grunit,gradle
-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.
+# Haskell http://www.haskell.org/
+--type-add=haskell:ext:hs,lhs
-=head2 Use F<-f> for working with big codesets
+# HTML
+--type-add=html:ext:htm,html
-Ack does more than search files. C<ack -f --perl> will create a
-list of all the Perl files in a tree, ideal for sending into F<xargs>.
-For example:
+# Java http://www.oracle.com/technetwork/java/index.html
+--type-add=java:ext:java,properties
- # Change all "this" to "that" in all Perl files in a tree.
- ack -f --perl | xargs perl -p -i -e's/this/that/g'
+# JavaScript
+--type-add=js:ext:js
-or if you prefer:
+# JSP http://www.oracle.com/technetwork/java/javaee/jsp/index.html
+--type-add=jsp:ext:jsp,jspx,jhtm,jhtml
- perl -p -i -e's/this/that/g' $(ack -f --perl)
+# JSON http://www.json.org/
+--type-add=json:ext:json
-=head2 Use F<-Q> when in doubt about metacharacters
+# Less http://www.lesscss.org/
+--type-add=less:ext:less
-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...
+# Common Lisp http://common-lisp.net/
+--type-add=lisp:ext:lisp,lsp
-=head2 Use ack to watch log files
+# Lua http://www.lua.org/
+--type-add=lua:ext:lua
+--type-add=lua:firstlinematch:/^#!.*\blua(jit)?/
-Here's one I used the other day to find trouble spots for a website
-visitor. The user had a problem loading F<troublesome.gif>, so I
-took the access log and scanned it with ack twice.
+# Objective-C
+--type-add=objc:ext:m,h
- ack -Q aa.bb.cc.dd /path/to/access.log | ack -Q -B5 troublesome.gif
+# Objective-C++
+--type-add=objcpp:ext:mm,h
-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.
+# OCaml http://caml.inria.fr/
+--type-add=ocaml:ext:ml,mli
-=head2 Examples of F<--output>
+# Matlab http://en.wikipedia.org/wiki/MATLAB
+--type-add=matlab:ext:m
-Following variables are useful in the expansion string:
+# Parrot http://www.parrot.org/
+--type-add=parrot:ext:pir,pasm,pmc,ops,pod,pg,tg
-=over 4
+# PHP http://www.php.net/
+--type-add=php:ext:php,phpt,php3,php4,php5,phtml
+--type-add=php:firstlinematch:/^#!.*\bphp/
-=item C<$&>
+# Plone http://plone.org/
+--type-add=plone:ext:pt,cpt,metadata,cpy,py
-The whole string matched by PATTERN.
+# Python http://www.python.org/
+--type-add=python:ext:py
+--type-add=python:firstlinematch:/^#!.*\bpython/
-=item C<$1>, C<$2>, ...
+# R http://www.r-project.org/
+--type-add=rr:ext:R
-The contents of the 1st, 2nd ... bracketed group in PATTERN.
+# 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/
-=item C<$`>
+# Rust http://www.rust-lang.org/
+--type-add=rust:ext:rs
-The string before the match.
+# Sass http://sass-lang.com
+--type-add=sass:ext:sass,scss
-=item C<$'>
+# Scala http://www.scala-lang.org/
+--type-add=scala:ext:scala
-The string after the match.
+# Scheme http://groups.csail.mit.edu/mac/projects/scheme/
+--type-add=scheme:ext:scm,ss
-=back
+# 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/
-For more details and other variables see
-L<http://perldoc.perl.org/perlvar.html#Variables-related-to-regular-expressions|perlvar>.
+# Smalltalk http://www.smalltalk.org/
+--type-add=smalltalk:ext:st
-This example shows how to add text around a particular pattern
-(in this case adding _ around word with "e")
+# SQL http://www.iso.org/iso/catalogue_detail.htm?csnumber=45498
+--type-add=sql:ext:sql,ctl
- 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
+# Tcl http://www.tcl.tk/
+--type-add=tcl:ext:tcl,itcl,itk
-This shows how to pick out particular parts of a match using ( ) within regular expression.
+# LaTeX http://www.latex-project.org/
+--type-add=tex:ext:tex,cls,sty
- ack '=head(\d+)\s+(.*)' --output=' $1 : $2'
- input file contains "=head1 NAME"
- output "1 : NAME"
+# Template Toolkit http://template-toolkit.org/
+--type-add=tt:ext:tt,tt2,ttml
-=head2 Share your knowledge
+# Visual Basic
+--type-add=vb:ext:bas,cls,frm,ctl,vb,resx
-Join the ack-users mailing list. Send me your tips and I may add
-them here.
+# Verilog
+--type-add=verilog:ext:v,vh,sv
-=head1 FAQ
+# VHDL http://www.eda.org/twiki/bin/view.cgi/P1076/WebHome
+--type-add=vhdl:ext:vhd,vhdl
-=head2 Why isn't ack finding a match in (some file)?
+# Vim http://www.vim.org/
+--type-add=vim:ext:vim
-Probably because it's of a type that ack doesn't recognize. ack's
-searching behavior is driven by filetype. B<If ack doesn't know
-what kind of file it is, ack ignores the file.>
+# XML http://www.w3.org/TR/REC-xml/
+--type-add=xml:ext:xml,dtd,xsl,xslt,ent
+--type-add=xml:firstlinematch:/<[?]xml/
-Use the C<-f> switch to see a list of files that ack will search
-for you.
+# YAML http://yaml.org/
+--type-add=yaml:ext:yaml,yml
+HERE
+}
-If you want ack to search files that it doesn't recognize, use the
-C<-a> switch.
+1;
+package App::Ack::ConfigFinder;
-If you want ack to search every file, even ones that it always
-ignores like coredumps and backup files, use the C<-u> switch.
-=head2 Why does ack ignore unknown files by default?
+use strict;
+use warnings;
-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.
+use Cwd 3.00 ();
+use File::Spec 3.00;
-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.
+use if ($^O eq 'MSWin32'), 'Win32';
-=head2 Wouldn't it be great if F<ack> did search & replace?
-No, ack will always be read-only. Perl has a perfectly good way
-to do search & replace in files, using the C<-i>, C<-p> and C<-n>
-switches.
+sub new {
+ my ( $class ) = @_;
-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 bless {}, $class;
+}
- $ perl -i -p -e's/foo/bar/g' $(ack -f --php)
+sub _remove_redundancies {
+ my ( @configs ) = @_;
-=head2 Can you make ack recognize F<.xyz> files?
+ if ( $App::Ack::is_windows ) {
+ # inode stat always returns 0 on windows, so just check filenames.
+ my (%seen, @uniq);
-Yes! Please see L</"Defining your own types">. If you think
-that F<ack> should recognize a type by default, please see
-L</"ENHANCEMENTS">.
+ foreach my $path (@configs) {
+ push @uniq, $path unless $seen{$path};
+ $seen{$path} = 1;
+ }
-=head2 There's already a program/package called ack.
+ return @uniq;
+ }
-Yes, I know.
+ else {
-=head2 Why is it called ack if it's called ack-grep?
+ my %dev_and_inode_seen;
-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.
+ foreach my $path ( @configs ) {
+ my ( $dev, $inode ) = (stat $path)[0, 1];
-I suggest you make a symlink named F<ack> that points to F<ack-grep>
-because one of the crucial benefits of ack is having a name that's
-so short and simple to type.
+ if( defined($dev) ) {
+ if( $dev_and_inode_seen{"$dev:$inode"} ) {
+ undef $path;
+ }
+ else {
+ $dev_and_inode_seen{"$dev:$inode"} = 1;
+ }
+ }
+ }
-To do that, run this with F<sudo> or as root:
+ return grep { defined() } @configs;
- ln -s /usr/bin/ack-grep /usr/bin/ack
+ }
+}
-Alternatively, you could use a shell alias:
+sub _check_for_ackrc {
+ return unless defined $_[0];
- # bash/zsh
- alias ack=ack-grep
+ my @files = grep { -f }
+ map { File::Spec->catfile(@_, $_) }
+ qw(.ackrc _ackrc);
- # csh
- alias ack ack-grep
+ die File::Spec->catdir(@_) . " contains both .ackrc and _ackrc.\n" .
+ "Please remove one of those files.\n"
+ if @files > 1;
-=head2 What does F<ack> mean?
+ return wantarray ? @files : $files[0];
+} # end _check_for_ackrc
-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?
+sub find_config_files {
+ my @config_files;
-No, ack does not support regexes that match multiple lines. Doing
-so would require reading in the entire file at a time.
+ 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';
+ }
-If you want to see lines near your match, use the C<--A>, C<--B>
-and C<--C> switches for displaying context.
-=head2 Why is ack telling me I have an invalid option when searching for C<+foo>?
+ if ( $ENV{'ACKRC'} && -f $ENV{'ACKRC'} ) {
+ push @config_files, $ENV{'ACKRC'};
+ }
+ else {
+ push @config_files, _check_for_ackrc($ENV{'HOME'});
+ }
-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 @dirs = File::Spec->splitdir(Cwd::getcwd());
+ while(@dirs) {
+ my $ackrc = _check_for_ackrc(@dirs);
+ if(defined $ackrc) {
+ push @config_files, $ackrc;
+ last;
+ }
+ pop @dirs;
+ }
-=head1 ACKRC LOCATION SEMANTICS
+ # 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 );
+}
-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)
-=over 4
+sub read_rcfile {
+ my $file = shift;
-=item *
+ return unless defined $file && -e $file;
-Defaults are loaded from App::Ack::ConfigDefaults. This can be omitted
-using C<--ignore-ack-defaults>.
+ my @lines;
-=item * Global ackrc
+ open( my $fh, '<', $file ) or App::Ack::die( "Unable to read $file: $!" );
+ while ( my $line = <$fh> ) {
+ chomp $line;
+ $line =~ s/^\s+//;
+ $line =~ s/\s+$//;
-Options are then loaded from the global ackrc. This is located at
-C</etc/ackrc> on Unix-like systems, and
-C<C:\Documents and Settings\All Users\Application Data> on Windows.
-This can be omitted using C<--noenv>.
+ next if $line eq '';
+ next if $line =~ /^#/;
-=item * User ackrc
+ push( @lines, $line );
+ }
+ close $fh;
-Options are then loaded from the user's ackrc. This is located at
-C<$HOME/.ackrc> on Unix-like systems, and
-C<C:\Documents and Settings\$USER\Application Data>. If a different
-ackrc is desired, it may be overriden with the C<$ACKRC> environment
-variable.
-This can be omitted using C<--noenv>.
+ return @lines;
+}
-=item * Project ackrc
+1;
+package App::Ack::ConfigLoader;
-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>.
+use strict;
+use warnings;
-=item * ACK_OPTIONS
+use Carp 1.04 ();
+use Getopt::Long 2.35 ();
+use Text::ParseWords 3.1 ();
-Options are then loaded from the enviroment variable C<ACK_OPTIONS>. This can
-be omitted using C<--noenv>.
-=item * Command line
+my @INVALID_COMBINATIONS;
-Options are then loaded from the command line.
+BEGIN {
+ my @context = qw( -A -B -C --after-context --before-context --context );
+ my @pretty = qw( --heading --group --break );
+ my @filename = qw( -h -H --with-filename --no-filename );
-=back
+ @INVALID_COMBINATIONS = (
+ # XXX normalize
+ [qw(-l)] => [@context, @pretty, @filename, qw(-L -o --passthru --output --max-count --column -f -g --show-types)],
+ [qw(-L)] => [@context, @pretty, @filename, qw(-l -o --passthru --output --max-count --column -f -g --show-types -c --count)],
+ [qw(--line)] => [@context, @pretty, @filename, qw(-l --files-with-matches --files-without-matches -L -o --passthru --match -m --max-count -1 -c --count --column --print0 -f -g --show-types)],
+ [qw(-o)] => [@context, qw(--output -c --count --column --column -f --show-types)],
+ [qw(--passthru)] => [@context, qw(--output --column -m --max-count -1 -c --count -f -g)],
+ [qw(--output)] => [@context, qw(-c --count -f -g)],
+ [qw(--match)] => [qw(-f -g)],
+ [qw(-m --max-count)] => [qw(-1 -f -g -c --count)],
+ [qw(-h --no-filename)] => [qw(-H --with-filename -f -g --group --heading)],
+ [qw(-H --with-filename)] => [qw(-h --no-filename -f -g)],
+ [qw(-c --count)] => [@context, @pretty, qw(--column -f -g)],
+ [qw(--column)] => [qw(-f -g)],
+ [@context] => [qw(-f -g)],
+ [qw(-f)] => [qw(-g), @pretty],
+ [qw(-g)] => [qw(-f), @pretty],
+ );
+}
-=head1 DIFFERENCES BETWEEN ACK 1.X AND ACK 2.X
+sub process_filter_spec {
+ my ( $spec ) = @_;
-A lot of changes were made for ack 2; here is a list of them.
+ if ( $spec =~ /^(\w+):(\w+):(.*)/ ) {
+ my ( $type_name, $ext_type, $arguments ) = ( $1, $2, $3 );
-=head2 GENERAL CHANGES
+ 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 );
-=over 4
+ my @extensions = split(/,/, $extensions);
+ foreach my $extension ( @extensions ) {
+ $extension =~ s/^[.]//;
+ }
-=item *
+ return ( $type_name, App::Ack::Filter->create_filter('ext', @extensions) );
+ }
+ else {
+ Carp::croak "invalid filter specification '$spec'";
+ }
+}
-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.
+sub uninvert_filter {
+ my ( $opt, @filters ) = @_;
-=item *
+ return unless defined $opt->{filters} && @filters;
-A more flexible filter system has been added, so that more powerful file types
-may be created by the user. For details, please consult
-L</"Defining your own types">.
+ # Loop through all the registered filters. If we hit one that
+ # matches this extension and it's inverted, we need to delete it from
+ # the options.
+ for ( my $i = 0; $i < @{ $opt->{filters} }; $i++ ) {
+ my $opt_filter = @{ $opt->{filters} }[$i];
-=item *
+ # 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--;
+ }
+ }
+}
-ack now loads multiple ackrc files; see L</"ACKRC LOCATION SEMANTICS"> for
-details.
+sub process_filetypes {
+ my ( $opt, $arg_sources ) = @_;
-=item *
+ 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;
-ack's default filter definitions aren't special; you may tell ack to
-completely disregard them if you don't like them.
+ my $add_spec = sub {
+ my ( undef, $spec ) = @_;
-=back
+ my ( $name, $filter ) = process_filter_spec($spec);
-=head2 REMOVED OPTIONS
+ push @{ $App::Ack::mappings{$name} }, $filter;
-=over 4
+ $additional_specs{$name . '!'} = sub {
+ my ( undef, $value ) = @_;
-=item *
+ my @filters = @{ $App::Ack::mappings{$name} };
+ if ( not $value ) {
+ @filters = map { $_->invert() } @filters;
+ }
+ else {
+ uninvert_filter( $opt, @filters );
+ }
-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.
+ push @{ $opt->{'filters'} }, @filters;
+ };
+ };
-=item *
+ my $set_spec = sub {
+ my ( undef, $spec ) = @_;
-The B<-G> option has been removed. Two regular expressions on the
-command line was considered too confusing; to simulate B<-G>'s functionality,
-you may use the new B<-x> option to pipe filenames from one invocation of
-ack into another.
+ my ( $name, $filter ) = process_filter_spec($spec);
-=item *
+ $App::Ack::mappings{$name} = [ $filter ];
-The B<--binary> option has been removed.
+ $additional_specs{$name . '!'} = sub {
+ my ( undef, $value ) = @_;
-=item *
+ my @filters = @{ $App::Ack::mappings{$name} };
+ if ( not $value ) {
+ @filters = map { $_->invert() } @filters;
+ }
-The B<--skipped> option has been removed.
+ push @{ $opt->{'filters'} }, @filters;
+ };
+ };
-=item *
+ my $delete_spec = sub {
+ my ( undef, $name ) = @_;
-The B<--text> option has been removed.
+ delete $App::Ack::mappings{$name};
+ delete $additional_specs{$name . '!'};
+ };
-=item *
+ my %type_arg_specs = (
+ 'type-add=s' => $add_spec,
+ 'type-set=s' => $set_spec,
+ 'type-del=s' => $delete_spec,
+ );
-The B<--invert-file-match> option has been removed. Instead, you may
-use B<-v> with B<-g>.
+ for ( my $i = 0; $i < @{$arg_sources}; $i += 2) {
+ my ( $source_name, $args ) = @{$arg_sources}[ $i, $i + 1];
-=back
+ 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 CHANGED OPTIONS
+ $additional_specs{'k|known-types'} = sub {
+ my ( undef, $value ) = @_;
-=over 4
+ my @filters = map { @{$_} } values(%App::Ack::mappings);
+
+ push @{ $opt->{'filters'} }, @filters;
+ };
+
+ return \%additional_specs;
+}
+
+sub removed_option {
+ my ( $option, $explanation ) = @_;
+
+ $explanation ||= '';
+ return sub {
+ warn "Option '$option' is not valid in ack 2\n$explanation";
+ exit 1;
+ };
+}
+
+sub get_arg_spec {
+ my ( $opt, $extra_specs ) = @_;
+
+ my $dash_a_explanation = <<EOT;
+This is because we now have -k/--known-types which makes it only select files
+of known types, rather than any text file (which is the behavior of ack 1.x).
+You may have options in a .ackrc, or in the ACKRC_OPTIONS environment variable.
+Try using the --dump flag.
+EOT
+
+ 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 ) = @_;
+
+ if ( !$value ) {
+ $opt->{noenv_seen} = 1;
+ }
+ },
+ f => \$opt->{f},
+ 'files-from=s' => \$opt->{files_from},
+ 'filter!' => \$App::Ack::is_filter_mode,
+ flush => \$opt->{flush},
+ 'follow!' => \$opt->{follow},
+ g => \$opt->{g},
+ G => removed_option('-G'),
+ 'group!' => sub { shift; $opt->{heading} = $opt->{break} = shift },
+ 'heading!' => \$opt->{heading},
+ 'h|no-filename' => \$opt->{h},
+ 'H|with-filename' => \$opt->{H},
+ 'i|ignore-case' => \$opt->{i},
+ 'ignore-directory|ignore-dir=s' # XXX Combine this version with the negated version below
+ => sub {
+ my ( undef, $dir ) = @_;
+
+ $dir = App::Ack::remove_dir_sep( $dir );
+ if ( $dir !~ /^(?:is|match):/ ) {
+ $dir = 'is:' . $dir;
+ }
+ push @{ $opt->{idirs} }, $dir;
+ },
+ 'ignore-file=s' => sub {
+ my ( undef, $file ) = @_;
+ push @{ $opt->{ifiles} }, $file;
+ },
+ 'lines=s' => sub { shift; my $val = shift; push @{$opt->{lines}}, $val },
+ 'l|files-with-matches'
+ => \$opt->{l},
+ 'L|files-without-matches'
+ => \$opt->{L},
+ 'm|max-count=i' => \$opt->{m},
+ 'match=s' => \$opt->{regex},
+ 'n|no-recurse' => \$opt->{n},
+ o => sub { $opt->{output} = '$&' },
+ 'output=s' => \$opt->{output},
+ 'pager:s' => sub {
+ my ( undef, $value ) = @_;
+
+ $opt->{pager} = $value || $ENV{PAGER};
+ },
+ 'noignore-directory|noignore-dir=s'
+ => sub {
+ my ( undef, $dir ) = @_;
+
+ # XXX can you do --noignore-dir=match,...?
+ $dir = App::Ack::remove_dir_sep( $dir );
+ if ( $dir !~ /^(?:is|match):/ ) {
+ $dir = 'is:' . $dir;
+ }
+ if ( $dir !~ /^(?:is|match):/ ) {
+ Carp::croak("invalid noignore-directory argument: '$dir'");
+ }
+
+ @{ $opt->{idirs} } = grep {
+ $_ ne $dir
+ } @{ $opt->{idirs} };
+
+ push @{ $opt->{no_ignore_dirs} }, $dir;
+ },
+ 'nopager' => sub { $opt->{pager} = undef },
+ 'passthru' => \$opt->{passthru},
+ 'print0' => \$opt->{print0},
+ 'Q|literal' => \$opt->{Q},
+ 'r|R|recurse' => sub { $opt->{n} = 0 },
+ 's' => \$opt->{dont_report_bad_filenames},
+ 'show-types' => \$opt->{show_types},
+ 'smart-case!' => \$opt->{smart_case},
+ 'sort-files' => \$opt->{sort_files},
+ 'type=s' => sub {
+ my ( $getopt, $value ) = @_;
+
+ my $cb_value = 1;
+ if ( $value =~ s/^no// ) {
+ $cb_value = 0;
+ }
+
+ my $callback = $extra_specs->{ $value . '!' };
+
+ if ( $callback ) {
+ $callback->( $getopt, $cb_value );
+ }
+ else {
+ Carp::croak( "Unknown type '$value'" );
+ }
+ },
+ 'u' => removed_option('-u'),
+ 'unrestricted' => removed_option('--unrestricted'),
+ 'v|invert-match' => \$opt->{v},
+ 'w|word-regexp' => \$opt->{w},
+ 'x' => sub { $opt->{files_from} = '-' },
+
+ 'version' => sub { App::Ack::print_version_statement(); exit; },
+ 'help|?:s' => sub { shift; App::Ack::show_help(@_); exit; },
+ 'help-types' => sub { App::Ack::show_help_types(); exit; },
+ 'man' => sub { App::Ack::show_man(); exit; },
+ $extra_specs ? %{$extra_specs} : (),
+ }; # arg_specs
+}
+
+sub process_other {
+ my ( $opt, $extra_specs, $arg_sources ) = @_;
+
+ 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',
+ );
+
+ my $argv_source;
+ my $is_help_types_active;
+
+ for ( my $i = 0; $i < @{$arg_sources}; $i += 2 ) {
+ my ( $source_name, $args ) = @{$arg_sources}[ $i, $i + 1 ];
+
+ if ( $source_name eq 'ARGV' ) {
+ $argv_source = $args;
+ last;
+ }
+ }
+
+ if ( $argv_source ) { # This *should* always be true, but you never know...
+ my @copy = @{$argv_source};
+ local @ARGV = @copy;
+
+ Getopt::Long::Configure('pass_through');
+
+ Getopt::Long::GetOptions(
+ 'help-types' => \$is_help_types_active,
+ );
+
+ Getopt::Long::Configure('no_pass_through');
+ }
+
+ my $arg_specs = get_arg_spec($opt, $extra_specs);
+
+ for ( my $i = 0; $i < @{$arg_sources}; $i += 2) {
+ my ($source_name, $args) = @{$arg_sources}[$i, $i + 1];
+
+ 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" );
+ }
+ }
+
+ # XXX We need to check on a -- in the middle of a non-ARGV source
+
+ return;
+}
+
+sub should_dump_options {
+ my ( $sources ) = @_;
+
+ 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;
+}
+
+sub explode_sources {
+ my ( $sources ) = @_;
+
+ my @new_sources;
+
+ Getopt::Long::Configure('default', 'pass_through', 'no_auto_help', 'no_auto_version');
+
+ my %opt;
+ my $arg_spec = get_arg_spec(\%opt);
+
+ my $add_type = sub {
+ my ( undef, $arg ) = @_;
+
+ # XXX refactor?
+ if ( $arg =~ /(\w+)=/) {
+ $arg_spec->{$1} = sub {};
+ }
+ else {
+ ( $arg ) = split /:/, $arg;
+ $arg_spec->{$arg} = sub {};
+ }
+ };
+
+ my $del_type = sub {
+ my ( undef, $arg ) = @_;
+
+ delete $arg_spec->{$arg};
+ };
+
+ 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--;
+
+ my @copy = @chunk;
+ local @ARGV = @chunk;
+ Getopt::Long::GetOptions(
+ 'type-add=s' => $add_type,
+ 'type-set=s' => $add_type,
+ 'type-del=s' => $del_type,
+ );
+ Getopt::Long::GetOptions( %{$arg_spec} );
+
+ push @new_sources, $name, \@copy;
+ }
+ }
+
+ return \@new_sources;
+}
+
+sub compare_opts {
+ my ( $a, $b ) = @_;
+
+ my $first_a = $a->[0];
+ my $first_b = $b->[0];
+
+ $first_a =~ s/^--?//;
+ $first_b =~ s/^--?//;
+
+ return $first_a cmp $first_b;
+}
+
+sub dump_options {
+ my ( $sources ) = @_;
+
+ $sources = explode_sources($sources);
+
+ my %opts_by_source;
+ my @source_names;
+
+ 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;
+ }
+
+ foreach my $name (@source_names) {
+ my $contents = $opts_by_source{$name};
+
+ print $name, "\n";
+ print '=' x length($name), "\n";
+ print ' ', join(' ', @{$_}), "\n" foreach sort { compare_opts($a, $b) } @{$contents};
+ }
+
+ return;
+}
+
+sub remove_default_options_if_needed {
+ my ( $sources ) = @_;
+
+ my $default_index;
+
+ foreach my $index ( 0 .. $#$sources ) {
+ if ( $sources->[$index] eq 'Defaults' ) {
+ $default_index = $index;
+ last;
+ }
+ }
+
+ return $sources unless defined $default_index;
+
+ my $should_remove = 0;
+
+ # Start with default options, minus some annoying ones.
+ Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version');
+ Getopt::Long::Configure(
+ 'no_ignore_case',
+ 'no_auto_abbrev',
+ 'pass_through',
+ );
+
+ foreach my $index ( $default_index + 2 .. $#$sources ) {
+ next if $index % 2 != 0;
+
+ my ( $name, $args ) = @{$sources}[ $index, $index + 1 ];
+
+ 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,
+ );
+ }
+ }
+
+ Getopt::Long::Configure('default');
+ Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version');
+
+ return $sources unless $should_remove;
+
+ my @copy = @{$sources};
+ splice @copy, $default_index, 2;
+ return \@copy;
+}
+
+sub check_for_mutually_exclusive_options {
+ my ( $arg_sources ) = @_;
+
+ my %mutually_exclusive_with;
+ my @copy = @{$arg_sources};
+
+ for(my $i = 0; $i < @INVALID_COMBINATIONS; $i += 2) {
+ my ( $lhs, $rhs ) = @INVALID_COMBINATIONS[ $i, $i + 1 ];
+
+ foreach my $l_opt ( @{$lhs} ) {
+ foreach my $r_opt ( @{$rhs} ) {
+ push @{ $mutually_exclusive_with{ $l_opt } }, $r_opt;
+ push @{ $mutually_exclusive_with{ $r_opt } }, $l_opt;
+ }
+ }
+ }
+
+ while( @copy ) {
+ my %set_opts;
+
+ my ( $source_name, $args ) = splice @copy, 0, 2;
+ $args = ref($args) ? [ @{$args} ] : [ Text::ParseWords::shellwords($args) ];
+
+ foreach my $opt ( @{$args} ) {
+ next unless $opt =~ /^[-+]/;
+ last if $opt eq '--';
+
+ if( $opt =~ /^(.*)=/ ) {
+ $opt = $1;
+ }
+ elsif ( $opt =~ /^(-[^-]).+/ ) {
+ $opt = $1;
+ }
+
+ $set_opts{ $opt } = 1;
+
+ my $mutex_opts = $mutually_exclusive_with{ $opt };
+
+ next unless $mutex_opts;
+
+ foreach my $mutex_opt ( @{$mutex_opts} ) {
+ if($set_opts{ $mutex_opt }) {
+ die "Options '$mutex_opt' and '$opt' are mutually exclusive\n";
+ }
+ }
+ }
+ }
+}
+
+sub process_args {
+ my $arg_sources = \@_;
+
+ my %opt = (
+ pager => $ENV{ACK_PAGER_COLOR} || $ENV{ACK_PAGER},
+ );
+
+ check_for_mutually_exclusive_options($arg_sources);
+
+ $arg_sources = remove_default_options_if_needed($arg_sources);
+
+ if ( should_dump_options($arg_sources) ) {
+ dump_options($arg_sources);
+ exit(0);
+ }
+
+ my $type_specs = process_filetypes(\%opt, $arg_sources);
+ process_other(\%opt, $type_specs, $arg_sources);
+ while ( @{$arg_sources} ) {
+ my ( $source_name, $args ) = splice( @{$arg_sources}, 0, 2 );
+
+ # 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;
+}
-The options that modify the regular expression's behavior (B<-i>, B<-w>,
-B<-Q>, and B<-v>) may now be used with B<-g>.
-=back
+sub retrieve_arg_sources {
+ my @arg_sources;
-=head2 ADDED OPTIONS
+ my $noenv;
+ my $ackrc;
-=over 4
+ Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version');
+ Getopt::Long::Configure('pass_through');
+ Getopt::Long::Configure('no_auto_abbrev');
-=item *
+ Getopt::Long::GetOptions(
+ 'noenv' => \$noenv,
+ 'ackrc=s' => \$ackrc,
+ );
-B<--files-from> was added so that a user may submit a list of filenames as
-a list of files to search.
+ Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version');
-=item *
+ my @files;
-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.
+ 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 );
+ }
-=item *
+ push @arg_sources, Defaults => [ App::Ack::ConfigDefault::options() ];
-B<-s> was added to tell ack to suppress error messages about non-existent or
-unreadable files.
+ foreach my $file ( @files) {
+ my @lines = App::Ack::ConfigFinder::read_rcfile($file);
+ push ( @arg_sources, $file, \@lines ) if @lines;
+ }
-=item *
+ if ( $ENV{ACK_OPTIONS} && !$noenv ) {
+ push( @arg_sources, 'ACK_OPTIONS' => $ENV{ACK_OPTIONS} );
+ }
-B<--ignore-directory> and B<--noignore-directory> were added as aliases for
-B<--ignore-dir> and B<--noignore-dir> respectively.
+ push( @arg_sources, 'ARGV' => [ @ARGV ] );
-=item *
+ return @arg_sources;
+}
-B<--ignore-file> was added so that users may specify patterns of files to
-ignore (ex. /.*~$/).
+1; # End of App::Ack::ConfigLoader
+package App::Ack::Filter;
-=item *
+use strict;
+use warnings;
+use overload
+ '""' => 'to_string';
-B<--dump> was added to allow users to easily find out which options are
-set where.
+use Carp 1.04 ();
-=item *
+my %filter_types;
-B<--create-ackrc> was added so that users may create custom ackrc files based
-on the default settings loaded by ack, and so that users may easily view those
-defaults.
-=item *
+sub create_filter {
+ my ( undef, $type, @args ) = @_;
-B<--type-del> was added to selectively remove file type definitions.
+ if ( my $package = $filter_types{$type} ) {
+ return $package->new(@args);
+ }
+ Carp::croak "Unknown filter type '$type'";
+}
-=item *
-B<--ignore-ack-defaults> was added so that users may ignore ack's default
-options in favor of their own.
+sub register_filter {
+ my ( undef, $type, $package ) = @_;
-=item *
+ $filter_types{$type} = $package;
-B<--bar> was added so ack users may consult Admiral Ackbar.
+ return;
+}
-=back
-=head1 AUTHOR
+sub invert {
+ my ( $self ) = @_;
-Andy Lester, C<< <andy at petdance.com> >>
+ return App::Ack::Filter::Inverse->new( $self );
+}
-=head1 BUGS
-Please report any bugs or feature requests to the issues list at
-Github: L<https://github.com/petdance/ack2/issues>
+sub is_inverted {
+ return 0;
+}
-=head1 ENHANCEMENTS
-All enhancement requests MUST first be posted to the ack-users
-mailing list at L<http://groups.google.com/group/ack-users>. I
-will not consider a request without it first getting seen by other
-ack users. This includes requests for new filetypes.
+sub to_string {
+ my ( $self ) = @_;
-There is a list of enhancements I want to make to F<ack> in the ack
-issues list at Github: L<https://github.com/petdance/ack2/issues>
+ return '(unimplemented to_string)';
+}
-Patches are always welcome, but patches with tests get the most
-attention.
-=head1 SUPPORT
+sub inspect {
+ my ( $self ) = @_;
-Support for and information about F<ack> can be found at:
+ return ref($self);
+}
-=over 4
+1;
+package App::Ack::Filter::Extension;
-=item * The ack homepage
+use strict;
+use warnings;
+BEGIN {
+ our @ISA = 'App::Ack::Filter';
+}
-L<http://betterthangrep.com/>
-=item * The ack-users mailing list
+sub new {
+ my ( $class, @extensions ) = @_;
-L<http://groups.google.com/group/ack-users>
+ my $exts = join('|', map { "\Q$_\E"} @extensions);
+ my $re = qr/[.](?:$exts)$/i;
-=item * The ack issues list at Github
+ return bless {
+ extensions => \@extensions,
+ regex => $re,
+ groupname => 'ExtensionGroup',
+ }, $class;
+}
-L<https://github.com/petdance/ack2/issues>
+sub create_group {
+ return App::Ack::Filter::ExtensionGroup->new();
+}
-=item * AnnoCPAN: Annotated CPAN documentation
+sub filter {
+ my ( $self, $resource ) = @_;
-L<http://annocpan.org/dist/ack>
+ my $re = $self->{'regex'};
-=item * CPAN Ratings
+ return $resource->name =~ /$re/;
+}
-L<http://cpanratings.perl.org/d/ack>
+sub inspect {
+ my ( $self ) = @_;
-=item * Search CPAN
+ my $re = $self->{'regex'};
-L<http://search.cpan.org/dist/ack>
+ return ref($self) . " - $re";
+}
-=item * Git source repository
+sub to_string {
+ my ( $self ) = @_;
-L<https://github.com/petdance/ack2>
+ my $exts = $self->{'extensions'};
-=back
+ return join(' ', map { ".$_" } @{$exts});
+}
-=head1 ACKNOWLEDGEMENTS
+BEGIN {
+ App::Ack::Filter->register_filter(ext => __PACKAGE__);
+}
-How appropriate to have I<ack>nowledgements!
+1;
+package App::Ack::Filter::FirstLineMatch;
-Thanks to everyone who has contributed to ack in any way, including
-Andrew Black,
-Ralph Bodenner,
-Shaun Patterson,
-Ryan Olson,
-Shlomi Fish,
-Karen Etheridge,
-Olivier Mengue,
-Matthew Wild,
-Scott Kyle,
-Nick Hooey,
-Bo Borgerson,
-Mark Szymanski,
-Marq Schneider,
-Packy Anderson,
-JR Boyens,
-Dan Sully,
-Ryan Niebur,
-Kent Fredric,
-Mike Morearty,
-Ingmar Vanhassel,
-Eric Van Dewoestine,
-Sitaram Chamarty,
-Adam James,
-Richard Carlsson,
-Pedro Melo,
-AJ Schuster,
-Phil Jackson,
-Michael Schwern,
-Jan Dubois,
-Christopher J. Madsen,
-Matthew Wickline,
-David Dyck,
-Jason Porritt,
-Jjgod Jiang,
-Thomas Klausner,
-Uri Guttman,
-Peter Lewis,
-Kevin Riggle,
-Ori Avtalion,
-Torsten Blix,
-Nigel Metheringham,
-GE<aacute>bor SzabE<oacute>,
-Tod Hagan,
-Michael Hendricks,
-E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason,
-Piers Cawley,
-Stephen Steneker,
-Elias Lutfallah,
-Mark Leighton Fisher,
-Matt Diephouse,
-Christian Jaeger,
-Bill Sully,
-Bill Ricker,
-David Golden,
-Nilson Santos F. Jr,
-Elliot Shank,
-Merijn Broeren,
-Uwe Voelker,
-Rick Scott,
-Ask BjE<oslash>rn Hansen,
-Jerry Gay,
-Will Coleda,
-Mike O'Regan,
-Slaven ReziE<0x107>,
-Mark Stosberg,
-David Alan Pisoni,
-Adriano Ferreira,
-James Keenan,
-Leland Johnson,
-Ricardo Signes,
-Pete Krawczyk and
-Rob Hoelz.
+use strict;
+use warnings;
+BEGIN {
+ our @ISA = 'App::Ack::Filter';
+}
-=head1 COPYRIGHT & LICENSE
+sub new {
+ my ( $class, $re ) = @_;
-Copyright 2005-2013 Andy Lester.
+ $re =~ s{^/|/$}{}g; # XXX validate?
+ $re = qr{$re}i;
-This program is free software; you can redistribute it and/or modify
-it under the terms of the Artistic License v2.0.
+ return bless {
+ regex => $re,
+ }, $class;
+}
-See http://www.perlfoundation.org/artistic_license_2_0 or the LICENSE.md
-file that comes with the ack distribution.
+# 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.
-=cut
-package File::Next;
+sub filter {
+ my ( $self, $resource ) = @_;
+
+ my $re = $self->{'regex'};
+
+ my $line = $resource->firstliney;
+
+ return $line =~ /$re/;
+}
+
+sub inspect {
+ my ( $self ) = @_;
+
+ my $re = $self->{'regex'};
+
+ return ref($self) . " - $re";
+}
+
+sub to_string {
+ my ( $self ) = @_;
+
+ (my $re = $self->{regex}) =~ s{\([^:]*:(.*)\)$}{$1};
+
+ return "first line matches /$re/";
+}
+
+BEGIN {
+ App::Ack::Filter->register_filter(firstlinematch => __PACKAGE__);
+}
+
+1;
+package App::Ack::Filter::Is;
use strict;
use warnings;
+BEGIN {
+ our @ISA = 'App::Ack::Filter';
+}
+use File::Spec 3.00 ();
-our $VERSION = '1.12';
+sub new {
+ my ( $class, $filename ) = @_;
+ return bless {
+ filename => $filename,
+ groupname => 'IsGroup',
+ }, $class;
+}
+sub create_group {
+ return App::Ack::Filter::IsGroup->new();
+}
-use File::Spec ();
+sub filter {
+ my ( $self, $resource ) = @_;
-our $name; # name of the current file
-our $dir; # dir of the current file
+ my $filename = $self->{'filename'};
+ my $base = (File::Spec->splitpath($resource->name))[2];
-our %files_defaults;
-our %skip_dirs;
+ return $base eq $filename;
+}
+
+sub inspect {
+ my ( $self ) = @_;
+
+ my $filename = $self->{'filename'};
+
+ return ref($self) . " - $filename";
+}
+
+sub to_string {
+ my ( $self ) = @_;
+
+ my $filename = $self->{'filename'};
+
+ return $filename;
+}
BEGIN {
- %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);
+ App::Ack::Filter->register_filter(is => __PACKAGE__);
}
+1;
+package App::Ack::Filter::Match;
-sub files {
- die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__);
+use strict;
+use warnings;
+BEGIN {
+ our @ISA = 'App::Ack::Filter';
+}
- my ($parms,@queue) = _setup( \%files_defaults, @_ );
- my $filter = $parms->{file_filter};
+use File::Spec 3.00;
- 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 new {
+ my ( $class, $re ) = @_;
- return;
- }; # iterator
+ $re =~ s{^/|/$}{}g; # XXX validate?
+ $re = qr/$re/i;
+
+ return bless {
+ regex => $re,
+ }, $class;
}
+sub filter {
+ my ( $self, $resource ) = @_;
+ my $re = $self->{'regex'};
+ my $base = (File::Spec->splitpath($resource->name))[2];
+ return $base =~ /$re/;
+}
+sub inspect {
+ my ( $self ) = @_;
+ my $re = $self->{'regex'};
-sub from_file {
- die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__);
+ print ref($self) . " - $re";
+}
- my ($parms,@queue) = _setup( \%files_defaults, @_ );
- my $err = $parms->{error_handler};
- my $warn = $parms->{error_handler};
+sub to_string {
+ my ( $self ) = @_;
- my $filename = $queue[1];
+ my $re = $self->{'regex'};
- if ( !defined($filename) ) {
- $err->( 'Must pass a filename to from_file()' );
- return undef;
- }
+ return "filename matches $re";
+}
- my $fh;
- if ( $filename eq '-' ) {
- $fh = \*STDIN;
- }
- else {
- if ( !open( $fh, '<', $filename ) ) {
- $err->( "Unable to open $filename: $!" );
- return undef;
- }
- }
- my $filter = $parms->{file_filter};
+BEGIN {
+ App::Ack::Filter->register_filter(match => __PACKAGE__);
+}
- 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;
- }
+1;
+package App::Ack::Filter::Default;
- 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;
+use strict;
+use warnings;
+BEGIN {
+ our @ISA = 'App::Ack::Filter';
+}
- return;
- }; # iterator
+sub new {
+ my ( $class ) = @_;
+
+ return bless {}, $class;
}
-sub _bad_invocation {
- my $good = (caller(1))[3];
- my $bad = $good;
- $bad =~ s/(.+)::/$1->/;
- return "$good must not be invoked as $bad";
+sub filter {
+ my ( $self, $resource ) = @_;
+
+ return -T $resource->name;
}
-sub sort_standard($$) { return $_[0]->[1] cmp $_[1]->[1] }
-sub sort_reverse($$) { return $_[1]->[1] cmp $_[0]->[1] }
+1;
+package App::Ack::Filter::Inverse;
-sub reslash {
- my $path = shift;
+use strict;
+use warnings;
+BEGIN {
+ our @ISA = 'App::Ack::Filter';
+}
+
+sub new {
+ my ( $class, $filter ) = @_;
+
+ return bless {
+ filter => $filter,
+ }, $class;
+}
+
+sub filter {
+ my ( $self, $resource ) = @_;
+
+ my $filter = $self->{'filter'};
+ return !$filter->filter( $resource );
+}
+
+sub invert {
+ my $self = shift;
+
+ return $self->{'filter'};
+}
+
+sub is_inverted {
+ return 1;
+}
+
+sub inspect {
+ my ( $self ) = @_;
+
+ my $filter = $self->{'filter'};
- 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];
-use Getopt::Long 2.36 ();
+ return exists $data->{$base};
+}
+sub inspect {
+ my ( $self ) = @_;
-our $VERSION;
-our $GIT_REVISION;
-our $COPYRIGHT;
-BEGIN {
- $VERSION = '2.00b06';
- $COPYRIGHT = 'Copyright 2005-2013 Andy Lester.';
- $GIT_REVISION = q{04e8986};
+ return ref($self) . " - $self";
}
-our $fh;
+sub to_string {
+ my ( $self ) = @_;
+
+ return join(' ', keys %{$self->{data}});
+}
+
+1;
+package App::Ack::Filter::ExtensionGroup;
+use strict;
+use warnings;
BEGIN {
- $fh = *STDOUT;
+ our @ISA = 'App::Ack::Filter';
}
+use File::Spec 3.00 ();
-our %types;
-our %type_wanted;
-our %mappings;
-our %ignore_dirs;
+sub new {
+ my ( $class ) = @_;
-our $is_filter_mode;
-our $output_to_pipe;
+ return bless {
+ data => {},
+ }, $class;
+}
-our $dir_sep_chars;
-our $is_cygwin;
-our $is_windows;
+sub add {
+ my ( $self, $filter ) = @_;
-use File::Spec 1.00015 ();
-use File::Glob 1.00015 ':glob';
+ my $data = $self->{'data'};
+ my $extensions = $filter->{'extensions'};
-BEGIN {
- # These have to be checked before any filehandle diddling.
- $output_to_pipe = not -t *STDOUT;
- $is_filter_mode = -p STDIN;
+ foreach my $ext (@{$extensions}) {
+ $data->{lc $ext} = 1;
+ }
+}
- $is_cygwin = ($^O eq 'cygwin');
- $is_windows = ($^O =~ /MSWin32/);
- $dir_sep_chars = $is_windows ? quotemeta( '\\/' ) : quotemeta( File::Spec->catfile( '', '' ) );
+sub filter {
+ my ( $self, $resource ) = @_;
+
+ if ($resource->name =~ /[.]([^.]*)$/) {
+ return exists $self->{'data'}->{lc $1};
+ }
+
+ return 0;
}
+sub inspect {
+ my ( $self ) = @_;
-sub retrieve_arg_sources {
- my @arg_sources;
+ return ref($self) . " - $self";
+}
- my $noenv;
- my $ackrc;
+sub to_string {
+ my ( $self ) = @_;
- Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version');
- Getopt::Long::Configure('pass_through');
- Getopt::Long::Configure('no_auto_abbrev');
+ my $data = $self->{'data'};
- Getopt::Long::GetOptions(
- 'noenv' => \$noenv,
- 'ackrc=s' => \$ackrc,
- );
+ return join(' ', map { ".$_" } (keys %$data));
+}
- Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version');
+1;
+package main;
- my @files;
+use strict;
+use warnings;
- 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 );
- }
+use 5.008008;
- push @arg_sources, Defaults => [ App::Ack::ConfigDefault::options() ];
- foreach my $file ( @files) {
- my @lines = read_rcfile($file);
- push ( @arg_sources, $file, \@lines ) if @lines;
- }
+# XXX Don't make this so brute force
+# See also: https://github.com/petdance/ack2/issues/89
- if ( $ENV{ACK_OPTIONS} && !$noenv ) {
- push( @arg_sources, 'ACK_OPTIONS' => $ENV{ACK_OPTIONS} );
- }
+use Getopt::Long 2.35 ();
- push( @arg_sources, 'ARGV' => [ @ARGV ] );
+use Carp 1.04 ();
- return @arg_sources;
-}
+our $VERSION = '2.10';
+# Check http://beyondgrep.com/ for updates
-sub read_rcfile {
- my $file = shift;
+# These are all our globals.
- return unless defined $file && -e $file;
+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" );
+ }
- my @lines;
+ # Do preliminary arg checking;
+ my $env_is_usable = 1;
+ for my $arg ( @ARGV ) {
+ last if ( $arg eq '--' );
- open( my $fh, '<', $file ) or App::Ack::die( "Unable to read $file: $!" );
- while ( my $line = <$fh> ) {
- chomp $line;
- $line =~ s/^\s+//;
- $line =~ s/\s+$//;
+ # 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();
- next if $line eq '';
- next if $line =~ /^#/;
+ # 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;
+ }
- push( @lines, $line );
+ 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;
}
- close $fh;
- return @lines;
+ main();
}
+sub _compile_descend_filter {
+ my ( $opt ) = @_;
-sub create_ignore_rules {
- my $what = shift;
- my $where = shift;
- my $opts = shift;
+ my $idirs = $opt->{idirs};
+ my $dont_ignore_dirs = $opt->{no_ignore_dirs};
- my @opts = @{$opts};
+ # 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 %rules;
+ my %ignore_dirs;
- for my $opt ( @opts ) {
- if ( $opt =~ /^(is|ext|regex),(.+)$/ ) {
- my $method = $1;
- my $arg = $2;
- if ( $method eq 'regex' ) {
- push( @{$rules{regex}}, qr/$arg/ );
+ foreach my $idir (@{$idirs}) {
+ if ( $idir =~ /^(\w+):(.*)/ ) {
+ if ( $1 eq 'is') {
+ $ignore_dirs{$2} = 1;
}
else {
- ++$rules{$method}{$arg};
+ Carp::croak( 'Non-is filters are not yet supported for --ignore-dir' );
}
}
else {
- App::Ack::die( "Invalid argument for --$what: $opt" );
+ Carp::croak( qq{Invalid filter specification "$idir"} );
}
}
- return \%rules;
+ return sub {
+ return !exists $ignore_dirs{$_} && !exists $ignore_dirs{$File::Next::dir};
+ };
}
+sub _compile_file_filter {
+ my ( $opt, $start ) = @_;
-sub remove_dir_sep {
- my $path = shift;
- $path =~ s/[$dir_sep_chars]$//;
+ my $ifiles = $opt->{ifiles};
+ $ifiles ||= [];
- return $path;
-}
+ 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();
-sub build_regex {
- my $str = shift;
- my $opt = shift;
+ 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);
+ }
+ }
- defined $str or App::Ack::die( 'No regular expression found.' );
+ my %is_member_of_starting_set = map { (get_file_id($_) => 1) } @{$start};
- $str = quotemeta( $str ) if $opt->{Q};
- if ( $opt->{w} ) {
- $str = "\\b$str" if $str =~ /^\w/;
- $str = "$str\\b" if $str =~ /\w$/;
- }
+ my $ignore_dir_list = $opt->{idirs};
+ my $dont_ignore_dir_list = $opt->{no_ignore_dirs};
- my $regex_is_lc = $str eq lc $str;
- if ( $opt->{i} || ($opt->{smart_case} && $regex_is_lc) ) {
- $str = "(?i)$str";
+ 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"} );
+ }
}
- my $ok = eval {
- qr/$str/
- };
+ 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) };
- my $error = $@;
+ if ( $dont_ignore_dir_list ) {
+ my ( undef, $dirname ) = File::Spec->splitpath($File::Next::name);
+ my @dirs = File::Spec->splitdir($dirname);
- if ( !$ok ) {
- die "Invalid regex '$str':\n $error";
- }
+ my $is_ignoring = 0;
- return $str;
-}
+ 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;
-sub check_regex {
- my $regex = shift;
+ # 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;
+ }
- return unless defined $regex;
+ my $resource = App::Ack::Resource::Basic->new($File::Next::name);
+ return 0 if ! $resource;
+ if ( $ifiles_filters->filter($resource) ) {
+ return 0;
+ }
- eval { qr/$regex/ };
- if ($@) {
- (my $error = $@) =~ s/ at \S+ line \d+.*//;
- chomp($error);
- App::Ack::die( "Invalid regex '$regex':\n $error" );
- }
+ my $match_found = $direct_filters->filter($resource);
- return;
+ # 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 );
-
-sub warn {
- return CORE::warn( _my_program(), ': ', @_, "\n" );
+ 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;
-sub die {
- return CORE::die( _my_program(), ': ', @_, "\n" );
-}
+ $ENV{ACK_COLOR_MATCH} ||= 'black on_yellow';
+ $ENV{ACK_COLOR_FILENAME} ||= 'bold green';
+ $ENV{ACK_COLOR_LINENO} ||= 'bold yellow';
-sub _my_program {
- require File::Basename;
- return File::Basename::basename( $0 );
+ return;
}
+sub filetypes {
+ my ( $resource ) = @_;
+ my @matches;
-sub filetypes_supported {
- return keys %mappings;
-}
+ foreach my $k (keys %App::Ack::mappings) {
+ my $filters = $App::Ack::mappings{$k};
-sub _get_thpppt {
- my $y = q{_ /|,\\'!.x',=(www)=, U };
- $y =~ tr/,x!w/\nOo_/;
- return $y;
-}
+ foreach my $filter (@{$filters}) {
+ # clone the resource
+ my $clone = $resource->clone;
+ if ( $filter->filter($clone) ) {
+ push @matches, $k;
+ last;
+ }
+ }
+ }
-sub _thpppt {
- my $y = _get_thpppt();
- App::Ack::print( "$y ack $_[0]!\n" );
- exit 0;
+ # http://search.cpan.org/dist/Perl-Critic/lib/Perl/Critic/Policy/Subroutines/ProhibitReturnSort.pm
+ @matches = sort @matches;
+ return @matches;
}
-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
+# 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 ) = @_;
- $x =~ s/(.)(.)/$1x(ord($2)-32)/eg;
- App::Ack::print( $x );
- exit 0;
+ 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 show_help {
- my $help_arg = shift || 0;
+sub build_regex {
+ my $str = shift;
+ my $opt = shift;
- return show_help_types() if $help_arg =~ /^types?/;
+ defined $str or App::Ack::die( 'No regular expression found.' );
- App::Ack::print( <<"END_OF_HELP" );
-Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES]
+ $str = quotemeta( $str ) if $opt->{Q};
+ if ( $opt->{w} ) {
+ $str = "\\b$str" if $str =~ /^\w/;
+ $str = "$str\\b" if $str =~ /\w$/;
+ }
-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 $regex_is_lc = $str eq lc $str;
+ if ( $opt->{i} || ($opt->{smart_case} && $regex_is_lc) ) {
+ $str = "(?i)$str";
+ }
-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 $re = eval { qr/$str/ };
+ if ( !$re ) {
+ die "Invalid regex '$str':\n $@";
+ }
-Example: ack -i select
+ return $re;
-Searching:
- -i, --ignore-case Ignore case distinctions in PATTERN
- --[no]smart-case Ignore case distinctions in PATTERN,
- only if PATTERN contains no upper case.
- Ignored if -i is specified
- -v, --invert-match Invert match: select non-matching lines
- -w, --word-regexp Force PATTERN to match only whole words
- -Q, --literal Quote all metacharacters; PATTERN is literal
+}
-Search output:
- --lines=NUM Only print line(s) NUM of each file
- -l, --files-with-matches Only print filenames containing matches
- -L, --files-without-matches Only print filenames with no matches
- --output=expr Output the evaluation of expr for each line
- (turns off text highlighting)
- -o Show only the part of a line matching PATTERN
- Same as --output='\$&'
- --passthru Print all lines, whether matching or not
- --match PATTERN Specify PATTERN explicitly.
- -m, --max-count=NUM Stop searching in each file after NUM matches
- -1 Stop searching after one match of any kind
- -H, --with-filename Print the filename for each match
- -h, --no-filename Suppress the prefixing filename on output
- -c, --count Show number of lines matching per file
- --[no]column Show the column number of the first match
+{
- -A NUM, --after-context=NUM Print NUM lines of trailing context after matching
- lines.
- -B NUM, --before-context=NUM Print NUM lines of leading context before matching
- lines.
- -C [NUM], --context[=NUM] Print NUM lines (default 2) of output context.
+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};
- --print0 Print null byte as separator between filenames,
- only works with -f, -g, -l, -L or -c.
+ my $has_printed_for_this_resource = 0;
- -s Suppress error messages about nonexistent or
- unreadable files.
+ $is_iterating = 1;
+ local $opt->{before_context} = $opt->{output} ? 0 : $opt->{before_context};
+ local $opt->{after_context} = $opt->{output} ? 0 : $opt->{after_context};
-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).
+ my $n_before_ctx_lines = $opt->{before_context} || 0;
+ my $n_after_ctx_lines = $opt->{after_context} || 0;
+ @after_ctx_lines = @before_ctx_lines = ();
-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.
+ my $fh = $resource->open();
+ if ( !$fh ) {
+ if ( $App::Ack::report_bad_filenames ) {
+ App::Ack::warn( "$filename: $!" );
+ }
+ return 0;
+ }
-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.
+ my $display_filename = $filename;
+ if ( $print_filename && $heading && $color ) {
+ $display_filename = Term::ANSIColor::colored($display_filename, $ENV{ACK_COLOR_FILENAME});
+ }
- --type=X Include only X files, where X is a recognized filetype.
- --type=noX Exclude X files.
- See "ack --help-types" for supported filetypes.
+ # 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 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.
+ 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;
-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
+ 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 $_;
-Exit status is 0 if match, 1 if no match.
+ 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;
+ }
+ }
-This is version $VERSION of ack.
-END_OF_HELP
+ $is_iterating = 0; # XXX this won't happen on an exception
+ # then again, do we care? ack doesn't really
+ # handle exceptions anyway.
- return;
- }
+ return $nmatches;
+}
+sub print_line_with_options {
+ my ( $opt, $filename, $line, $line_no, $separator ) = @_;
+ $has_printed_something = 1;
-sub show_help_types {
- App::Ack::print( <<'END_OF_HELP' );
-Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES]
+ 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};
-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 @line_parts;
-Note that some extensions may appear in multiple types. For example,
-.pod files are both Perl and Parrot.
+ if( $color ) {
+ $filename = Term::ANSIColor::colored($filename,
+ $ENV{ACK_COLOR_FILENAME});
+ $line_no = Term::ANSIColor::colored($line_no,
+ $ENV{ACK_COLOR_LINENO});
+ }
-END_OF_HELP
+ if($print_filename) {
+ if( $heading ) {
+ push @line_parts, $line_no;
+ }
+ else {
+ push @line_parts, $filename, $line_no;
+ }
- my @types = filetypes_supported();
- my $maxlen = 0;
- for ( @types ) {
- $maxlen = length if $maxlen < length;
+ if( $print_column ) {
+ push @line_parts, get_match_column();
+ }
}
- for my $type ( sort @types ) {
- next if $type =~ /^-/; # Stuff to not show
- my $ext_list = $mappings{$type};
-
- if ( ref $ext_list ) {
- $ext_list = join( ' ', map { $_->to_string } @{$ext_list} );
+ if( $output_expr ) {
+ while ( $line =~ /$opt->{regex}/og ) {
+ my $output = eval $output_expr;
+ App::Ack::print( join( $separator, @line_parts, $output ), $ors );
}
- App::Ack::print( sprintf( " --[no]%-*.*s %s\n", $maxlen, $maxlen, $type, $ext_list ) );
}
+ 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
- return;
-}
+ 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;
-sub show_man {
- require Pod::Usage;
+ for ( my $i = 1; $i < @+; $i++ ) {
+ my ( $match_start, $match_end ) = ( $-[$i], $+[$i] );
- Pod::Usage::pod2usage({
- -input => $App::Ack::orig_program_name,
- -verbose => 2,
- -exitval => 0,
- });
+ next unless defined($match_start);
+ next if $match_start < $previous_match_end;
- return;
-}
+ 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 get_version_statement {
- require Config;
+ $previous_match_end = $match_end; # offsets do not need to be applied
+ $offset += length( $substitution ) - length( $substring );
+ }
- 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 );
+ pos($line) = $+[0] + $offset;
+ }
+ }
+ else {
+ my $matched = 0; # flag; if matched, need to escape afterwards
- my $git_revision = $GIT_REVISION ? " (git commit $GIT_REVISION)" : '';
+ while ( $line =~ /$opt->{regex}/og ) {
- return <<"END_OF_VERSION";
-ack ${VERSION}${git_revision}
-Running under Perl $ver at $this_perl
+ $matched = 1;
+ my ( $match_start, $match_end ) = ($-[0], $+[0]);
+ next unless defined($match_start);
-$copyright
+ my $substring = substr( $line, $match_start,
+ $match_end - $match_start );
+ my $substitution = Term::ANSIColor::colored( $substring,
+ $ENV{ACK_COLOR_MATCH} );
-This program is free software. You may modify or distribute it
-under the terms of the Artistic License v2.0.
-END_OF_VERSION
-}
+ 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;
+ }
+ }
-sub print_version_statement {
- App::Ack::print( get_version_statement() );
+ push @line_parts, $line;
+ App::Ack::print( join( $separator, @line_parts ), $ors );
+ }
return;
}
+sub iterate {
+ my ( $resource, $opt, $cb ) = @_;
-sub get_copyright {
- return $COPYRIGHT;
-}
+ $is_iterating = 1;
+ local $opt->{before_context} = $opt->{output} ? 0 : $opt->{before_context};
+ local $opt->{after_context} = $opt->{output} ? 0 : $opt->{after_context};
-sub load_colors {
- eval 'use Term::ANSIColor 1.12 ()';
+ my $n_before_ctx_lines = $opt->{before_context} || 0;
+ my $n_after_ctx_lines = $opt->{after_context} || 0;
- $ENV{ACK_COLOR_MATCH} ||= 'black on_yellow';
- $ENV{ACK_COLOR_FILENAME} ||= 'bold green';
- $ENV{ACK_COLOR_LINENO} ||= 'bold yellow';
+ @after_ctx_lines = @before_ctx_lines = ();
- return;
-}
+ my $fh = $resource->open();
+ if ( !$fh ) {
+ if ( $App::Ack::report_bad_filenames ) {
+ # XXX direct access to filename
+ App::Ack::warn( "$resource->{filename}: $!" );
+ }
+ return;
+ }
+ # 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
-# 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;
+ while ( defined $current_line ) {
+ while ( (@after_ctx_lines < $n_after_ctx_lines) && defined($_ = <$fh>) ) {
+ push @after_ctx_lines, $_;
+ }
- if ($show_filename) {
- App::Ack::print( $filename );
- App::Ack::print( ':', $nmatches ) if $count;
+ local $_ = $current_line;
+ my $former_dot_period = $.;
+ $. -= @after_ctx_lines;
+
+ 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
+
+ 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 {
- App::Ack::print( $nmatches ) if $count;
+ local $_;
+
+ while ( <$fh> ) {
+ last unless $cb->();
+ }
}
- App::Ack::print( $ors );
- return;
+ $is_iterating = 0; # XXX this won't happen on an exception
+ # then again, do we care? ack doesn't really
+ # handle exceptions anyway.
+
+ return;
+}
+
+sub 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;
+
+BEGIN {
+ $is_first_match = 1;
+ $previous_line_printed = -1;
}
-sub print_count0 {
- my $filename = shift;
- my $ors = shift;
- my $show_filename = shift;
+sub print_line_with_context {
+ my ( $opt, $filename, $matching_line, $line_no ) = @_;
- if ($show_filename) {
- App::Ack::print( $filename, ':0', $ors );
- }
- else {
- App::Ack::print( '0', $ors );
+ my $heading = $opt->{heading};
+
+ if( !defined($previous_file_processed) ||
+ $previous_file_processed ne $filename ) {
+ $previous_file_processed = $filename;
+ $previous_line_printed = -1;
+
+ if( $heading ) {
+ $is_first_match = 1;
+ }
}
- return;
-}
+ 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 set_up_pager {
- my $command = shift;
+ $matching_line =~ s/[\r\n]+$//g;
- return if App::Ack::output_to_pipe();
+ my ( $before_context, $after_context ) = get_context();
- my $pager;
- if ( not open( $pager, '|-', $command ) ) {
- App::Ack::die( qq{Unable to pipe to pager "$command": $!} );
+ if ( $before_context ) {
+ my $first_line = $. - @{$before_context};
+
+ if ( $first_line <= $previous_line_printed ) {
+ splice @{$before_context}, 0, $previous_line_printed - $first_line + 1;
+ $first_line = $. - @{$before_context};
+ }
+ if ( @{$before_context} ) {
+ my $offset = @{$before_context};
+
+ if( !$is_first_match && $previous_line_printed != $first_line - 1 ) {
+ App::Ack::print('--', $ors);
+ }
+ foreach my $line (@{$before_context}) {
+ my $context_line_no = $. - $offset;
+ if ( $context_line_no <= $previous_line_printed ) {
+ next;
+ }
+
+ chomp $line;
+ print_line_with_options($opt, $filename, $line, $context_line_no, '-');
+ $previous_line_printed = $context_line_no;
+ $offset--;
+ }
+ }
}
- $fh = $pager;
- return;
-}
+ if ( $. > $previous_line_printed ) {
+ if( $is_tracking_context && !$is_first_match && $previous_line_printed != $. - 1 ) {
+ App::Ack::print('--', $ors);
+ }
+ print_line_with_options($opt, $filename, $matching_line, $line_no, ':');
+ $previous_line_printed = $.;
+ }
-sub output_to_pipe {
- return $output_to_pipe;
-}
+ 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;
-sub exit_from_ack {
- my $nmatches = shift;
+ return;
+}
- my $rc = $nmatches ? 0 : 1;
- exit $rc;
}
{
-my @capture_indices;
my $match_column_number;
+# does_match() MUST have an $opt->{regex} set.
+
sub does_match {
my ( $opt, $line ) = @_;
- my $re = $opt->{regex};
- my $invert = $opt->{v};
-
- return unless $re;
-
$match_column_number = undef;
- @capture_indices = ();
- if ( $invert ? $line !~ /$re/ : $line =~ /$re/ ) {
- if ( not $invert ) {
+ 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;
-
- if ( @- > 1 ) {
- @capture_indices = map {
- [ $-[$_], $+[$_] ]
- } ( 1 .. $#- );
- }
+ return 1;
+ }
+ else {
+ return;
}
- return 1;
- }
- else {
- return;
}
}
-sub get_capture_indices {
- return @capture_indices;
-}
-
sub get_match_column {
return $match_column_number;
}
}
-sub print_matches_in_resource {
+sub resource_has_match {
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;
-
- App::Ack::iterate($resource, $opt, sub {
- if ( App::Ack::does_match($opt, $_) ) {
- if( !$has_printed_for_this_resource ) {
- if( $break && has_printed_something() ) {
- App::Ack::print_blank_line();
- }
- if( $print_filename) {
- if( $heading ) {
- my $filename = $resource->name;
- if($color) {
- $filename = Term::ANSIColor::colored($filename,
- $ENV{ACK_COLOR_FILENAME});
- }
- App::Ack::print_filename( $filename, $ors );
- }
- }
- }
- App::Ack::print_line_with_context($opt, $filename, $_, $.);
- $has_printed_for_this_resource = 1;
- $nmatches++;
- $max_count--;
+ 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}: $!" );
}
- elsif ( $passthru ) {
- chomp;
- if( $break && !$has_printed_for_this_resource && has_printed_something() ) {
- App::Ack::print_blank_line();
+ }
+ else {
+ my $opt_v = $opt->{v};
+ my $re = $opt->{regex};
+ while ( <$fh> ) {
+ if (/$re/o xor $opt_v) {
+ $has_match = 1;
+ last;
}
- App::Ack::print_line_with_options($opt, $filename, $_, $., ':');
- $has_printed_for_this_resource = 1;
}
- return $max_count != 0;
- });
+ close $fh;
+ }
- return $nmatches;
+ return $has_match;
}
sub count_matches_in_resource {
my ( $resource, $opt ) = @_;
my $nmatches = 0;
-
- App::Ack::iterate( $resource, $opt, sub {
- ++$nmatches if App::Ack::does_match($opt, $_);
- return 1;
- } );
-
- return $nmatches;
-}
-
-sub resource_has_match {
- my ( $resource, $opt ) = @_;
-
- return count_matches_in_resource($resource, $opt) > 0;
-}
-
-{
-
-my @before_ctx_lines;
-my @after_ctx_lines;
-my $is_iterating;
-
-sub get_context {
- if ( not $is_iterating ) {
- Carp::croak( 'get_context() called outside of iterate()' );
+ my $fh = $resource->open();
+ if ( !$fh ) {
+ if ( $App::Ack::report_bad_filenames ) {
+ # XXX direct access to filename
+ App::Ack::warn( "$resource->{filename}: $!" );
+ }
+ }
+ else {
+ my $opt_v = $opt->{v};
+ my $re = $opt->{regex};
+ while ( <$fh> ) {
+ ++$nmatches if (/$re/o xor $opt_v);
+ }
+ close $fh;
}
- return (
- scalar(@before_ctx_lines) ? \@before_ctx_lines : undef,
- scalar(@after_ctx_lines) ? \@after_ctx_lines : undef,
- );
+ return $nmatches;
}
-sub iterate {
- my ( $resource, $opt, $cb ) = @_;
-
- $is_iterating = 1;
-
- local $opt->{before_context} = $opt->{output} ? 0 : $opt->{before_context};
- local $opt->{after_context} = $opt->{output} ? 0 : $opt->{after_context};
+sub main {
+ my @arg_sources = App::Ack::ConfigLoader::retrieve_arg_sources();
- my $n_before_ctx_lines = $opt->{before_context} || 0;
- my $n_after_ctx_lines = $opt->{after_context} || 0;
- my $current_line;
+ my $opt = App::Ack::ConfigLoader::process_args( @arg_sources );
- @after_ctx_lines = @before_ctx_lines = ();
+ $App::Ack::report_bad_filenames = !$opt->{dont_report_bad_filenames};
- if ( $resource->next_text() ) {
- $current_line = $_; # prime the first line of input
+ if ( $opt->{flush} ) {
+ $| = 1;
}
- while ( defined $current_line ) {
- while ( (@after_ctx_lines < $n_after_ctx_lines) && $resource->next_text() ) {
- push @after_ctx_lines, $_;
+ 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();
+ }
- local $_ = $current_line;
- my $former_dot_period = $.;
- $. = $. - @after_ctx_lines;
-
- last unless $cb->();
+ if ( defined($opt->{H}) || defined($opt->{h}) ) {
+ $opt->{show_filename}= $opt->{H} && !$opt->{h};
+ }
- # 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 ( my $output = $opt->{output} ) {
+ $output =~ s{\\}{\\\\}g;
+ $output =~ s{"}{\\"}g;
+ $opt->{output} = qq{"$output"};
+ }
- push @before_ctx_lines, $current_line;
-if($n_after_ctx_lines) {
- $current_line = shift @after_ctx_lines;
- }
- elsif($resource->next_text()) {
- $current_line = $_;
+ 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 {
- undef $current_line;
+ my $regex = $opt->{regex};
+ $regex = shift @ARGV if not defined $regex;
+ $opt->{regex} = build_regex( $regex, $opt );
}
- shift @before_ctx_lines while @before_ctx_lines > $n_before_ctx_lines;
- }
-
- $is_iterating = 0; # XXX this won't happen on an exception
- # then again, do we care? ack doesn't really
- # handle exceptions anyway.
-
- return;
-}
-
-}
-
-my $has_printed_something;
-
-BEGIN {
- $has_printed_something = 0;
-}
-
-sub has_printed_something {
- return $has_printed_something;
-}
-
-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 $re = $opt->{regex};
- 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;
+ my @start;
+ if ( not defined $opt->{files_from} ) {
+ @start = @ARGV;
}
- else {
- push @line_parts, $filename, $line_no;
+ if ( !exists($opt->{show_filename}) ) {
+ unless(@start == 1 && !(-d $start[0])) {
+ $opt->{show_filename} = 1;
+ }
}
- if( $print_column ) {
- push @line_parts, get_match_column();
+ if ( defined $opt->{files_from} ) {
+ $resources = App::Ack::Resources->from_file( $opt, $opt->{files_from} );
+ exit 1 unless $resources;
}
- }
- if( $output_expr ) {
- # XXX avoid re-evaluation if we can
- while( $line =~ /$re/g ) {
- my $output = eval $output_expr;
- App::Ack::print( join( $separator, @line_parts, $output ), $ors );
+ else {
+ @start = ('.') unless @start;
+ foreach my $target (@start) {
+ if ( !-e $target && $App::Ack::report_bad_filenames) {
+ App::Ack::warn( "$target: No such file or directory" );
+ }
+ }
+
+ $opt->{file_filter} = _compile_file_filter($opt, \@start);
+ $opt->{descend_filter} = _compile_descend_filter($opt);
+
+ $resources = App::Ack::Resources->from_argv( $opt, \@start );
}
}
- else {
- my @capture_indices = get_capture_indices();
- if( @capture_indices ) {
- my $offset = 0; # additional offset for when we add stuff
-
- foreach my $index_pair ( @capture_indices ) {
- my ( $match_start, $match_end ) = @{$index_pair};
+ App::Ack::set_up_pager( $opt->{pager} ) if defined $opt->{pager};
- my $substring = substr( $line,
- $offset + $match_start, $match_end - $match_start );
- my $substitution = Term::ANSIColor::colored( $substring,
- $ENV{ACK_COLOR_MATCH} );
+ my $print_filenames = $opt->{show_filename};
+ my $max_count = $opt->{m};
+ my $ors = $opt->{print0} ? "\0" : "\n";
+ my $only_first = $opt->{1};
- substr( $line, $offset + $match_start,
- $match_end - $match_start, $substitution );
+ 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.
- $offset += length( $substitution ) - length( $substring );
+ # 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( $color ) {
- # XXX I know $& is a no-no; fix it later
- if($line =~ s/$re/Term::ANSIColor::colored($&, $ENV{ACK_COLOR_MATCH})/ge) {
- $line .= "\033[0m\033[K";
+ 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};
- push @line_parts, $line;
- App::Ack::print( join( $separator, @line_parts ), $ors );
- }
+ 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;
+ }
- return;
-}
+ my $filename = $resource->name;
-{
+ local $opt->{color} = 0;
-my $is_first_match;
-my $previous_file_processed;
-my $previous_line_printed;
+ iterate($resource, $opt, sub {
+ chomp;
-BEGIN {
- $is_first_match = 1;
- $previous_line_printed = -1;
-}
+ 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 print_line_with_context {
- my ( $opt, $filename, $matching_line, $line_no ) = @_;
+ unless ( $opt->{show_filename} ) {
+ $total_count += $matches_for_this_file;
+ next RESOURCES;
+ }
- my $heading = $opt->{heading};
+ 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( !defined($previous_file_processed) ||
- $previous_file_processed ne $filename ) {
- $previous_file_processed = $filename;
- $previous_line_printed = -1;
+ if ( $opt->{L} ? !$is_match : $is_match ) {
+ App::Ack::print( $resource->name, $ors );
+ ++$nmatches;
- if( $heading ) {
- $is_first_match = 1;
+ last RESOURCES if $only_first;
+ last RESOURCES if defined($max_count) && $nmatches >= $max_count;
+ }
+ }
+ else {
+ $nmatches += print_matches_in_resource( $resource, $opt );
+ if ( $nmatches && $only_first ) {
+ last RESOURCES;
+ }
}
}
- my $ors = $opt->{print0} ? "\0" : "\n";
- my $match_word = $opt->{w};
- my $re = $opt->{regex};
- my $is_tracking_context = $opt->{after_context} || $opt->{before_context};
- my $output_expr = $opt->{output};
+ if ( $opt->{count} && !$opt->{show_filename} ) {
+ App::Ack::print( $total_count, "\n" );
+ }
- chomp $matching_line;
+ close $App::Ack::fh;
+ App::Ack::exit_from_ack( $nmatches );
+}
- my ( $before_context, $after_context ) = get_context();
- if ( $before_context ) {
- my $first_line = $. - @{$before_context};
- if ( $first_line <= $previous_line_printed ) {
- splice @{$before_context}, 0, $previous_line_printed - $first_line + 1;
- $first_line = $. - @{$before_context};
- }
- if ( @{$before_context} ) {
- my $offset = @{$before_context};
+=head1 NAME
- 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;
- }
+ack - grep-like text finder
- chomp $line;
- App::Ack::print_line_with_options($opt, $filename, $line, $context_line_no, '-');
- $previous_line_printed = $context_line_no;
- $offset--;
- }
- }
- }
+=head1 SYNOPSIS
- if ( $. > $previous_line_printed ) {
- if( $is_tracking_context && !$is_first_match && $previous_line_printed != $. - 1 ) {
- App::Ack::print('--', $ors);
- }
+ ack [options] PATTERN [FILE...]
+ ack -f [options] [DIRECTORY...]
- App::Ack::print_line_with_options($opt, $filename, $matching_line, $line_no, ':');
- $previous_line_printed = $.;
- }
+=head1 DESCRIPTION
- if($after_context) {
- my $offset = 1;
- foreach my $line (@{$after_context}) {
- # XXX improve this!
- if ( $previous_line_printed >= $. + $offset ) {
- $offset++;
- next;
- }
- chomp $line;
- my $separator = App::Ack::does_match( $opt, $line ) ? ':' : '-';
- App::Ack::print_line_with_options($opt, $filename, $line, $. + $offset, $separator);
- $previous_line_printed = $. + $offset;
- $offset++;
- }
- }
+Ack is designed as a replacement for 99% of the uses of F<grep>.
- $is_first_match = 0;
+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;
-}
+PATTERN is a Perl regular expression. Perl regular expressions
+are commonly found in other programming languages, but for the particulars
+of their behavior, please consult
+L<http://perldoc.perl.org/perlreref.html|perlreref>. If you don't know
+how to use regular expression but are interested in learning, you may
+consult L<http://perldoc.perl.org/perlretut.html|perlretut>. If you do not
+need or want ack to use regular expressions, please see the
+C<-Q>/C<--literal> option.
-}
+Ack can also list files that would be searched, without actually
+searching them, to let you take advantage of ack's file-type filtering
+capabilities.
-# inefficient, but functional
-sub filetypes {
- my ( $resource ) = @_;
+=head1 FILE SELECTION
- my @matches;
+If files are not specified for searching, either on the command
+line or piped in with the C<-x> option, I<ack> delves into
+subdirectories selecting files for searching.
- foreach my $k (keys %mappings) {
- my $filters = $mappings{$k};
+I<ack> is intelligent about the files it searches. It knows about
+certain file types, based on both the extension on the file and,
+in some cases, the contents of the file. These selections can be
+made with the B<--type> option.
- foreach my $filter (@{$filters}) {
- # clone the resource
- my $clone = $resource->clone;
- if ( $filter->filter($clone) ) {
- push @matches, $k;
- last;
- }
- }
- }
+With no file selection, I<ack> searches through regular files that
+are not explicitly excluded by B<--ignore-dir> and B<--ignore-file>
+options, either present in F<ackrc> files or on the command line.
- return sort @matches;
-}
+The default options for I<ack> ignore certain files and directories. These
+include:
-# 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 ) = @_;
+=over 4
- if ( $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;
- }
- }
-}
+=item * Backup files: Files matching F<#*#> or ending with F<~>.
+
+=item * Coredumps: Files matching F<core.\d+>
+
+=item * Version control directories like F<.svn> and F<.git>.
-sub create_ackrc {
- my @lines = App::Ack::ConfigDefault::options();
+=back
- print join("\n", '--ignore-ack-defaults', @lines);
-}
+Run I<ack> with the C<--dump> option to see what settings are set.
+However, I<ack> always searches the files given on the command line,
+no matter what type. If you tell I<ack> to search in a coredump,
+it will search in a coredump.
+=head1 DIRECTORY SELECTION
-1; # End of App::Ack
-package App::Ack::Resource;
+I<ack> descends through the directory tree of the starting directories
+specified. If no directories are specified, the current working directory is
+used. However, it will ignore the shadow directories used by
+many version control systems, and the build directories used by the
+Perl MakeMaker system. You may add or remove a directory from this
+list with the B<--[no]ignore-dir> option. The option may be repeated
+to add/remove multiple directories from the ignore list.
+For a complete list of directories that do not get searched, run
+C<ack --dump>.
-use warnings;
-use strict;
-use overload
- '""' => 'name';
+=head1 WHEN TO USE GREP
-sub FAIL {
- require Carp;
- Carp::confess( 'Must be overloaded' );
-}
+I<ack> trumps I<grep> as an everyday tool 99% of the time, but don't
+throw I<grep> away, because there are times you'll still need it.
+E.g., searching through huge files looking for regexes that can be
+expressed with I<grep> syntax should be quicker with I<grep>.
-sub new {
- return FAIL();
-}
+If your script or parent program uses I<grep> C<--quiet> or C<--silent>
+or needs exit 2 on IO error, use I<grep>.
+=head1 OPTIONS
-sub name {
- return FAIL();
-}
+=over 4
+=item B<--ackrc>
-sub is_binary {
- return FAIL();
-}
+Specifies an ackrc file to load after all others; see L</"ACKRC LOCATION SEMANTICS">.
+=item B<-A I<NUM>>, B<--after-context=I<NUM>>
+Print I<NUM> lines of trailing context after matching lines.
-sub needs_line_scan {
- return FAIL();
-}
+=item B<-B I<NUM>>, B<--before-context=I<NUM>>
+Print I<NUM> lines of leading context before matching lines.
-sub reset {
- return FAIL();
-}
+=item B<--[no]break>
+Print a break between results from different files. On by default
+when used interactively.
-sub next_text {
- return FAIL();
-}
+=item B<-C [I<NUM>]>, B<--context[=I<NUM>]>
+Print I<NUM> lines (default 2) of context around matching lines.
-sub close {
- return FAIL();
-}
+=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 clone {
- return FAIL();
-}
+If combined with B<-h> (B<--no-filename>) ack outputs only one total
+count.
-1;
-package App::Ack::Resources;
+=item B<--[no]color>, B<--[no]colour>
+B<--color> highlights the matching text. B<--nocolor> suppresses
+the color. This is on by default unless the output is redirected.
+On Windows, this option is off by default unless the
+L<Win32::Console::ANSI> module is installed or the C<ACK_PAGER_COLOR>
+environment variable is used.
-use warnings;
-use strict;
+=item B<--color-filename=I<color>>
+Sets the color to be used for filenames.
-sub from_argv {
- my $class = shift;
- my $opt = shift;
- my $start = shift;
+=item B<--color-match=I<color>>
- my $self = bless {}, $class;
+Sets the color to be used for matches.
- my $file_filter = undef;
- my $descend_filter = $opt->{descend_filter};
+=item B<--color-lineno=I<color>>
- if( $opt->{n} ) {
- $descend_filter = sub {
- return 0;
- };
- }
+Sets the color to be used for line numbers.
- $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} );
+=item B<--[no]column>
- return $self;
-}
+Show the column number of the first match. This is helpful for
+editors that can place your cursor at a given position.
+=item B<--create-ackrc>
-sub from_file {
- my $class = shift;
- my $opt = shift;
- my $file = shift;
+Dumps the default ack options to standard output. This is useful for
+when you want to customize the defaults.
- 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;
+=item B<--dump>
- return bless {
- iter => $iter,
- }, $class;
-}
+Writes the list of options loaded and where they came from to standard
+output. Handy for debugging.
-# This is for reading input lines from STDIN, not the list of files from STDIN
-sub from_stdin {
- my $class = shift;
- my $opt = shift;
+=item B<--[no]env>
- my $self = bless {}, $class;
+B<--noenv> disables all environment processing. No F<.ackrc> is
+read and all environment variables are ignored. By default, F<ack>
+considers F<.ackrc> and settings in the environment.
- my $has_been_called = 0;
+=item B<--flush>
- $self->{iter} = sub {
- if ( !$has_been_called ) {
- $has_been_called = 1;
- return '-';
- }
- return;
- };
+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;
-}
+=item B<-f>
-sub next {
- my $self = shift;
+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.
- my $file = $self->{iter}->() or return;
+=item B<--files-from=I<FILE>>
- return App::Ack::Resource::Basic->new( $file );
-}
+The list of files to be searched is specified in I<FILE>. The list of
+files are separated by newlines. If I<FILE> is C<->, the list is loaded
+from standard input.
-1;
-package App::Ack::Resource::Basic;
+=item B<--[no]filter>
+Forces ack to act as if it were receiving input via a pipe.
-use warnings;
-use strict;
+=item B<--[no]follow>
-BEGIN {
- our @ISA = 'App::Ack::Resource';
-}
+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 new {
- my $class = shift;
- my $filename = shift;
+=item B<-g I<PATTERN>>
- my $self = bless {
- filename => $filename,
- fh => undef,
- opened => undef,
- }, $class;
+Print files where the relative path + filename matches I<PATTERN>.
- if ( $self->{filename} eq '-' ) {
- $self->{fh} = *STDIN;
- }
- else {
- if ( !open( $self->{fh}, '<', $self->{filename} ) && $App::Ack::report_bad_filenames ) {
- App::Ack::warn( "$self->{filename}: $!" );
- return;
- }
- }
+=item B<--[no]group>
- return $self;
-}
+B<--group> groups matches by file name. This is the default
+when used interactively.
+B<--nogroup> prints one result per line, like grep. This is the
+default when output is redirected.
-sub name {
- my $self = shift;
+=item B<-H>, B<--with-filename>
- return $self->{filename};
-}
+Print the filename for each match. This is the default unless searching
+a single explicitly specified file.
+=item B<-h>, B<--no-filename>
+Suppress the prefixing of filenames on output when multiple files are
+searched.
-sub needs_line_scan {
- my $self = shift;
- my $opt = shift;
+=item B<--[no]heading>
- return 1 if $opt->{v};
+Print a filename heading above each file's results. This is the default
+when used interactively.
- my $size = -s $self->{fh};
- if ( $size == 0 ) {
- return 0;
- }
- elsif ( $size > 100_000 ) {
- return 1;
- }
+=item B<--help>, B<-?>
- 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 short help statement.
- my $regex = $opt->{regex};
- return $buffer =~ /$regex/m;
-}
+=item B<--help-types>, B<--help=types>
+Print all known types.
-sub reset {
- my $self = shift;
+=item B<-i>, B<--ignore-case>
- if( !seek( $self->{fh}, 0, 0 ) && $App::Ack::report_bad_filenames ) {
- App::Ack::warn( "$self->{filename}: $!" );
- }
+Ignore case distinctions in PATTERN
- return;
-}
+=item B<--ignore-ack-defaults>
+Tells ack to completely ignore the default definitions provided with ack.
+This is useful in combination with B<--create-ackrc> if you I<really> want
+to customize ack.
-sub next_text {
- if ( defined ($_ = readline $_[0]->{fh}) ) {
- $. = ++$_[0]->{line};
- s/[\r\n]+$//; # chomp may not handle this
- $_ .= "\n"; # add back newline (XXX make it native)
- return 1;
- }
+=item B<--[no]ignore-dir=I<DIRNAME>>, B<--[no]ignore-directory=I<DIRNAME>>
- return;
-}
+Ignore directory (as CVS, .svn, etc are ignored). May be used
+multiple times to ignore multiple directories. For example, mason
+users may wish to include B<--ignore-dir=data>. The B<--noignore-dir>
+option allows users to search directories which would normally be
+ignored (perhaps to research the contents of F<.svn/props> directories).
+
+The I<DIRNAME> must always be a simple directory name. Nested
+directories like F<foo/bar> are NOT supported. You would need to
+specify B<--ignore-dir=foo> and then no files from any foo directory
+are taken into account by ack unless given explicitly on the command
+line.
+=item B<--ignore-file=I<FILTERTYPE:FILTERARGS>>
-sub close {
- my $self = shift;
+Ignore files matching I<FILTERTYPE:FILTERARGS>. The filters are specified
+identically to file type filters as seen in L</"Defining your own types">.
- if ( !close($self->{fh}) && $App::Ack::report_bad_filenames ) {
- App::Ack::warn( $self->name() . ": $!" );
- }
+=item B<-k>, B<--known-types>
- return;
-}
+Limit selected files to those with types that ack knows about. This is
+equivalent to the default behavior found in ack 1.
+=item B<--lines=I<NUM>>
-sub clone {
- my ( $self ) = @_;
+Only print line I<NUM> of each file. Multiple lines can be given with multiple
+B<--lines> options or as a comma separated list (B<--lines=3,5,7>). B<--lines=4-7>
+also works. The lines are always output in ascending order, no matter the
+order given on the command line.
- return __PACKAGE__->new($self->name);
-}
+=item B<-l>, B<--files-with-matches>
+Only print the filenames of matching files, instead of the matching text.
-1;
-package App::Ack::Filter;
+=item B<-L>, B<--files-without-matches>
-use strict;
-use warnings;
-use overload
- '""' => 'to_string';
+Only print the filenames of files that do I<NOT> match.
-use Carp 1.10 ();
+=item B<--match I<PATTERN>>
-my %filter_types;
+Specify the I<PATTERN> explicitly. This is helpful if you don't want to put the
+regex as your first argument, e.g. when executing multiple searches over the
+same set of files.
+ # search for foo and bar in given files
+ ack file1 t/file* --match foo
+ ack file1 t/file* --match bar
-sub create_filter {
- my ( undef, $type, @args ) = @_;
+=item B<-m=I<NUM>>, B<--max-count=I<NUM>>
- if ( my $package = $filter_types{$type} ) {
- return $package->new(@args);
- }
- Carp::croak "Unknown filter type '$type'";
-}
+Stop reading a file after I<NUM> matches.
+=item B<--man>
-sub register_filter {
- my ( undef, $type, $package ) = @_;
+Print this manual page.
- $filter_types{$type} = $package;
+=item B<-n>, B<--no-recurse>
- return;
-}
+No descending into subdirectories.
+=item B<-o>
-sub invert {
- my ( $self ) = @_;
+Show only the part of each line matching PATTERN (turns off text
+highlighting)
- return App::Ack::Filter::Inverse->new( $self );
-}
+=item B<--output=I<expr>>
+Output the evaluation of I<expr> for each line (turns off text
+highlighting)
+If PATTERN matches more than once then a line is output for each non-overlapping match.
+For more information please see the section L</"Examples of F<--output>">.
-sub is_inverted {
- return 0;
-}
+=item B<--pager=I<program>>, B<--nopager>
+B<--pager> directs ack's output through I<program>. This can also be specified
+via the C<ACK_PAGER> and C<ACK_PAGER_COLOR> environment variables.
-sub to_string {
- my ( $self ) = @_;
+Using --pager does not suppress grouping and coloring like piping
+output on the command-line does.
- return '(unimplemented to_string)';
-}
+B<--nopager> cancels any setting in ~/.ackrc, C<ACK_PAGER> or C<ACK_PAGER_COLOR>.
+No output will be sent through a pager.
+=item B<--passthru>
-sub inspect {
- my ( $self ) = @_;
+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:
- return ref($self);
-}
+ # Watch a log file, and highlight a certain IP address
+ $ tail -f ~/access.log | ack --passthru 123.45.67.89
-1;
-package App::Ack::Filter::Extension;
+=item B<--print0>
-use strict;
-use warnings;
-BEGIN {
- our @ISA = 'App::Ack::Filter';
-}
+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 new {
- my ( $class, @extensions ) = @_;
+ # remove all files of type html
+ ack -f --html --print0 | xargs -0 rm -f
- my $exts = join('|', map { "\Q$_\E"} @extensions);
- my $re = qr/[.](?:$exts)$/i;
+=item B<-Q>, B<--literal>
- return bless {
- extensions => \@extensions,
- regex => $re,
- }, $class;
-}
+Quote all metacharacters in PATTERN, it is treated as a literal.
-sub filter {
- my ( $self, $resource ) = @_;
+=item B<-r>, B<-R>, B<--recurse>
- my $re = $self->{'regex'};
+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.
- return $resource->name =~ /$re/;
-}
+=item B<-s>
-sub inspect {
- my ( $self ) = @_;
+Suppress error messages about nonexistent or unreadable files. This is taken
+from fgrep.
- my $re = $self->{'regex'};
+=item B<--[no]smart-case>, B<--no-smart-case>
- return ref($self) . " - $re";
-}
+Ignore case in the search strings if PATTERN contains no uppercase
+characters. This is similar to C<smartcase> in vim. This option is
+off by default, and ignored if C<-i> is specified.
-sub to_string {
- my ( $self ) = @_;
+B<-i> always overrides this option.
- my $exts = $self->{'extensions'};
+=item B<--sort-files>
- return join(' ', map { ".$_" } @{$exts});
-}
+Sorts the found files lexicographically. Use this if you want your file
+listings to be deterministic between runs of I<ack>.
-BEGIN {
- App::Ack::Filter->register_filter(ext => __PACKAGE__);
-}
+=item B<--show-types>
-1;
-package App::Ack::Filter::FirstLineMatch;
+Outputs the filetypes that ack associates with each file.
-use strict;
-use warnings;
-BEGIN {
- our @ISA = 'App::Ack::Filter';
-}
+Works with B<-f> and B<-g> options.
-sub new {
- my ( $class, $re ) = @_;
+=item B<--type=[no]TYPE>
- $re =~ s{^/|/$}{}g; # XXX validate?
- $re = qr{$re}i;
+Specify the types of files to include or exclude from a search.
+TYPE is a filetype, like I<perl> or I<xml>. B<--type=perl> can
+also be specified as B<--perl>, and B<--type=noperl> can be done
+as B<--noperl>.
- return bless {
- regex => $re,
- }, $class;
-}
+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.
-# XXX This test checks the first "line" of the file, but we need
-# it to be less piggy. If it's something like a .min.js file, then
-# the "line" could be the entire file. Instead, it should read the
-# first, say, 100 characters of the first line.
+Type specifications can be repeated and are ORed together.
-sub filter {
- my ( $self, $resource ) = @_;
+See I<ack --help=types> for a list of valid types.
- my $re = $self->{'regex'};
+=item B<--type-add I<TYPE>:I<FILTER>:I<FILTERARGS>>
- local $_;
- return unless $resource->next_text;
+Files with the given FILTERARGS applied to the given FILTER
+are recognized as being of (the existing) type TYPE.
+See also L</"Defining your own types">.
- return /$re/;
-}
-sub inspect {
- my ( $self ) = @_;
+=item B<--type-set I<TYPE>:I<FILTER>:I<FILTERARGS>>
- my $re = $self->{'regex'};
+Files with the given FILTERARGS applied to the given FILTER are recognized as
+being of type TYPE. This replaces an existing definition for type TYPE. See
+also L</"Defining your own types">.
- return ref($self) . " - $re";
-}
+=item B<--type-del I<TYPE>>
-sub to_string {
- my ( $self ) = @_;
+The filters associated with TYPE are removed from Ack, and are no longer considered
+for searches.
- my $re = $self->{'regex'};
+=item B<-v>, B<--invert-match>
- return "first line matches $re";
-}
+Invert match: select non-matching lines
-BEGIN {
- App::Ack::Filter->register_filter(firstlinematch => __PACKAGE__);
-}
+=item B<--version>
-1;
-package App::Ack::Filter::Is;
+Display version and copyright information.
-use strict;
-use warnings;
-BEGIN {
- our @ISA = 'App::Ack::Filter';
-}
+=item B<-w>, B<--word-regexp>
-use File::Spec 3.00 ();
+Force PATTERN to match only whole words. The PATTERN is wrapped with
+C<\b> metacharacters.
-sub new {
- my ( $class, $filename ) = @_;
+=item B<-x>
- return bless {
- filename => $filename,
- }, $class;
-}
+An abbreviation for B<--files-from=->; the list of files to search are read
+from standard input, with one line per file.
-sub filter {
- my ( $self, $resource ) = @_;
+=item B<-1>
- my $filename = $self->{'filename'};
- my $base = (File::Spec->splitpath($resource->name))[2];
+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.
- return $base eq $filename;
-}
+=item B<--thpppt>
-sub inspect {
- my ( $self ) = @_;
+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.
- my $filename = $self->{'filename'};
+=item B<--bar>
- return ref($self) . " - $filename";
-}
+Check with the admiral for traps.
-sub to_string {
- my ( $self ) = @_;
+=item B<--cathy>
- my $filename = $self->{'filename'};
-}
+Chocolate, Chocolate, Chocolate!
-BEGIN {
- App::Ack::Filter->register_filter(is => __PACKAGE__);
-}
+=back
-1;
-package App::Ack::Filter::Match;
+=head1 THE .ackrc FILE
-use strict;
-use warnings;
-BEGIN {
- our @ISA = 'App::Ack::Filter';
-}
+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:
-use File::Spec 3.00;
+ # Always sort the files
+ --sort-files
-sub new {
- my ( $class, $re ) = @_;
+ # Always color, even if piping to a another program
+ --color
- $re =~ s{^/|/$}{}g; # XXX validate?
- $re = qr/$re/i;
+ # Use "less -r" as my pager
+ --pager=less -r
- return bless {
- regex => $re,
- }, $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<line>
+in the F<.ackrc> file is interpreted as one element of C<@ARGV>.
-sub filter {
- my ( $self, $resource ) = @_;
+F<ack> looks in several locations for F<.ackrc> files; the searching
+process is detailed in L</"ACKRC LOCATION SEMANTICS">. These
+files are not considered if B<--noenv> is specified on the command line.
- my $re = $self->{'regex'};
- my $base = (File::Spec->splitpath($resource->name))[2];
+=head1 Defining your own types
- return $base =~ /$re/;
-}
+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.
-sub inspect {
- my ( $self ) = @_;
+I<ack --perl foo> searches for foo in all perl files. I<ack --help=types>
+tells you, that perl files are files ending
+in .pl, .pm, .pod or .t. So what if you would like to include .xs
+files as well when searching for --perl files? I<ack --type-add perl:ext:xs --perl foo>
+does this for you. B<--type-add> appends
+additional extensions to an existing type.
- my $re = $self->{'regex'};
+If you want to define a new type, or completely redefine an existing
+type, then use B<--type-set>. I<ack --type-set eiffel:ext:e,eiffel> defines
+the type I<eiffel> to include files with
+the extensions .e or .eiffel. So to search for all eiffel files
+containing the word Bertrand use I<ack --type-set eiffel:ext:e,eiffel --eiffel Bertrand>.
+As usual, you can also write B<--type=eiffel>
+instead of B<--eiffel>. Negation also works, so B<--noeiffel> excludes
+all eiffel files from a search. Redefining also works: I<ack --type-set cc:ext:c,h>
+and I<.xs> files no longer belong to the type I<cc>.
- print ref($self) . " - $re";
-}
+When defining your own types in the F<.ackrc> file you have to use
+the following:
-sub to_string {
- my ( $self ) = @_;
+ --type-set=eiffel:ext:e,eiffel
- my $re = $self->{'regex'};
+or writing on separate lines
- return "filename matches $re";
-}
+ --type-set
+ eiffel:ext:e,eiffel
-BEGIN {
- App::Ack::Filter->register_filter(match => __PACKAGE__);
-}
+The following does B<NOT> work in the F<.ackrc> file:
-1;
-package App::Ack::Filter::Default;
+ --type-set eiffel:ext:e,eiffel
-use strict;
-use warnings;
-BEGIN {
- our @ISA = 'App::Ack::Filter';
-}
-sub new {
- my ( $class ) = @_;
+In order to see all currently defined types, use I<--help-types>, e.g.
+I<ack --type-set backup:ext:bak --type-add perl:ext:perl --help-types>
- return bless {}, $class;
-}
+In addition to filtering based on extension (like ack 1.x allowed), ack 2
+offers additional filter types. The generic syntax is
+I<--type-set TYPE:FILTER:FILTERARGS>; I<FILTERARGS> depends on the value
+of I<FILTER>.
-sub filter {
- my ( $self, $resource ) = @_;
+=over 4
- return -T $resource->name;
-}
+=item is:I<FILENAME>
-1;
-package App::Ack::Filter::Inverse;
+I<is> filters match the target filename exactly. It takes exactly one
+argument, which is the name of the file to match.
-use strict;
-use warnings;
-BEGIN {
- our @ISA = 'App::Ack::Filter';
-}
+Example:
-sub new {
- my ( $class, $filter ) = @_;
+ --type-set make:is:Makefile
- return bless {
- filter => $filter,
- }, $class;
-}
+=item ext:I<EXTENSION>[,I<EXTENSION2>[,...]]
-sub filter {
- my ( $self, $resource ) = @_;
+I<ext> filters match the extension of the target file against a list
+of extensions. No leading dot is needed for the extensions.
- my $filter = $self->{'filter'};
- return !$filter->filter( $resource );
-}
+Example:
-sub invert {
- my $self = shift;
+ --type-set perl:ext:pl,pm,t
- return $self->{'filter'};
-}
+=item match:I<PATTERN>
-sub is_inverted {
- return 1;
-}
+I<match> filters match the target filename against a regular expression.
+The regular expression is made case insensitive for the search.
-sub inspect {
- my ( $self ) = @_;
+Example:
- my $filter = $self->{'filter'};
+ --type-set make:match:/(gnu)?makefile/
- return "!$filter";
-}
+=item firstlinematch:I<PATTERN>
-1;
-package App::Ack::ConfigFinder;
+I<firstlinematch> matches the first line of the target file against a
+regular expression. Like I<match>, the regular expression is made
+case insensitive.
+Example:
-use strict;
-use warnings;
+ --type-add perl:firstlinematch:/perl/
-use Cwd 3.00 ();
-use File::Spec 3.00;
+=back
-BEGIN {
- if ($App::Ack::is_windows) {
- require Win32;
- }
-}
+More filter types may be made available in the future.
+=head1 ENVIRONMENT VARIABLES
-sub new {
- my ( $class ) = @_;
+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.
- return bless {}, $class;
-}
+=over 4
-sub _remove_redundancies {
- my ( @configs ) = @_;
+=item ACKRC
- my %dev_and_inode_seen;
+Specifies the location of the user's F<.ackrc> file. If this file doesn't
+exist, F<ack> looks in the default location.
- foreach my $path ( @configs ) {
- my ( $dev, $inode ) = (stat $path)[0, 1];
+=item ACK_OPTIONS
- if( defined($dev) ) {
- if( $dev_and_inode_seen{"$dev:$inode"} ) {
- undef $path;
- }
- else {
- $dev_and_inode_seen{"$dev:$inode"} = 1;
- }
- }
- }
- return grep { defined() } @configs;
-}
+This variable specifies default options to be placed in front of
+any explicit options on the command line.
-sub _check_for_ackrc {
- return unless defined $_[0];
+=item ACK_COLOR_FILENAME
- my @files = grep { -f }
- map { File::Spec->catfile(@_, $_) }
- qw(.ackrc _ackrc);
+Specifies the color of the filename when it's printed in B<--group>
+mode. By default, it's "bold green".
- die File::Spec->catdir(@_) . " contains both .ackrc and _ackrc.\n" .
- "Please remove one of those files.\n"
- if @files > 1;
+The recognized attributes are clear, reset, dark, bold, underline,
+underscore, blink, reverse, concealed black, red, green, yellow,
+blue, magenta, on_black, on_red, on_green, on_yellow, on_blue,
+on_magenta, on_cyan, and on_white. Case is not significant.
+Underline and underscore are equivalent, as are clear and reset.
+The color alone sets the foreground color, and on_color sets the
+background color.
- return wantarray ? @files : $files[0];
-} # end _check_for_ackrc
+This option can also be set with B<--color-filename>.
+=item ACK_COLOR_MATCH
-sub find_config_files {
- my @config_files;
+Specifies the color of the matching text when printed in B<--color>
+mode. By default, it's "black on_yellow".
- 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';
- }
+This option can also be set with B<--color-match>.
- if ( $ENV{'ACKRC'} && -f $ENV{'ACKRC'} ) {
- push @config_files, $ENV{'ACKRC'};
- }
- else {
- push @config_files, _check_for_ackrc($ENV{'HOME'});
- }
+See B<ACK_COLOR_FILENAME> for the color specifications.
- 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 ACK_COLOR_LINENO
- # 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 );
-}
+Specifies the color of the line number when printed in B<--color>
+mode. By default, it's "bold yellow".
-1;
-package App::Ack::ConfigLoader;
+This option can also be set with B<--color-lineno>.
-use strict;
-use warnings;
+See B<ACK_COLOR_FILENAME> for the color specifications.
-use Carp 1.10 ();
-use Getopt::Long 2.36 ();
-use Text::ParseWords 3.1 ();
+=item ACK_PAGER
+Specifies a pager program, such as C<more>, C<less> or C<most>, to which
+ack will send its output.
-my @INVALID_COMBINATIONS;
+Using C<ACK_PAGER> does not suppress grouping and coloring like
+piping output on the command-line does, except that on Windows
+ack will assume that C<ACK_PAGER> does not support color.
-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 );
+C<ACK_PAGER_COLOR> overrides C<ACK_PAGER> if both are specified.
- @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],
- );
-}
+=item ACK_PAGER_COLOR
-sub process_filter_spec {
- my ( $spec ) = @_;
+Specifies a pager program that understands ANSI color sequences.
+Using C<ACK_PAGER_COLOR> does not suppress grouping and coloring
+like piping output on the command-line does.
- if ( $spec =~ /^(\w+):(\w+):(.*)/ ) {
- my ( $type_name, $ext_type, $arguments ) = ( $1, $2, $3 );
+If you are not on Windows, you never need to use C<ACK_PAGER_COLOR>.
- 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 );
+=back
- my @extensions = split(/,/, $extensions);
- foreach my $extension ( @extensions ) {
- $extension =~ s/^[.]//;
- }
+=head1 ACK & OTHER TOOLS
- return ( $type_name, App::Ack::Filter->create_filter('ext', @extensions) );
- }
- else {
- Carp::croak "invalid filter specification '$spec'";
- }
-}
+=head2 Vim integration
-sub process_filetypes {
- my ( $opt, $arg_sources ) = @_;
+F<ack> integrates easily with the Vim text editor. Set this in your
+F<.vimrc> to use F<ack> instead of F<grep>:
- 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;
+ set grepprg=ack\ -k
- my $add_spec = sub {
- my ( undef, $spec ) = @_;
+That example uses C<-k> to search through only files of the types ack
+knows about, but you may use other default flags. Now you can search
+with F<ack> and easily step through the results in Vim:
- my ( $name, $filter ) = process_filter_spec($spec);
+ :grep Dumper perllib
- push @{ $App::Ack::mappings{$name} }, $filter;
+Miles Sterrett has written a Vim plugin for F<ack> which allows you to use
+C<:Ack> instead of C<:grep>, as well as several other advanced features.
- $additional_specs{$name . '!'} = sub {
- my ( undef, $value ) = @_;
+L<https://github.com/mileszs/ack.vim>
- my @filters = @{ $App::Ack::mappings{$name} };
- if ( not $value ) {
- @filters = map { $_->invert() } @filters;
- }
+=head2 Emacs integration
- push @{ $opt->{'filters'} }, @filters;
- };
- };
+Phil Jackson put together an F<ack.el> extension that "provides a
+simple compilation mode ... has the ability to guess what files you
+want to search for based on the major-mode."
- my $set_spec = sub {
- my ( undef, $spec ) = @_;
+L<http://www.shellarchive.co.uk/content/emacs.html>
- my ( $name, $filter ) = process_filter_spec($spec);
+=head2 TextMate integration
- $App::Ack::mappings{$name} = [ $filter ];
+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<http://www.simplicidade.org/notes/archives/2008/03/search_in_proje.html>"
- $additional_specs{$name . '!'} = sub {
- my ( undef, $value ) = @_;
+=head2 Shell and Return Code
- my @filters = @{ $App::Ack::mappings{$name} };
- if ( not $value ) {
- @filters = map { $_->invert() } @filters;
- }
+For greater compatibility with I<grep>, I<ack> in normal use returns
+shell return or exit code of 0 only if something is found and 1 if
+no match is found.
- push @{ $opt->{'filters'} }, @filters;
- };
- };
+(Shell exit code 1 is C<$?=256> in perl with C<system> or backticks.)
- my $delete_spec = sub {
- my ( undef, $name ) = @_;
+The I<grep> code 2 for errors is not used.
- delete $App::Ack::mappings{$name};
- delete $additional_specs{$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.
- my %type_arg_specs = (
- 'type-add=s' => $add_spec,
- 'type-set=s' => $set_spec,
- 'type-del=s' => $delete_spec,
- );
+=cut
- for ( my $i = 0; $i < @{$arg_sources}; $i += 2) {
- my ( $source_name, $args ) = @{$arg_sources}[ $i, $i + 1];
+=head1 DEBUGGING ACK PROBLEMS
- if ( ref($args) ) {
- # $args are modified in place, so no need to munge $arg_sources
- Getopt::Long::GetOptionsFromArray($args, %type_arg_specs);
- }
- else {
- ( undef, $arg_sources->[$i + 1] ) =
- Getopt::Long::GetOptionsFromString($args, %type_arg_specs);
- }
- }
+If ack gives you output you're not expecting, start with a few simple steps.
- $additional_specs{'k|known-types'} = sub {
- my ( undef, $value ) = @_;
+=head2 Use B<--noenv>
- my @filters = map { @{$_} } values(%App::Ack::mappings);
+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>.
- push @{ $opt->{'filters'} }, @filters;
- };
+=head2 Use B<-f> to see what files have been selected
- return \%additional_specs;
-}
+Ack's B<-f> was originally added as a debugging tool. If ack is
+not finding matches you think it should find, run F<ack -f> to see
+what files have been selected. You can also add the C<--show-types>
+options to show the type of each file selected.
-sub removed_option {
- my ( $option, $explanation ) = @_;
+=head2 Use B<--dump>
- $explanation ||= '';
- return sub {
- warn "Option '$option' is not valid in ack 2\n$explanation";
- exit 1;
- };
-}
+This lists the ackrc files that are loaded and the options loaded
+from them.
+So for example you can find a list of directories that do not get searched or where filetypes are defined.
-sub get_arg_spec {
- my ( $opt, $extra_specs ) = @_;
+=head1 TIPS
- my $dash_a_explanation = <<EOT;
-This is because we now have -k/--known-types which makes it only select files
-of known types, rather than any text file (which is the behavior of ack 1.x).
-EOT
+=head2 Use the F<.ackrc> file.
- 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 { App::Ack::create_ackrc(); exit; },
- 'env!' => sub {
- my ( undef, $value ) = @_;
+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.
- 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 ) = @_;
+=head2 Use F<-f> for working with big codesets
- $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 ) = @_;
+Ack does more than search files. C<ack -f --perl> will create a
+list of all the Perl files in a tree, ideal for sending into F<xargs>.
+For example:
- # 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'");
- }
+ # 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->{idirs} } = grep {
- $_ ne $dir
- } @{ $opt->{idirs} };
- },
- '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 ) = @_;
+or if you prefer:
- my $cb_value = 1;
- if ( $value =~ s/^no// ) {
- $cb_value = 0;
- }
+ perl -p -i -e's/this/that/g' $(ack -f --perl)
- my $callback = $extra_specs->{ $value . '!' };
+=head2 Use F<-Q> when in doubt about metacharacters
- 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} = '-' },
+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...
- '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
-}
+=head2 Use ack to watch log files
-sub process_other {
- my ( $opt, $extra_specs, $arg_sources ) = @_;
+Here's one I used the other day to find trouble spots for a website
+visitor. The user had a problem loading F<troublesome.gif>, so I
+took the access log and scanned it with ack twice.
- 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',
- );
+ ack -Q aa.bb.cc.dd /path/to/access.log | ack -Q -B5 troublesome.gif
- my $argv_source;
- my $is_help_types_active;
+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.
- for ( my $i = 0; $i < @{$arg_sources}; $i += 2 ) {
- my ( $source_name, $args ) = @{$arg_sources}[ $i, $i + 1 ];
+=head2 Examples of F<--output>
- if ( $source_name eq 'ARGV' ) {
- $argv_source = $args;
- last;
- }
- }
+Following variables are useful in the expansion string:
- if ( $argv_source ) { # this *should* always be true, but you never know...
- my @copy = @{$argv_source};
+=over 4
- Getopt::Long::Configure('pass_through');
+=item C<$&>
- Getopt::Long::GetOptionsFromArray( \@copy,
- 'help-types' => \$is_help_types_active,
- );
+The whole string matched by PATTERN.
- Getopt::Long::Configure('no_pass_through');
- }
+=item C<$1>, C<$2>, ...
- my $arg_specs = get_arg_spec($opt, $extra_specs);
+The contents of the 1st, 2nd ... bracketed group in PATTERN.
- for ( my $i = 0; $i < @{$arg_sources}; $i += 2) {
- my ($source_name, $args) = @{$arg_sources}[$i, $i + 1];
+=item C<$`>
- my $ret;
- if ( ref($args) ) {
- $ret = Getopt::Long::GetOptionsFromArray( $args, %{$arg_specs} );
- }
- 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 string before the match.
- # XXX We need to check on a -- in the middle of a non-ARGV source
+=item C<$'>
- return;
-}
+The string after the match.
-sub should_dump_options {
- my ( $sources ) = @_;
+=back
- for(my $i = 0; $i < @{$sources}; $i += 2) {
- my ( $name, $options ) = @{$sources}[$i, $i + 1];
- if($name eq 'ARGV') {
- my $dump;
- Getopt::Long::Configure('default', 'pass_through', 'no_auto_help', 'no_auto_version');
- Getopt::Long::GetOptionsFromArray($options,
- 'dump' => \$dump,
- );
- return $dump;
- }
- }
- return;
-}
+For more details and other variables see
+L<http://perldoc.perl.org/perlvar.html#Variables-related-to-regular-expressions|perlvar>.
-sub explode_sources {
- my ( $sources ) = @_;
+This example shows how to add text around a particular pattern
+(in this case adding _ around word with "e")
- my @new_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
- Getopt::Long::Configure('default', 'pass_through', 'no_auto_help', 'no_auto_version');
+This shows how to pick out particular parts of a match using ( ) within regular expression.
- my %opt;
- my $arg_spec = get_arg_spec(\%opt);
+ ack '=head(\d+)\s+(.*)' --output=' $1 : $2'
+ input file contains "=head1 NAME"
+ output "1 : NAME"
- my $add_type = sub {
- my ( undef, $arg ) = @_;
+=head2 Share your knowledge
- # XXX refactor?
- if ( $arg =~ /(\w+)=/) {
- $arg_spec->{$1} = sub {};
- }
- else {
- ( $arg ) = split /:/, $arg;
- $arg_spec->{$arg} = sub {};
- }
- };
+Join the ack-users mailing list. Send me your tips and I may add
+them here.
- my $del_type = sub {
- my ( undef, $arg ) = @_;
+=head1 FAQ
- delete $arg_spec->{$arg};
- };
+=head2 Why isn't ack finding a match in (some file)?
- 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--;
+Probably because it's of a type that ack doesn't recognize. ack's
+searching behavior is driven by filetype. B<If ack doesn't know
+what kind of file it is, ack ignores the file.>
- my @copy = @chunk;
- Getopt::Long::GetOptionsFromArray(\@chunk,
- 'type-add=s' => $add_type,
- 'type-set=s' => $add_type,
- 'type-del=s' => $del_type,
- );
- Getopt::Long::GetOptionsFromArray(\@chunk, %{$arg_spec});
+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.
- splice @copy, -1 * @chunk if @chunk; # XXX explain this
- push @new_sources, $name, \@copy;
- }
- }
+=head2 Wouldn't it be great if F<ack> did search & replace?
- return \@new_sources;
-}
+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.
-sub compare_opts {
- my ( $a, $b ) = @_;
+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:
- my $first_a = $a->[0];
- my $first_b = $b->[0];
+ $ perl -i -p -e's/foo/bar/g' $(ack -f --php)
- $first_a =~ s/^--?//;
- $first_b =~ s/^--?//;
+=head2 Can I make ack recognize F<.xyz> files?
- return $first_a cmp $first_b;
-}
+Yes! Please see L</"Defining your own types">. If you think
+that F<ack> should recognize a type by default, please see
+L</"ENHANCEMENTS">.
-sub dump_options {
- my ( $sources ) = @_;
+=head2 There's already a program/package called ack.
- $sources = explode_sources($sources);
+Yes, I know.
- my %opts_by_source;
- my @source_names;
+=head2 Why is it called ack if it's called ack-grep?
- for(my $i = 0; $i < @{$sources}; $i += 2) {
- my ( $name, $contents ) = @{$sources}[$i, $i + 1];
- if ( not $opts_by_source{$name} ) {
- $opts_by_source{$name} = [];
- push @source_names, $name;
- }
- push @{$opts_by_source{$name}}, $contents;
- }
+The 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.
- foreach my $name (@source_names) {
- my $contents = $opts_by_source{$name};
+I suggest you make a symlink named F<ack> that points to F<ack-grep>
+because one of the crucial benefits of ack is having a name that's
+so short and simple to type.
- print $name, "\n";
- print '=' x length($name), "\n";
- print ' ', join(' ', @{$_}), "\n" foreach sort { compare_opts($a, $b) } @{$contents};
- }
+To do that, run this with F<sudo> or as root:
- return;
-}
+ ln -s /usr/bin/ack-grep /usr/bin/ack
-sub remove_default_options_if_needed {
- my ( $sources ) = @_;
+Alternatively, you could use a shell alias:
- my $default_index;
+ # bash/zsh
+ alias ack=ack-grep
- foreach my $index ( 0 .. $#$sources ) {
- if ( $sources->[$index] eq 'Defaults' ) {
- $default_index = $index;
- last;
- }
- }
+ # csh
+ alias ack ack-grep
+
+=head2 What does F<ack> mean?
+
+Nothing. I wanted a name that was easy to type and that you could
+pronounce as a single syllable.
- return $sources unless defined $default_index;
+=head2 Can I do multi-line regexes?
- my $should_remove = 0;
+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', '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',
- );
+If you want to see lines near your match, use the C<--A>, C<--B>
+and C<--C> switches for displaying context.
- foreach my $index ( $default_index + 2 .. $#$sources ) {
- next if $index % 2 != 0;
+=head2 Why is ack telling me I have an invalid option when searching for C<+foo>?
- my ( $name, $args ) = @{$sources}[ $index, $index + 1 ];
+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!)
- if (ref($args)) {
- Getopt::Long::GetOptionsFromArray($args,
- 'ignore-ack-defaults' => \$should_remove,
- );
- }
- else {
- ( undef, $sources->[$index + 1] ) = Getopt::Long::GetOptionsFromString($args,
- 'ignore-ack-defaults' => \$should_remove,
- );
- }
- }
+=head2 Why does C<"ack '.{40000,}'"> fail? Isn't that a valid regex?
- Getopt::Long::Configure('default');
- Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version');
+The Perl language limits the repetition quanitifier to 32K. You
+can search for C<.{32767}> but not C<.{32768}>.
- return $sources unless $should_remove;
+=head1 ACKRC LOCATION SEMANTICS
- my @copy = @{$sources};
- splice @copy, $default_index, 2;
- return \@copy;
-}
+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)
-sub check_for_mutually_exclusive_options {
- my ( $arg_sources ) = @_;
+=over 4
- my %mutually_exclusive_with;
- my @copy = @{$arg_sources};
+=item *
- for(my $i = 0; $i < @INVALID_COMBINATIONS; $i += 2) {
- my ( $lhs, $rhs ) = @INVALID_COMBINATIONS[ $i, $i + 1 ];
+Defaults are loaded from App::Ack::ConfigDefaults. This can be omitted
+using C<--ignore-ack-defaults>.
- 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 * Global ackrc
- while( @copy ) {
- my %set_opts;
+Options are then loaded from the global ackrc. This is located at
+C</etc/ackrc> on Unix-like systems, and
+C<C:\Documents and Settings\All Users\Application Data\ackrc> on Windows.
+This can be omitted using C<--noenv>.
- my ( $source_name, $args ) = splice @copy, 0, 2;
- $args = ref($args) ? [ @{$args} ] : [ Text::ParseWords::shellwords($args) ];
+=item * User ackrc
- foreach my $opt ( @{$args} ) {
- next unless $opt =~ /^[-+]/;
- last if $opt eq '--';
+Options are then loaded from the user's ackrc. This is located at
+C<$HOME/.ackrc> on Unix-like systems, and
+C<C:\Documents and Settings\$USER\Application Data\ackrc>. If a different
+ackrc is desired, it may be overridden with the C<$ACKRC> environment
+variable.
+This can be omitted using C<--noenv>.
- if( $opt =~ /^(.*)=/ ) {
- $opt = $1;
- }
- elsif ( $opt =~ /^(-[^-]).+/ ) {
- $opt = $1;
- }
+=item * Project ackrc
- $set_opts{ $opt } = 1;
+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 $mutex_opts = $mutually_exclusive_with{ $opt };
+=item * --ackrc
- next unless $mutex_opts;
+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.
- 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 environment variable C<ACK_OPTIONS>. This can
+be omitted using C<--noenv>.
- my %opt;
+=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
-1; # End of App::Ack::ConfigLoader
-package App::Ack::ConfigDefault;
+=item *
-use warnings;
-use strict;
+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.
-sub options {
- my @options = split( /\n/, _options_block() );
- @options = grep { /./ && !/^#/ } @options;
+=item *
- return @options;
-}
+A more flexible filter system has been added, so that more powerful file types
+may be created by the user. For details, please consult
+L</"Defining your own types">.
-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 80 characters of the first line
-# of text against a Perl regular expression. This is only for
-# the --type-add option.
+ack now loads multiple ackrc files; see L</"ACKRC LOCATION SEMANTICS"> for
+details.
+=item *
-# Directories to ignore
-# Bazaar
---ignore-directory=is:.bzr
+ack's default filter definitions aren't special; you may tell ack to
+completely disregard them if you don't like them.
-# Codeville
---ignore-directory=is:.cdv
+=back
-# Interface Builder
---ignore-directory=is:~.dep
---ignore-directory=is:~.dot
---ignore-directory=is:~.nib
---ignore-directory=is:~.plst
+=head2 REMOVED OPTIONS
-# Git
---ignore-directory=is:.git
+=over 4
-# Mercurial
---ignore-directory=is:.hg
+=item *
-# quilt
---ignore-directory=is:.pc
+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.
-# Subversion
---ignore-directory=is:.svn
+=item *
-# Monotone
---ignore-directory=is:_MTN
+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.
-# CVS
---ignore-directory=is:CVS
+=item *
-# RCS
---ignore-directory=is:RCS
+The B<--binary> option has been removed.
-# SCCS
---ignore-directory=is:SCCS
+=item *
-# darcs
---ignore-directory=is:_darcs
+The B<--skipped> option has been removed.
-# Vault/Fortress
---ignore-directory=is:_sgbak
+=item *
-# autoconf
---ignore-directory=is:autom4te.cache
+The B<--text> 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<--invert-file-match> option has been removed. Instead, you may
+use B<-v> with B<-g>.
+=back
+=head2 CHANGED OPTIONS
-# Files to ignore
-# Backup files
---ignore-file=ext:bak
---ignore-file=match:/~$/
+=over 4
-# Emacs swap files
---ignore-file=match:/^#.+#$/
+=item *
-# vi/vim swap files
---ignore-file=match:/[._].*\.swp$/
+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>.
-# core dumps
---ignore-file=match:/core\.\d+$/
+=back
-# minified Javascript
---ignore-file=match:/[.]min[.]js$/
+=head2 ADDED OPTIONS
+=over 4
-# Filetypes defined
+=item *
-# Perl http://perl.org/
---type-add=perl:ext:pl,pm,pod,t
---type-add=perl:firstlinematch:/#!.*\bperl/
+B<--files-from> was added so that a user may submit a list of filenames as
+a list of files to search.
-# 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
+=item *
-# Rakefiles http://rake.rubyforge.org/
---type-add=rake:is:Rakefile
+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.
-# CMake http://www.cmake.org/
---type-add=cmake:is:CMakeLists.txt
---type-add=cmake:ext:cmake
+=item *
-# Actionscript
---type-add=actionscript:ext:as,mxml
+B<-s> was added to tell ack to suppress error messages about non-existent or
+unreadable files.
-# Ada http://www.adaic.org/
---type-add=ada:ext:ada,adb,ads
+=item *
-# ASP http://msdn.microsoft.com/en-us/library/aa286483.aspx
---type-add=asp:ext:asp
+B<--ignore-directory> and B<--noignore-directory> were added as aliases for
+B<--ignore-dir> and B<--noignore-dir> respectively.
-# ASP.Net http://www.asp.net/
---type-add=aspx:ext:master,ascx,asmx,aspx,svc
+=item *
-# Assembly
---type-add=asm:ext:asm,s
+B<--ignore-file> was added so that users may specify patterns of files to
+ignore (ex. /.*~$/).
-# Batch
---type-add=batch:ext:bat,cmd
+=item *
-# ColdFusion http://en.wikipedia.org/wiki/ColdFusion
---type-add=cfmx:ext:cfc,cfm,cfml
+B<--dump> was added to allow users to easily find out which options are
+set where.
-# Clojure http://clojure.org/
---type-add=clojure:ext:clj
+=item *
-# C
-# .xs are Perl C files
---type-add=cc:ext:c,h,xs
+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.
-# C header files
---type-add=hh:ext:h
+=item *
-# C++
---type-add=cpp:ext:cpp,cc,cxx,m,hpp,hh,h,hxx
+B<--type-del> was added to selectively remove file type definitions.
-# C#
---type-add=csharp:ext:cs
+=item *
-# CSS http://www.w3.org/Style/CSS/
---type-add=css:ext:css
+B<--ignore-ack-defaults> was added so that users may ignore ack's default
+options in favor of their own.
-# 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<--bar> was added so ack users may consult Admiral Ackbar.
-# Erlang http://www.erlang.org/
---type-add=erlang:ext:erl,hrl
+=back
-# Fortran http://en.wikipedia.org/wiki/Fortran
---type-add=fortran:ext:f,f77,f90,f95,f03,for,ftn,fpp
+=head1 AUTHOR
-# Google Go http://golang.org/
---type-add=go:ext:go
+Andy Lester, C<< <andy at petdance.com> >>
-# Groovy http://groovy.codehaus.org/
---type-add=groovy:ext:groovy,gtmpl,gpp,grunit,gradle
+=head1 BUGS
-# Haskell http://www.haskell.org/
---type-add=haskell:ext:hs,lhs
+Please report any bugs or feature requests to the issues list at
+Github: L<https://github.com/petdance/ack2/issues>
-# HTML
---type-add=html:ext:htm,html
+=head1 ENHANCEMENTS
-# Java http://www.oracle.com/technetwork/java/index.html
---type-add=java:ext:java,properties
+All enhancement requests MUST first be posted to the ack-users
+mailing list at L<http://groups.google.com/group/ack-users>. I
+will not consider a request without it first getting seen by other
+ack users. This includes requests for new filetypes.
-# JavaScript
---type-add=js:ext:js
+There is a list of enhancements I want to make to F<ack> in the ack
+issues list at Github: L<https://github.com/petdance/ack2/issues>
-# JSP http://www.oracle.com/technetwork/java/javaee/jsp/index.html
---type-add=jsp:ext:jsp,jspx,jhtm,jhtml
+Patches are always welcome, but patches with tests get the most
+attention.
-# Common Lisp http://common-lisp.net/
---type-add=lisp:ext:lisp,lsp
+=head1 SUPPORT
-# Lua http://www.lua.org/
---type-add=lua:ext:lua
+Support for and information about F<ack> can be found at:
-# Objective-C
---type-add=objc:ext:m,h
+=over 4
-# Objective-C++
---type-add=objcpp:ext:mm,h
+=item * The ack homepage
-# OCaml http://caml.inria.fr/
---type-add=ocaml:ext:ml,mli
+L<http://beyondgrep.com/>
-# Parrot http://www.parrot.org/
---type-add=parrot:ext:pir,pasm,pmc,ops,pod,pg,tg
+=item * The ack-users mailing list
-# PHP http://www.php.net/
---type-add=php:ext:php,phpt,php3,php4,php5,phtml
---type-add=php:firstlinematch:/#!.*\bphp/
+L<http://groups.google.com/group/ack-users>
-# Plone http://plone.org/
---type-add=plone:ext:pt,cpt,metadata,cpy,py
+=item * The ack issues list at Github
-# Python http://www.python.org/
---type-add=python:ext:py
---type-add=python:firstlinematch:/#!.*\bpython/
+L<https://github.com/petdance/ack2/issues>
-# R http://www.r-project.org/
---type-add=rr:ext:R
+=item * AnnoCPAN: Annotated CPAN documentation
-# 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<http://annocpan.org/dist/ack>
-# Scala http://www.scala-lang.org/
---type-add=scala:ext:scala
+=item * CPAN Ratings
-# Scheme http://groups.csail.mit.edu/mac/projects/scheme/
---type-add=scheme:ext:scm,ss
+L<http://cpanratings.perl.org/d/ack>
-# Shell
---type-add=shell:ext:sh,bash,csh,tcsh,ksh,zsh
---type-add=shell:firstlinematch:/(?:ba|t?c|k|z)?sh\b/
+=item * Search CPAN
-# Smalltalk http://www.smalltalk.org/
---type-add=smalltalk:ext:st
+L<http://search.cpan.org/dist/ack>
-# SQL http://www.iso.org/iso/catalogue_detail.htm?csnumber=45498
---type-add=sql:ext:sql,ctl
+=item * Git source repository
-# Tcl http://www.tcl.tk/
---type-add=tcl:ext:tcl,itcl,itk
+L<https://github.com/petdance/ack2>
-# LaTeX http://www.latex-project.org/
---type-add=tex:ext:tex,cls,sty
+=back
-# Template Toolkit http://template-toolkit.org/
---type-add=tt:ext:tt,tt2,ttml
+=head1 ACKNOWLEDGEMENTS
-# Visual Basic
---type-add=vb:ext:bas,cls,frm,ctl,vb,resx
+How appropriate to have I<ack>nowledgements!
-# Verilog
---type-add=verilog:ext:v,vh,sv
+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,
+GE<aacute>bor SzabE<oacute>,
+Tod Hagan,
+Michael Hendricks,
+E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason,
+Piers Cawley,
+Stephen Steneker,
+Elias Lutfallah,
+Mark Leighton Fisher,
+Matt Diephouse,
+Christian Jaeger,
+Bill Sully,
+Bill Ricker,
+David Golden,
+Nilson Santos F. Jr,
+Elliot Shank,
+Merijn Broeren,
+Uwe Voelker,
+Rick Scott,
+Ask BjE<oslash>rn Hansen,
+Jerry Gay,
+Will Coleda,
+Mike O'Regan,
+Slaven ReziE<0x107>,
+Mark Stosberg,
+David Alan Pisoni,
+Adriano Ferreira,
+James Keenan,
+Leland Johnson,
+Ricardo Signes,
+Pete Krawczyk and
+Rob Hoelz.
-# VHDL http://www.eda.org/twiki/bin/view.cgi/P1076/WebHome
---type-add=vhdl:ext:vhd,vhdl
+=head1 COPYRIGHT & LICENSE
-# Vim http://www.vim.org/
---type-add=vim:ext:vim
+Copyright 2005-2013 Andy Lester.
-# XML http://www.w3.org/TR/REC-xml/
---type-add=xml:ext:xml,dtd,xsl,xslt,ent
---type-add=xml:firstlinematch:/<[?]xml/
+This program is free software; you can redistribute it and/or modify
+it under the terms of the Artistic License v2.0.
-# YAML http://yaml.org/
---type-add=yaml:ext:yaml,yml
-HERE
-}
+See http://www.perlfoundation.org/artistic_license_2_0 or the LICENSE.md
+file that comes with the ack distribution.
-1;
+=cut