# Please DO NOT EDIT or send patches for it.
#
# Please take a look at the source from
-# http://github.com/petdance/ack
+# https://github.com/beyondgrep/ack3
# and submit patches against the individual files
# that build ack.
#
-use warnings;
+$App::Ack::STANDALONE = 1;
+{
+package main;
+
use strict;
+use warnings;
+
+our $VERSION = 'v3.8.1'; # Check https://beyondgrep.com/ for updates
+
+use 5.010001;
+
+use File::Spec ();
+use Getopt::Long ();
+
+
+
+# Global command-line options
+our $opt_1;
+our $opt_A;
+our $opt_B;
+our $opt_break;
+our $opt_color;
+our $opt_column;
+our $opt_debug;
+our $opt_c;
+our $opt_f;
+our $opt_g;
+our $opt_heading;
+our $opt_L;
+our $opt_l;
+our $opt_m;
+our $opt_output;
+our $opt_passthru;
+our $opt_p;
+our $opt_range_start;
+our $opt_range_end;
+our $opt_regex;
+our $opt_show_filename;
+our $opt_show_types;
+our $opt_underline;
+our $opt_v;
+
+# Flag if we need any context tracking.
+our $is_tracking_context;
+
+# The regex that we use to match each line in the file.
+our $re_match;
+
+# Regex for matching for highlighting in matched lines.
+our $re_hilite;
+
+# The regex that matches for things we want to exclude via the --not option.
+our $re_not;
-our $VERSION = '1.96';
-# Check http://betterthangrep.com/ for updates
+# Version of the match regex for checking to see if the file should be scanned line-by-line.
+our $re_scan;
-# These are all our globals.
+our @special_vars_used_by_opt_output;
+our $using_ranges;
+
+# Internal stats for debugging.
+our %stats;
MAIN: {
+ $App::Ack::ORIGINAL_PROGRAM_NAME = $0;
+ $0 = join(' ', 'ack', $0);
+ $App::Ack::ors = "\n";
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 '--' );
+ for my $arg ( @ARGV ) {
+ last if ( $arg eq '--' );
- # Priorities! Get the --thpppt checking out of the way.
- /^--th[pt]+t+$/ && App::Ack::_thpppt($_);
+ # Get the --thpppt, --bar, --cathy and --man checking out of the way.
+ $arg =~ /^--th[pt]+t+$/ and App::Ack::thpppt($arg);
+ $arg eq '--bar' and App::Ack::ackbar();
+ $arg eq '--cathy' and App::Ack::cathy();
# See if we want to ignore the environment. (Don't tell Al Gore.)
- if ( /^--(no)?env$/ ) {
- $env_is_usable = defined $1 ? 0 : 1;
- }
+ $arg eq '--env' and $env_is_usable = 1;
+ $arg eq '--noenv' and $env_is_usable = 0;
}
+
if ( $env_is_usable ) {
- unshift( @ARGV, App::Ack::read_ackrc() );
+ if ( $ENV{ACK_OPTIONS} ) {
+ App::Ack::warn( 'WARNING: ack no longer uses the ACK_OPTIONS environment variable. Use an ackrc file instead.' );
+ }
}
else {
my @keys = ( 'ACKRC', grep { /^ACK_/ } keys %ENV );
delete @ENV{@keys};
}
- App::Ack::load_colors();
- if ( exists $ENV{ACK_SWITCHES} ) {
- App::Ack::warn( 'ACK_SWITCHES is no longer supported. Use ACK_OPTIONS.' );
+ # Load colors
+ my $modules_loaded_ok = eval 'use Term::ANSIColor 1.10 (); 1;';
+ if ( $modules_loaded_ok && $App::Ack::is_windows ) {
+ $modules_loaded_ok = eval 'use Win32::Console::ANSI; 1;';
}
+ if ( $modules_loaded_ok ) {
+ $ENV{ACK_COLOR_MATCH} ||= 'black on_yellow';
+ $ENV{ACK_COLOR_FILENAME} ||= 'bold green';
+ $ENV{ACK_COLOR_LINENO} ||= 'bold yellow';
+ $ENV{ACK_COLOR_COLNO} ||= 'bold yellow';
+ }
+
+ App::Ack::ConfigLoader::configure_parser( 'no_auto_abbrev', 'pass_through' );
+ Getopt::Long::GetOptions(
+ help => sub { App::Ack::show_help(); exit; },
+ version => sub { App::Ack::print( App::Ack::get_version_statement() ); exit; },
+ man => sub { App::Ack::show_man(); },
+ );
if ( !@ARGV ) {
App::Ack::show_help();
exit 1;
}
- main();
-}
+ my @arg_sources = App::Ack::ConfigLoader::retrieve_arg_sources();
+
+ my $opt = App::Ack::ConfigLoader::process_args( @arg_sources );
+
+ $opt_1 = $opt->{1};
+ $opt_A = $opt->{A};
+ $opt_B = $opt->{B};
+ $opt_break = $opt->{break};
+ $opt_c = $opt->{c};
+ $opt_color = $opt->{color};
+ $opt_column = $opt->{column};
+ $opt_debug = $opt->{debug};
+ $opt_f = $opt->{f};
+ $opt_g = $opt->{g};
+ $opt_heading = $opt->{heading};
+ $opt_L = $opt->{L};
+ $opt_l = $opt->{l};
+ $opt_m = $opt->{m};
+ $opt_output = $opt->{output};
+ $opt_p = $opt->{p};
+ $opt_passthru = $opt->{passthru};
+ $opt_range_start = $opt->{range_start};
+ $opt_range_end = $opt->{range_end};
+ $opt_regex = $opt->{regex};
+ $opt_show_filename = $opt->{show_filename};
+ $opt_show_types = $opt->{show_types};
+ $opt_underline = $opt->{underline};
+ $opt_v = $opt->{v};
+
+ if ( $opt_show_types && not( $opt_f || $opt_g ) ) {
+ App::Ack::die( '--show-types can only be used with -f or -g.' );
+ }
-sub main {
- my $opt = App::Ack::get_command_line_options();
+ if ( $opt_range_start ) {
+ ($opt_range_start, undef) = App::Ack::build_regex( $opt_range_start, {} );
+ }
+ if ( $opt_range_end ) {
+ ($opt_range_end, undef) = App::Ack::build_regex( $opt_range_end, {} );
+ }
+ $using_ranges = $opt_range_start || $opt_range_end;
- $| = 1 if $opt->{flush}; # Unbuffer the output if flush mode
+ $App::Ack::report_bad_filenames = !$opt->{s};
+ $App::Ack::ors = $opt->{print0} ? "\0" : "\n";
- if ( App::Ack::input_from_pipe() ) {
- # We're going into filter mode
- for ( qw( f g l ) ) {
- $opt->{$_} and App::Ack::die( "Can't use -$_ when acting as a filter." );
- }
- $opt->{show_filename} = 0;
- $opt->{regex} = App::Ack::build_regex( defined $opt->{regex} ? $opt->{regex} : shift @ARGV, $opt );
- if ( my $nargs = @ARGV ) {
- my $s = $nargs == 1 ? '' : 's';
- App::Ack::warn( "Ignoring $nargs argument$s on the command-line while acting as a filter." );
+ if ( !defined($opt_color) && !$opt_g ) {
+ my $windows_color = 1;
+ if ( $App::Ack::is_windows ) {
+ $windows_color = eval { require Win32::Console::ANSI; };
}
+ $opt_color = !App::Ack::output_to_pipe() && $windows_color;
+ }
+ $opt_heading //= !App::Ack::output_to_pipe();
+ $opt_break //= !App::Ack::output_to_pipe();
- my $res = App::Ack::Resource::Basic->new( '-' );
- my $nmatches;
- if ( $opt->{count} ) {
- $nmatches = App::Ack::search_and_list( $res, $opt );
- }
- else {
- # normal searching
- $nmatches = App::Ack::search_resource( $res, $opt );
+ if ( defined($opt->{H}) || defined($opt->{h}) ) {
+ $opt_show_filename = $opt->{show_filename} = $opt->{H} && !$opt->{h};
+ }
+
+ if ( defined $opt_output ) {
+ # Expand out \t, \n and \r.
+ $opt_output =~ s/\\n/\n/g;
+ $opt_output =~ s/\\r/\r/g;
+ $opt_output =~ s/\\t/\t/g;
+
+ my @supported_special_variables = ( 1..9, qw( _ . ` & ' + f ) );
+ @special_vars_used_by_opt_output = grep { $opt_output =~ /\$$_/ } @supported_special_variables;
+
+ # If the $opt_output contains $&, $` or $', those vars won't be
+ # captured until they're used at least once in the program.
+ # Do the eval to make this happen.
+ for my $i ( @special_vars_used_by_opt_output ) {
+ if ( $i eq q{&} || $i eq q{'} || $i eq q{`} ) {
+ no warnings; # They will be undef, so don't warn.
+ eval qq{"\$$i"};
+ }
}
- $res->close();
- App::Ack::exit_from_ack( $nmatches );
}
- my $file_matching = $opt->{f} || $opt->{lines};
- if ( $file_matching ) {
- App::Ack::die( "Can't specify both a regex ($opt->{regex}) and use one of --line, -f or -g." ) if $opt->{regex};
+
+ # Set up file filters.
+ my $files;
+ if ( $App::Ack::is_filter_mode && !$opt->{files_from} ) { # probably -x
+ $files = App::Ack::Files->from_stdin();
+ $opt_regex //= shift @ARGV;
+ defined $opt_regex or App::Ack::die( 'No regular expression found.' );
+ ($re_match, $re_not, $re_hilite, $re_scan) = App::Ack::build_all_regexes( $opt_regex, $opt );
+ $stats{re_match} = $re_match;
+ $stats{re_not} = $re_not;
+ $stats{re_hilite} = $re_hilite;
+ $stats{re_scan} = $re_scan;
}
else {
- $opt->{regex} = App::Ack::build_regex( defined $opt->{regex} ? $opt->{regex} : shift @ARGV, $opt );
- }
-
- # check that all regexes do compile fine
- App::Ack::check_regex( $_ ) for ( $opt->{regex}, $opt->{G} );
+ if ( $opt_f ) {
+ # No need to check for regex, since mutex options are handled elsewhere.
+ }
+ else {
+ $opt_regex //= shift @ARGV;
+ defined $opt_regex or App::Ack::die( 'No regular expression found.' );
+ ($re_match, $re_not, $re_hilite, $re_scan) = App::Ack::build_all_regexes( $opt_regex, $opt );
+ $stats{re_match} = $re_match;
+ $stats{re_not} = $re_not;
+ $stats{re_hilite} = $re_hilite;
+ $stats{re_scan} = $re_scan;
+ }
+ my @start;
+ if ( not defined $opt->{files_from} ) {
+ @start = @ARGV;
+ }
+ if ( !exists($opt->{show_filename}) ) {
+ unless(@start == 1 && !(-d $start[0])) {
+ $opt_show_filename = $opt->{show_filename} = 1;
+ }
+ }
- my $what = App::Ack::get_starting_points( \@ARGV, $opt );
- my $iter = App::Ack::get_iterator( $what, $opt );
- App::Ack::filetype_setup();
+ if ( defined $opt->{files_from} ) {
+ $files = App::Ack::Files->from_file( $opt, $opt->{files_from} );
+ exit 1 unless $files;
+ }
+ 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" );
+ }
+ }
- my $nmatches = 0;
+ $opt->{file_filter} = _compile_file_filter($opt, \@start);
+ $opt->{descend_filter} = _compile_descend_filter($opt);
+ $files = App::Ack::Files->from_argv( $opt, \@start );
+ }
+ }
App::Ack::set_up_pager( $opt->{pager} ) if defined $opt->{pager};
- if ( $opt->{f} ) {
- $nmatches = App::Ack::print_files( $iter, $opt );
+
+ my $nmatches;
+ if ( $opt_f || $opt_g ) {
+ $nmatches = file_loop_fg( $files );
+ }
+ elsif ( $opt_c ) {
+ $nmatches = file_loop_c( $files );
}
- elsif ( $opt->{l} || $opt->{count} ) {
- $nmatches = App::Ack::print_files_with_matches( $iter, $opt );
+ elsif ( $opt_l || $opt_L ) {
+ $nmatches = file_loop_lL( $files );
}
else {
- $nmatches = App::Ack::print_matches( $iter, $opt );
+ $nmatches = file_loop_normal( $files );
+ }
+
+ if ( $opt_debug ) {
+ require List::Util;
+ my @stats = qw( re_match re_scan re_not prescans linescans filematches linematches );
+ my $width = List::Util::max( map { length } @stats );
+
+ for my $stat ( @stats ) {
+ App::Ack::warn( sprintf( '%-*.*s = %s', $width, $width, $stat, $stats{$stat} // 'undef' ) );
+ }
}
+
close $App::Ack::fh;
+
App::Ack::exit_from_ack( $nmatches );
-}
+} # End of MAIN
-=head1 NAME
-ack - grep-like text finder
+exit 0;
-=head1 SYNOPSIS
- ack [options] PATTERN [FILE...]
- ack -f [options] [DIRECTORY...]
+sub file_loop_fg {
+ my $files = shift;
-=head1 DESCRIPTION
+ my $nmatches = 0;
+ while ( defined( my $file = $files->next ) ) {
+ if ( $opt_show_types ) {
+ App::Ack::show_types( $file );
+ }
+ elsif ( $opt_g ) {
+ print_line_with_options( undef, $file->name, 0, $App::Ack::ors );
+ }
+ else {
+ App::Ack::say( $file->name );
+ }
+ ++$nmatches;
+ last if defined($opt_m) && ($nmatches >= $opt_m);
+ }
-Ack is designed as a replacement for 99% of the uses of F<grep>.
+ return $nmatches;
+}
-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.
-Ack can also list files that would be searched, without actually searching
-them, to let you take advantage of ack's file-type filtering capabilities.
+sub file_loop_c {
+ my $files = shift;
-=head1 FILE SELECTION
+ my $total_count = 0;
+ while ( defined( my $file = $files->next ) ) {
+ my $matches_for_this_file = count_matches_in_file( $file );
-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.
+ if ( not $opt_show_filename ) {
+ $total_count += $matches_for_this_file;
+ next;
+ }
-With no file selections, I<ack> only searches files of types that
-it recognizes. If you have a file called F<foo.wango>, and I<ack>
-doesn't know what a .wango file is, I<ack> won't search it.
+ if ( !$opt_l || $matches_for_this_file > 0 ) {
+ if ( $opt_show_filename ) {
+ my $display_filename = $file->name;
+ if ( $opt_color ) {
+ $display_filename = Term::ANSIColor::colored($display_filename, $ENV{ACK_COLOR_FILENAME});
+ }
+ App::Ack::say( $display_filename, ':', $matches_for_this_file );
+ }
+ else {
+ App::Ack::say( $matches_for_this_file );
+ }
+ }
+ }
-The B<-a> option tells I<ack> to select all files, regardless of
-type.
+ if ( !$opt_show_filename ) {
+ App::Ack::say( $total_count );
+ }
-Some files will never be selected by I<ack>, even with B<-a>,
-including:
+ return;
+}
-=over 4
-=item * Backup files: Files matching F<#*#> or ending with F<~>.
+sub file_loop_lL {
+ my $files = shift;
-=item * Coredumps: Files matching F<core.\d+>
+ my $nmatches = 0;
+ while ( defined( my $file = $files->next ) ) {
+ my $is_match = count_matches_in_file( $file, 1 );
-=back
+ if ( $opt_L ? !$is_match : $is_match ) {
+ App::Ack::say( $file->name );
+ ++$nmatches;
-However, I<ack> always searches the files given on the command line,
-no matter what type. Furthermore, by specifying the B<-u> option all
-files will be searched.
+ last if $opt_1;
+ last if defined($opt_m) && ($nmatches >= $opt_m);
+ }
+ }
-=head1 DIRECTORY SELECTION
+ return $nmatches;
+}
-I<ack> descends through the directory tree of the starting directories
-specified. 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
-F<ack --help>.
+sub _compile_descend_filter {
+ my ( $opt ) = @_;
-=head1 WHEN TO USE GREP
+ my $idirs = 0;
+ my $dont_ignore_dirs = 0;
-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.
+ for my $filter (@{$opt->{idirs} || []}) {
+ if ($filter->is_inverted()) {
+ $dont_ignore_dirs++;
+ }
+ else {
+ $idirs++;
+ }
+ }
-E.g., searching through huge files looking for regexes that can be
-expressed with I<grep> syntax should be quicker with I<grep>.
+ # 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;
-If your script or parent program uses I<grep> C<--quiet> or
-C<--silent> or needs exit 2 on IO error, use I<grep>.
+ $idirs = $opt->{idirs};
-=head1 OPTIONS
+ return sub {
+ my $file = App::Ack::File->new($File::Next::dir);
+ return !grep { $_->filter($file) } @{$idirs};
+ };
+}
-=over 4
-=item B<-a>, B<--all>
+sub _compile_file_filter {
+ my ( $opt, $start ) = @_;
-Operate on all files, regardless of type (but still skip directories
-like F<blib>, F<CVS>, etc.)
+ my $ifiles_filters = $opt->{ifiles};
-=item B<-A I<NUM>>, B<--after-context=I<NUM>>
+ my $filters = $opt->{'filters'} || [];
+ my $direct_filters = App::Ack::Filter::Collection->new();
+ my $inverse_filters = App::Ack::Filter::Collection->new();
-Print I<NUM> lines of trailing context after matching lines.
+ 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);
+ }
+ }
-=item B<-B I<NUM>>, B<--before-context=I<NUM>>
+ my %is_member_of_starting_set = map { (get_file_id($_) => 1) } @{$start};
-Print I<NUM> lines of leading context before matching lines.
+ my @ignore_dir_filter = @{$opt->{idirs} || []};
+ my @is_inverted = map { $_->is_inverted() } @ignore_dir_filter;
+ # This depends on InverseFilter->invert returning the original filter (for optimization).
+ @ignore_dir_filter = map { $_->is_inverted() ? $_->invert() : $_ } @ignore_dir_filter;
+ my $dont_ignore_dir_filter = grep { $_ } @is_inverted;
+ my $previous_dir = '';
+ my $previous_dir_ignore_result;
-=item B<-C [I<NUM>]>, B<--context[=I<NUM>]>
+ return sub {
+ if ( $opt_g ) {
+ if ( $File::Next::name =~ /$re_match/o ) {
+ return 0 if $opt_v;
+ }
+ else {
+ return 0 if !$opt_v;
+ }
+ }
+ # ack always selects files that are specified on the command
+ # line, regardless of filetype. If you want to ack a JPEG,
+ # and say "ack foo whatever.jpg" it will do it for you.
+ return 1 if $is_member_of_starting_set{ get_file_id($File::Next::name) };
+
+ if ( $dont_ignore_dir_filter ) {
+ if ( $previous_dir eq $File::Next::dir ) {
+ if ( $previous_dir_ignore_result ) {
+ return 0;
+ }
+ }
+ else {
+ my @dirs = File::Spec->splitdir($File::Next::dir);
-Print I<NUM> lines (default 2) of context around matching lines.
+ my $is_ignoring;
-=item B<-c>, B<--count>
+ for ( my $i = 0; $i < @dirs; $i++) {
+ my $dir_rsrc = App::Ack::File->new(File::Spec->catfile(@dirs[0 .. $i]));
-Suppress normal output; instead print a count of matching lines for
-each input file. If B<-l> is in effect, it will only show the
-number of lines for each file that has lines matching. Without
-B<-l>, some line counts may be zeroes.
+ my $j = 0;
+ for my $filter (@ignore_dir_filter) {
+ if ( $filter->filter($dir_rsrc) ) {
+ $is_ignoring = !$is_inverted[$j];
+ }
+ $j++;
+ }
+ }
-If combined with B<-h> (B<--no-filename>) ack outputs only one total count.
+ $previous_dir = $File::Next::dir;
+ $previous_dir_ignore_result = $is_ignoring;
-=item B<--color>, B<--nocolor>
+ if ( $is_ignoring ) {
+ return 0;
+ }
+ }
+ }
-B<--color> highlights the matching text. B<--nocolor> supresses
-the color. This is on by default unless the output is redirected.
+ # 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;
-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.
+ # We can't handle unreadable filenames; report them.
+ if ( not -r _ ) {
+ use filetest 'access';
-=item B<--color-filename=I<color>>
+ if ( not -R $File::Next::name ) {
+ if ( $App::Ack::report_bad_filenames ) {
+ App::Ack::warn( "${File::Next::name}: cannot open file for reading" );
+ }
+ return 0;
+ }
+ }
-Sets the color to be used for filenames.
+ my $file = App::Ack::File->new($File::Next::name);
-=item B<--color-match=I<color>>
+ if ( $ifiles_filters && $ifiles_filters->filter($file) ) {
+ return 0;
+ }
-Sets the color to be used for matches.
+ my $match_found = $direct_filters->filter($file);
-=item B<--color-lineno=I<color>>
+ # Don't bother invoking inverse filters unless we consider the current file a match.
+ if ( $match_found && $inverse_filters->filter( $file ) ) {
+ $match_found = 0;
+ }
+ return $match_found;
+ }; # End of compiled sub
+}
-Sets the color to be used for line numbers.
-=item B<--column>
+# 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 ) = @_;
-Show the column number of the first match. This is helpful for editors
-that can place your cursor at a given position.
+ 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;
+ }
+ }
+}
-=item B<--env>, B<--noenv>
-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 $match_colno;
-=item B<--flush>
+{
-B<--flush> flushes output immediately. This is off by default
-unless ack is running interactively (when output goes to a pipe
-or file).
+# Number of context lines
+my $n_before_ctx_lines;
+my $n_after_ctx_lines;
-=item B<-f>
+# Array to keep track of lines that might be required for a "before" context
+my @before_context_buf;
+# Position to insert next line in @before_context_buf
+my $before_context_pos;
-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.
+# Number of "after" context lines still pending
+my $after_context_pending;
-=item B<--follow>, B<--nofollow>
+# Number of latest line that got printed
+my $printed_lineno;
-Follow or don't follow symlinks, other than whatever starting files
-or directories were specified on the command line.
+my $is_first_match;
+state $has_printed_from_any_file;
-This is off by default.
-=item B<-G I<REGEX>>
+sub file_loop_normal {
+ my $files = shift;
-Only paths matching I<REGEX> are included in the search. The entire
-path and filename are matched against I<REGEX>, and I<REGEX> is a
-Perl regular expression, not a shell glob.
+ $n_before_ctx_lines = $opt_output ? 0 : ($opt_B || 0);
+ $n_after_ctx_lines = $opt_output ? 0 : ($opt_A || 0);
-The options B<-i>, B<-w>, B<-v>, and B<-Q> do not apply to this I<REGEX>.
+ @before_context_buf = (undef) x $n_before_ctx_lines;
+ $before_context_pos = 0;
-=item B<-g I<REGEX>>
+ $is_tracking_context = $n_before_ctx_lines || $n_after_ctx_lines;
-Print files where the relative path + filename matches I<REGEX>. This option is
-a convenience shortcut for B<-f> B<-G I<REGEX>>.
+ $is_first_match = 1;
-The options B<-i>, B<-w>, B<-v>, and B<-Q> do not apply to this I<REGEX>.
+ my $nmatches = 0;
+ while ( defined( my $file = $files->next ) ) {
+ if ($is_tracking_context) {
+ $printed_lineno = 0;
+ $after_context_pending = 0;
+ if ( $opt_heading ) {
+ $is_first_match = 1;
+ }
+ }
+ my $needs_line_scan = 1;
+ if ( !$opt_passthru && !$opt_v ) {
+ $stats{prescans}++;
+ if ( $file->may_be_present( $re_scan ) ) {
+ $file->reset();
+ }
+ else {
+ $needs_line_scan = 0;
+ }
+ }
+ if ( $needs_line_scan ) {
+ $stats{linescans}++;
+ $nmatches += print_matches_in_file( $file );
+ }
+ last if $opt_1 && $nmatches;
+ }
-=item B<--group>, B<--nogroup>
+ return $nmatches;
+}
-B<--group> groups matches by file name with. 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 print_matches_in_file {
+ my $file = shift;
-=item B<-H>, B<--with-filename>
+ my $filename = $file->name;
-Print the filename for each match.
+ my $fh = $file->open;
+ if ( !$fh ) {
+ if ( $App::Ack::report_bad_filenames ) {
+ App::Ack::warn( "$filename: $!" );
+ }
+ return 0;
+ }
-=item B<-h>, B<--no-filename>
+ my $display_filename = $filename;
+ if ( $opt_show_filename && $opt_heading && $opt_color ) {
+ $display_filename = Term::ANSIColor::colored($display_filename, $ENV{ACK_COLOR_FILENAME});
+ }
-Suppress the prefixing of filenames on output when multiple files are
-searched.
+ # Check for context before the main loop, so we don't pay for it if we don't need it.
+ my $nmatches;
+ my $max_count = $opt_m || -1; # Go negative for no limit so it can never reduce to 0.
+ if ( $is_tracking_context ) {
+ $nmatches = pmif_context( $fh, $filename, $display_filename, $max_count );
+ }
+ elsif ( $opt_passthru ) {
+ $nmatches = pmif_passthru( $fh, $filename, $display_filename, $max_count );
+ }
+ elsif ( $opt_v ) {
+ $nmatches = pmif_opt_v( $fh, $filename, $display_filename, $max_count );
+ }
+ else {
+ $nmatches = pmif_normal( $fh, $filename, $display_filename, $max_count );
+ }
-=item B<--help>
+ return $nmatches;
+}
-Print a short help statement.
-=item B<-i>, B<--ignore-case>
+sub pmif_context {
+ my $fh = shift;
+ my $filename = shift;
+ my $display_filename = shift;
+ my $max_count = shift;
-Ignore case in the search strings.
+ my $in_range = range_setup();
+ my $has_printed_from_this_file;
+ my $nmatches = 0;
-This applies only to the PATTERN, not to the regexes given for the B<-g>
-and B<-G> options.
+ $after_context_pending = 0;
+ local $_ = undef;
-=item B<--[no]ignore-dir=I<DIRNAME>>
+ while ( <$fh> ) {
+ chomp;
+ $match_colno = undef;
-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).
+ $in_range = 1 if ( $using_ranges && !$in_range && defined($opt_range_start) && /$opt_range_start/o );
-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.
+ my $does_match;
+ if ( $in_range ) {
+ $does_match = /$re_match/o;
+ if ( $does_match && defined($re_not) ) {
+ local @-;
+ $does_match = !/$re_not/o;
+ }
+ if ( $opt_v ) {
+ $does_match = !$does_match;
+ }
+ else {
+ if ( $does_match ) {
+ # @- = @LAST_MATCH_START
+ $match_colno = $-[0] + 1;
+ }
+ }
+ }
-=item B<--line=I<NUM>>
+ if ( $does_match && $max_count ) {
+ if ( !$has_printed_from_this_file ) {
+ $stats{filematches}++;
+ if ( $opt_break && $has_printed_from_any_file ) {
+ App::Ack::print_blank_line();
+ }
+ if ( $opt_show_filename && $opt_heading ) {
+ App::Ack::say( $display_filename );
+ }
+ }
+ print_line_with_context( $filename, $_, $. );
+ $has_printed_from_this_file = 1;
+ $stats{linematches}++;
+ $nmatches++;
+ $max_count--;
+ }
+ else {
+ if ( $after_context_pending ) {
+ # Disable $opt_column since there are no matches in the context lines.
+ local $opt_column = 0;
+ print_line_with_options( $filename, $_, $., '-' );
+ --$after_context_pending;
+ }
+ elsif ( $n_before_ctx_lines ) {
+ # Save line for "before" context.
+ $before_context_buf[$before_context_pos] = $_;
+ $before_context_pos = ($before_context_pos+1) % $n_before_ctx_lines;
+ }
+ }
-Only print line I<NUM> of each file. Multiple lines can be given with multiple
-B<--line> options or as a comma separated list (B<--line=3,5,7>). B<--line=4-7>
-also works. The lines are always output in ascending order, no matter the
-order given on the command line.
+ $in_range = 0 if ( $using_ranges && $in_range && defined($opt_range_end) && /$opt_range_end/o );
-=item B<-l>, B<--files-with-matches>
+ last if ($max_count == 0) && ($after_context_pending == 0);
+ }
-Only print the filenames of matching files, instead of the matching text.
+ return $nmatches;
+}
-=item B<-L>, B<--files-without-matches>
-Only print the filenames of files that do I<NOT> match. This is equivalent
-to specifying B<-l> and B<-v>.
+sub pmif_passthru {
+ my $fh = shift;
+ my $filename = shift;
+ my $display_filename = shift;
+ my $max_count = shift;
-=item B<--match I<REGEX>>
+ my $in_range = range_setup();
+ my $has_printed_from_this_file;
+ my $nmatches = 0;
-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.
+ local $_ = undef;
- # search for foo and bar in given files
- ack file1 t/file* --match foo
- ack file1 t/file* --match bar
+ while ( <$fh> ) {
+ chomp;
-=item B<-m=I<NUM>>, B<--max-count=I<NUM>>
+ $in_range = 1 if ( $using_ranges && !$in_range && defined($opt_range_start) && /$opt_range_start/o );
-Stop reading a file after I<NUM> matches.
+ $match_colno = undef;
+ my $does_match = /$re_match/o;
+ if ( $does_match && defined($re_not) ) {
+ local @-;
+ $does_match = !/$re_not/o;
+ }
+ if ( $in_range && $does_match ) {
+ $match_colno = $-[0] + 1;
+ if ( !$has_printed_from_this_file ) {
+ if ( $opt_break && $has_printed_from_any_file ) {
+ App::Ack::print_blank_line();
+ }
+ if ( $opt_show_filename && $opt_heading ) {
+ App::Ack::say( $display_filename );
+ }
+ }
+ print_line_with_options( $filename, $_, $., ':' );
+ $has_printed_from_this_file = 1;
+ $nmatches++;
+ $max_count--;
+ }
+ else {
+ if ( $opt_break && !$has_printed_from_this_file && $has_printed_from_any_file ) {
+ App::Ack::print_blank_line();
+ }
+ print_line_with_options( $filename, $_, $., '-', 1 );
+ $has_printed_from_this_file = 1;
+ }
-=item B<--man>
+ $in_range = 0 if ( $using_ranges && $in_range && defined($opt_range_end) && /$opt_range_end/o );
-Print this manual page.
+ last if $max_count == 0;
+ }
-=item B<-n>, B<--no-recurse>
+ return $nmatches;
+}
-No descending into subdirectories.
-=item B<-o>
+sub pmif_opt_v {
+ my $fh = shift;
+ my $filename = shift;
+ my $display_filename = shift;
+ my $max_count = shift;
-Show only the part of each line matching PATTERN (turns off text
-highlighting)
+ my $in_range = range_setup();
+ my $has_printed_from_this_file;
+ my $nmatches = 0;
-=item B<--output=I<expr>>
+ $match_colno = undef;
+ local $_ = undef;
-Output the evaluation of I<expr> for each line (turns off text
-highlighting)
+ while ( <$fh> ) {
+ chomp;
-=item B<--pager=I<program>>
+ $in_range = 1 if ( $using_ranges && !$in_range && defined($opt_range_start) && /$opt_range_start/o );
-Direct ack's output through I<program>. This can also be specified
-via the C<ACK_PAGER> and C<ACK_PAGER_COLOR> environment variables.
+ if ( $in_range ) {
+ my $does_match = /$re_match/o;
+ if ( $does_match && defined($re_not) ) {
+ # local @-; No need to localize this because we don't use @-.
+ $does_match = !/$re_not/o;
+ }
+ if ( !$does_match ) {
+ if ( !$has_printed_from_this_file ) {
+ if ( $opt_break && $has_printed_from_any_file ) {
+ App::Ack::print_blank_line();
+ }
+ if ( $opt_show_filename && $opt_heading ) {
+ App::Ack::say( $display_filename );
+ }
+ }
+ print_line_with_context( $filename, $_, $. );
+ $has_printed_from_this_file = 1;
+ $nmatches++;
+ $max_count--;
+ }
+ }
-Using --pager does not suppress grouping and coloring like piping
-output on the command-line does.
+ $in_range = 0 if ( $using_ranges && $in_range && defined($opt_range_end) && /$opt_range_end/o );
-=item B<--passthru>
+ last if $max_count == 0;
+ }
-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 $nmatches;
+}
- # Watch a log file, and highlight a certain IP address
- $ tail -f ~/access.log | ack --passthru 123.45.67.89
-=item B<--print0>
+sub pmif_normal {
+ my $fh = shift;
+ my $filename = shift;
+ my $display_filename = shift;
+ my $max_count = shift;
-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.
+ my $in_range = range_setup();
+ my $has_printed_from_this_file;
+ my $nmatches = 0;
- # remove all files of type html
- ack -f --html --print0 | xargs -0 rm -f
+ my $last_match_lineno;
+ local $_ = undef;
-=item B<-Q>, B<--literal>
+ while ( <$fh> ) {
+ chomp;
-Quote all metacharacters in PATTERN, it is treated as a literal.
+ $in_range = 1 if ( $using_ranges && !$in_range && defined($opt_range_start) && /$opt_range_start/o );
-This applies only to the PATTERN, not to the regexes given for the B<-g>
-and B<-G> options.
+ if ( $in_range ) {
+ $match_colno = undef;
+ my $is_match = /$re_match/o;
+ if ( $is_match && defined($re_not) ) {
+ local @-;
+ $is_match = !/$re_not/o;
+ }
+ if ( $is_match ) {
+ $match_colno = $-[0] + 1;
+ if ( !$has_printed_from_this_file ) {
+ $stats{filematches}++;
+ if ( $opt_break && $has_printed_from_any_file ) {
+ App::Ack::print_blank_line();
+ }
+ if ( $opt_show_filename && $opt_heading ) {
+ App::Ack::say( $display_filename );
+ }
+ }
+ if ( $opt_p ) {
+ if ( $last_match_lineno ) {
+ if ( $. > $last_match_lineno + $opt_p ) {
+ App::Ack::print_blank_line();
+ }
+ }
+ elsif ( !$opt_break && $has_printed_from_any_file ) {
+ App::Ack::print_blank_line();
+ }
+ }
+ s/[\r\n]+$//;
+ print_line_with_options( $filename, $_, $., ':' );
+ $has_printed_from_this_file = 1;
+ $nmatches++;
+ $stats{linematches}++;
+ $max_count--;
+ $last_match_lineno = $.;
+ }
+ }
-=item B<-r>, B<-R>, B<--recurse>
+ $in_range = 0 if ( $using_ranges && $in_range && defined($opt_range_end) && /$opt_range_end/o );
-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.
+ last if $max_count == 0;
+ }
-=item B<--smart-case>, B<--no-smart-case>
+ return $nmatches;
+}
-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 print_line_with_options {
+ my ( $filename, $line, $lineno, $separator, $skip_coloring ) = @_;
-This applies only to the PATTERN, not to the regexes given for the
-B<-g> and B<-G> options.
+ $has_printed_from_any_file = 1;
+ $printed_lineno = $lineno;
-=item B<--sort-files>
+ my @line_parts;
-Sorts the found files lexically. Use this if you want your file
-listings to be deterministic between runs of I<ack>.
+ if ( $opt_show_filename && defined($filename) ) {
+ my $colno;
+ $colno = get_match_colno() if $opt_column;
+ if ( $opt_color ) {
+ $filename = Term::ANSIColor::colored( $filename, $ENV{ACK_COLOR_FILENAME} );
+ $lineno = Term::ANSIColor::colored( $lineno, $ENV{ACK_COLOR_LINENO} );
+ $colno = Term::ANSIColor::colored( $colno, $ENV{ACK_COLOR_COLNO} ) if $opt_column;
+ }
+ if ( $opt_heading ) {
+ push @line_parts, $lineno;
+ }
+ else {
+ push @line_parts, $filename, $lineno;
+ }
+ push @line_parts, $colno if $opt_column;
+ }
-=item B<--show-types>
+ if ( $opt_output ) {
+ while ( $line =~ /$re_match/og ) {
+ my $output = $opt_output;
+ if ( @special_vars_used_by_opt_output ) {
+ no strict;
-Outputs the filetypes that ack associates with each file.
+ # Stash copies of the special variables because we can't rely
+ # on them not changing in the process of doing the s///.
-Works with B<-f> and B<-g> options.
+ my %keep = map { ($_ => ${$_} // '') } @special_vars_used_by_opt_output;
+ $keep{_} = $line if exists $keep{_}; # Manually set it because $_ gets reset in a map.
+ $keep{f} = $filename if exists $keep{f};
+ my $special_vars_used_by_opt_output = join( '', @special_vars_used_by_opt_output );
+ $output =~ s/\$([$special_vars_used_by_opt_output])/$keep{$1}/ego;
+ }
+ App::Ack::say( join( $separator, @line_parts, $output ) );
+ }
+ }
+ else {
+ my $underline = '';
-=item B<--thpppt>
+ # We have to do underlining before any highlighting because highlighting modifies string length.
+ if ( $opt_underline && !$skip_coloring ) {
+ while ( $line =~ /$re_hilite/og ) {
+ my $match_start = $-[0] // next;
+ my $match_end = $+[0];
+ my $match_length = $match_end - $match_start;
+ last if $match_length <= 0;
-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 $spaces_needed = $match_start - length $underline;
-=item B<--type=TYPE>, B<--type=noTYPE>
+ $underline .= (' ' x $spaces_needed);
+ $underline .= ('^' x $match_length);
+ }
+ }
+ if ( $opt_color && !$skip_coloring ) {
+ my $highlighted = 0; # If highlighted, need to escape afterwards.
-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>.
+ while ( $line =~ /$re_hilite/og ) {
+ my $match_start = $-[0] // next;
+ my $match_end = $+[0];
+ my $match_length = $match_end - $match_start;
+ last if $match_length <= 0;
-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 $substring = substr( $line, $match_start, $match_length );
+ my $substitution = Term::ANSIColor::colored( $substring, $ENV{ACK_COLOR_MATCH} );
-Type specifications can be repeated and are ORed together.
+ # Fourth argument replaces the string specified by the first three.
+ substr( $line, $match_start, $match_length, $substitution );
-See I<ack --help=types> for a list of valid types.
+ # Move the offset of where /g left off forward the number of spaces of highlighting.
+ pos($line) = $match_end + (length( $substitution ) - length( $substring ));
+ $highlighted = 1;
+ }
+ # Reset formatting and delete everything to the end of the line.
+ $line .= "\e[0m\e[K" if $highlighted;
+ }
-=item B<--type-add I<TYPE>=I<.EXTENSION>[,I<.EXT2>[,...]]>
+ push @line_parts, $line;
+ App::Ack::say( join( $separator, @line_parts ) );
+
+ # Print the underline, if appropriate.
+ if ( $underline ne '' ) {
+ # Figure out how many spaces are used per line for the ANSI coloring.
+ state $chars_used_by_coloring;
+ if ( !defined($chars_used_by_coloring) ) {
+ $chars_used_by_coloring = 0;
+ if ( $opt_color ) {
+ my $len_fn = sub { length( Term::ANSIColor::colored( 'x', $ENV{$_[0]} ) ) - 1 };
+ $chars_used_by_coloring += $len_fn->('ACK_COLOR_FILENAME') unless $opt_heading;
+ $chars_used_by_coloring += $len_fn->('ACK_COLOR_LINENO');
+ $chars_used_by_coloring += $len_fn->('ACK_COLOR_COLNO') if $opt_column;
+ }
+ }
-Files with the given EXTENSION(s) are recognized as being of (the
-existing) type TYPE. See also L</"Defining your own types">.
+ pop @line_parts; # Leave only the stuff on the left.
+ if ( @line_parts ) {
+ my $stuff_on_the_left = join( $separator, @line_parts );
+ my $spaces_needed = length($stuff_on_the_left) - $chars_used_by_coloring + 1;
+ App::Ack::print( ' ' x $spaces_needed );
+ }
+ App::Ack::say( $underline );
+ }
+ }
-=item B<--type-set I<TYPE>=I<.EXTENSION>[,I<.EXT2>[,...]]>
+ return;
+}
-Files with the given EXTENSION(s) are recognized as being of type
-TYPE. This replaces an existing definition for type TYPE. See also
-L</"Defining your own types">.
+sub print_line_with_context {
+ my ( $filename, $matching_line, $lineno ) = @_;
-=item B<-u>, B<--unrestricted>
+ $matching_line =~ s/[\r\n]+$//;
-All files and directories (including blib/, core.*, ...) are searched,
-nothing is skipped. When both B<-u> and B<--ignore-dir> are used, the
-B<--ignore-dir> option has no effect.
+ # Check if we need to print context lines first.
+ if ( $opt_A || $opt_B ) {
+ my $before_unprinted = $lineno - $printed_lineno - 1;
+ if ( !$is_first_match && ( !$printed_lineno || $before_unprinted > $n_before_ctx_lines ) ) {
+ App::Ack::say( '--' );
+ }
-=item B<-v>, B<--invert-match>
+ # We want at most $n_before_ctx_lines of context.
+ if ( $before_unprinted > $n_before_ctx_lines ) {
+ $before_unprinted = $n_before_ctx_lines;
+ }
-Invert match: select non-matching lines
+ while ( $before_unprinted > 0 ) {
+ my $line = $before_context_buf[($before_context_pos - $before_unprinted + $n_before_ctx_lines) % $n_before_ctx_lines];
-This applies only to the PATTERN, not to the regexes given for the B<-g>
-and B<-G> options.
+ chomp $line;
-=item B<--version>
+ # Disable $opt->{column} since there are no matches in the context lines.
+ local $opt_column = 0;
-Display version and copyright information.
+ print_line_with_options( $filename, $line, $lineno-$before_unprinted, '-' );
+ $before_unprinted--;
+ }
+ }
-=item B<-w>, B<--word-regexp>
+ print_line_with_options( $filename, $matching_line, $lineno, ':' );
-Force PATTERN to match only whole words. The PATTERN is wrapped with
-C<\b> metacharacters.
+ # We want to get the next $n_after_ctx_lines printed.
+ $after_context_pending = $n_after_ctx_lines;
-This applies only to the PATTERN, not to the regexes given for the B<-g>
-and B<-G> options.
+ $is_first_match = 0;
-=item B<-1>
+ return;
+}
-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.
+}
-=back
+sub get_match_colno {
+ return $match_colno;
+}
-=head1 THE .ackrc FILE
+sub count_matches_in_file {
+ my $file = shift;
+ my $bail = shift; # True if we're just checking for existence.
-The F<.ackrc> file contains command-line options that are prepended
-to the command line before processing. Multiple options may live
-on multiple lines. Lines beginning with a # are ignored. A F<.ackrc>
-might look like this:
+ my $nmatches = 0;
+ my $do_scan = 1;
- # Always sort the files
- --sort-files
+ if ( !$file->open() ) {
+ $do_scan = 0;
+ if ( $App::Ack::report_bad_filenames ) {
+ App::Ack::warn( $file->name . ": $!" );
+ }
+ }
+ else {
+ if ( !$opt_v ) {
+ if ( !$file->may_be_present( $re_scan ) ) {
+ $do_scan = 0;
+ }
+ }
+ }
- # Always color, even if piping to a another program
- --color
+ if ( $do_scan ) {
+ $file->reset();
- # Use "less -r" as my pager
- --pager=less -r
+ my $in_range = range_setup();
-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>.
+ my $fh = $file->{fh};
+ if ( $using_ranges ) {
+ while ( <$fh> ) {
+ chomp;
+ $in_range = 1 if ( !$in_range && defined($opt_range_start) && /$opt_range_start/o );
+ if ( $in_range ) {
+ my $is_match = /$re_match/o;
+ if ( $is_match && defined($re_not) ) {
+ $is_match = !/$re_not/o;
+ }
+ if ( $is_match xor $opt_v ) {
+ ++$nmatches;
+ last if $bail;
+ }
+ }
+ $in_range = 0 if ( $in_range && defined($opt_range_end) && /$opt_range_end/o );
+ }
+ }
+ else {
+ while ( <$fh> ) {
+ chomp;
+ my $is_match = /$re_match/o;
+ if ( $is_match && defined($re_not) ) {
+ $is_match = !/$re_not/o;
+ }
+ if ( $is_match xor $opt_v ) {
+ ++$nmatches;
+ last if $bail;
+ }
+ }
+ }
+ }
+ $file->close;
-F<ack> looks in your home directory for the F<.ackrc>. You can
-specify another location with the F<ACKRC> variable, below.
+ return $nmatches;
+}
-If B<--noenv> is specified on the command line, the F<.ackrc> file
-is ignored.
-=head1 Defining your own types
+sub range_setup {
+ return !$using_ranges || (!$opt_range_start && $opt_range_end);
+}
-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.
-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=.xs --perl foo>
-does this for you. B<--type-add> appends
-additional extensions to an existing type.
+=pod
-If you want to define a new type, or completely redefine an existing
-type, then use B<--type-set>. I<ack --type-set
-eiffel=.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=.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=.c,.h>
-and I<.xs> files no longer belong to the type I<cc>.
+=encoding UTF-8
-When defining your own types in the F<.ackrc> file you have to use
-the following:
+=head1 NAME
- --type-set=eiffel=.e,.eiffel
+ack - grep-like text finder
-or writing on separate lines
+=head1 SYNOPSIS
- --type-set
- eiffel=.e,.eiffel
+ ack [options] PATTERN [FILE...]
+ ack -f [options] [DIRECTORY...]
-The following does B<NOT> work in the F<.ackrc> file:
+=head1 DESCRIPTION
- --type-set eiffel=.e,.eiffel
+ack is designed as an alternative to F<grep> for programmers.
+ack searches the named input FILEs or DIRECTORYs for lines containing a
+match to the given PATTERN. By default, ack prints the matching lines.
+If no FILE or DIRECTORY is given, the current directory will be searched.
-In order to see all currently defined types, use I<--help types>, e.g.
-I<ack --type-set backup=.bak --type-add perl=.perl --help types>
+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<perlreref|https://perldoc.perl.org/perlreref.html>. If you don't know
+how to use regular expression but are interested in learning, you may
+consult L<perlretut|https://perldoc.perl.org/perlretut.html>. If you do not
+need or want ack to use regular expressions, please see the
+C<-Q>/C<--literal> option.
-Restrictions:
+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.
-=over 4
+=head1 FILE SELECTION
-=item
+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.
-The types 'skipped', 'make', 'binary' and 'text' are considered "builtin" and
-cannot be altered.
+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.
-=item
+With no file selection, I<ack> searches through regular files that
+are not explicitly excluded by B<--ignore-dir> and B<--ignore-file>
+options, either present in F<ackrc> files or on the command line.
-The shebang line recognition of the types 'perl', 'ruby', 'php', 'python',
-'shell' and 'xml' cannot be redefined by I<--type-set>, it is always
-active. However, the shebang line is only examined for files where the
-extension is not recognised. Therefore it is possible to say
-I<ack --type-set perl=.perl --type-set foo=.pl,.pm,.pod,.t --perl --nofoo> and
-only find your shiny new I<.perl> files (and all files with unrecognized extension
-and perl on the shebang line).
+The default options for I<ack> ignore certain files and directories. These
+include:
-=back
+=over 4
-=head1 ENVIRONMENT VARIABLES
+=item * Backup files: Files matching F<#*#> or ending with F<~>.
-For commonly-used ack options, environment variables can make life much easier.
-These variables are ignored if B<--noenv> is specified on the command line.
+=item * Coredumps: Files matching F<core.\d+>
-=over 4
+=item * Version control directories like F<.svn> and F<.git>.
-=item ACKRC
+=back
-Specifies the location of the F<.ackrc> file. If this file doesn't
-exist, F<ack> looks in the default location.
+Run I<ack> with the C<--dump> option to see what settings are set.
-=item ACK_OPTIONS
+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.
-This variable specifies default options to be placed in front of
-any explicit options on the command line.
+=head1 DIRECTORY SELECTION
-=item ACK_COLOR_FILENAME
+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.
-Specifies the color of the filename when it's printed in B<--group>
-mode. By default, it's "bold green".
+For a complete list of directories that do not get searched, run
+C<ack --dump>.
-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.
+=head1 MATCHING IN A RANGE OF LINES
-This option can also be set with B<--color-filename>.
+The C<--range-start> and C<--range-end> options let you specify ranges of
+lines to search within each file.
-=item ACK_COLOR_MATCH
+Say you had the following file, called F<testfile>:
-Specifies the color of the matching text when printed in B<--color>
-mode. By default, it's "black on_yellow".
+ # This function calls print on "foo".
+ sub foo {
+ print 'foo';
+ }
+ my $print = 1;
+ sub bar {
+ print 'bar';
+ }
+ my $task = 'print';
-This option can also be set with B<--color-match>.
+Calling C<ack print> will give us five matches:
-See B<ACK_COLOR_FILENAME> for the color specifications.
+ $ ack print testfile
+ # This function calls print on "foo".
+ print 'foo';
+ my $print = 1;
+ print 'bar';
+ my $task = 'print';
-=item ACK_COLOR_LINENO
+What if we only want to search for C<print> within the subroutines? We can
+specify ranges of lines that we want ack to search. The range starts with
+any line that matches the pattern C<^sub \w+>, and stops with any line that
+matches C<^}>.
-Specifies the color of the line number when printed in B<--color>
-mode. By default, it's "bold yellow".
+ $ ack --range-start='^sub \w+' --range-end='^}' print testfile
+ print 'foo';
+ print 'bar';
-This option can also be set with B<--color-lineno>.
+Note that ack searched two ranges of lines. The listing below shows which
+lines were in a range and which were out of the range.
-See B<ACK_COLOR_FILENAME> for the color specifications.
+ Out # This function calls print on "foo".
+ In sub foo {
+ In print 'foo';
+ In }
+ Out my $print = 1;
+ In sub bar {
+ In print 'bar';
+ In }
+ Out my $task = 'print';
-=item ACK_PAGER
+You don't have to specify both C<--range-start> and C<--range-end>. IF
+C<--range-start> is omitted, then the range runs from the first line in the
+file until the first line that matches C<--range-end>. Similarly, if
+C<--range-end> is omitted, the range runs from the first line matching
+C<--range-start> to the end of the file.
-Specifies a pager program, such as C<more>, C<less> or C<most>, to which
-ack will send its output.
+For example, if you wanted to search all HTML files up until the first
+instance of the C<< <body> >>, you could do
-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.
+ ack foo --html --range-end='<body>'
-C<ACK_PAGER_COLOR> overrides C<ACK_PAGER> if both are specified.
+Or to search after Perl's `__DATA__` or `__END__` markers, you would do
-=item ACK_PAGER_COLOR
+ ack pattern --perl --range-start='^__(END|DATA)__'
-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.
+It's possible for a range to start and stop on the same line. For example
-If you are not on Windows, you never need to use C<ACK_PAGER_COLOR>.
+ --range-start='<title>' --range-end='</title>'
-=back
+would match this line as both the start and end of the range, making a
+one-line range.
-=head1 ACK & OTHER TOOLS
+ <title>Page title</title>
-=head2 Vim integration
+Note that the patterns in C<--range-start> and C<--range-end> are not
+affected by options like C<-i>, C<-w> and C<-Q> that modify the behavior of
+the main pattern being matched.
-F<ack> integrates easily with the Vim text editor. Set this in your
-F<.vimrc> to use F<ack> instead of F<grep>:
+Again, ranges only affect where matches are looked for. Everything else in
+ack works the same way. Using C<-c> option with a range will give a count
+of all the matches that appear within those ranges. The C<-l> shows those
+files that have a match within a range, and the C<-L> option shows files
+that do not have a match within a range.
- set grepprg=ack\ -a
+The C<-v> option for negating a match works inside the range, too.
+To see lines that don't match "google" within the "<head>" section of
+your HTML files, you could do:
-That examples uses C<-a> to search through all files, but you may
-use other default flags. Now you can search with F<ack> and easily
-step through the results in Vim:
+ ack google -v --html --range-start='<head' --range-end='</head>'
- :grep Dumper perllib
+Specifying a range to search does not affect how matches are displayed.
+The context for a match will still be the same, and
-=head2 Emacs integration
+Using the context options work the same way, and will show context
+lines for matches even if the context lines fall outside the range.
+Similarly, C<--passthru> will show all lines in the file, but only show
+matches for lines within the range.
-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."
+=head1 OPTIONS
-L<http://www.shellarchive.co.uk/content/emacs.html>
+=over 4
-=head2 TextMate integration
+=item B<--ackrc>
-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>"
+Specifies an ackrc file to load after all others; see L</"ACKRC LOCATION SEMANTICS">.
-=head2 Shell and Return Code
+=item B<--and=PATTERN>
-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.
+Specifies a I<PATTERN> that MUST ALSO be found on a given line for a match to
+occur. This option can be repeated.
-(Shell exit code 1 is C<$?=256> in perl with C<system> or backticks.)
+If you want to find all the lines with both "dogs" or "cats", use:
-The I<grep> code 2 for errors is not used.
+ ack dogs --and cats
-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.
+Note that the options that affect "dogs" also affect "cats", so if you have
-=cut
+ ack -i -w dogs --and cats
-=head1 DEBUGGING ACK PROBLEMS
+then the search for both "dogs" and "cats" will be case-insensitive and be
+word-limited.
-If ack gives you output you're not expecting, start with a few simple steps.
+See also the other two boolean options C<--or> and C<--not>, neither of
+which can be used with C<--and>.
-=head2 Use B<--noenv>
+=item B<-A I<NUM>>, B<--after-context=I<NUM>>
-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>.
+Print I<NUM> lines of trailing context after matching lines.
-=head2 Use B<-f> to see what files you're scanning
+=item B<-B I<NUM>>, B<--before-context=I<NUM>>
-The reason I created B<-f> in the first place was as a debugging
-tool. If ack is not finding matches you think it should find, run
-F<ack -f> to see what files are being checked.
+Print I<NUM> lines of leading context before matching lines.
-=head1 TIPS
+=item B<--[no]break>
-=head2 Use the F<.ackrc> file.
+Print a break between results from different files. On by default
+when used interactively.
-The F<.ackrc> is the place to put all your options you use most of
-the time but don't want to remember. Put all your --type-add and
---type-set definitions in it. If you like --smart-case, set it
-there, too. I also set --sort-files there.
+=item B<-C [I<NUM>]>, B<--context[=I<NUM>]>
-=head2 Use F<-f> for working with big codesets
+Print I<NUM> lines (default 2) of context around matching lines.
+You can specify zero lines of context to override another context
+specified in an ackrc.
-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:
+=item B<-c>, B<--count>
- # Change all "this" to "that" in all Perl files in a tree.
- ack -f --perl | xargs perl -p -i -e's/this/that/g'
+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.
-or if you prefer:
+If combined with B<-h> (B<--no-filename>) ack outputs only one total
+count.
- perl -p -i -e's/this/thatg/' $(ack -f --perl)
+=item B<--[no]color>, B<--[no]colour>
-=head2 Use F<-Q> when in doubt about metacharacters
+B<--color> highlights the matching text. B<--nocolor> suppresses
+the color. This is on by default unless the output is redirected.
-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...
+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.
-=head2 Use ack to watch log files
+=item B<--color-filename=I<color>>
-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.
+Sets the color to be used for filenames.
- ack -Q aa.bb.cc.dd /path/to/access.log | ack -Q -B5 troublesome.gif
+=item B<--color-match=I<color>>
-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.
+Sets the color to be used for matches.
-=head2 Share your knowledge
+=item B<--color-colno=I<color>>
-Join the ack-users mailing list. Send me your tips and I may add
-them here.
+Sets the color to be used for column numbers.
-=head1 FAQ
+=item B<--color-lineno=I<color>>
-=head2 Why isn't ack finding a match in (some file)?
+Sets the color to be used for line numbers.
-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.>
+=item B<--[no]column>
-Use the C<-f> switch to see a list of files that ack will search
-for you.
+Show the column number of the first match. This is helpful for
+editors that can place your cursor at a given position.
-If you want ack to search files that it doesn't recognize, use the
-C<-a> switch.
+=item B<--create-ackrc>
-If you want ack to search every file, even ones that it always
-ignores like coredumps and backup files, use the C<-u> switch.
+Dumps the default ack options to standard output. This is useful for
+when you want to customize the defaults.
-=head2 Why does ack ignore unknown files by default?
+=item B<--dump>
-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.
+Writes the list of options loaded and where they came from to standard
+output. Handy for debugging.
-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.
+=item B<--[no]env>
-=head2 Wouldn't it be great if F<ack> did search & replace?
+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.
-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.
+=item B<--flush>
-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:
+B<--flush> flushes output immediately. This is off by default
+unless ack is running interactively (when output goes to a pipe or
+file).
- $ perl -i -p -e's/foo/bar/g' $(ack -f --php)
+=item B<-f>
-=head2 Can you make ack recognize F<.xyz> files?
+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.
-That's an enhancement. Please see the section in the manual about
-enhancements.
+=item B<--files-from=I<FILE>>
-=head2 There's already a program/package called ack.
+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.
-Yes, I know.
+Note that the list of files is B<not> filtered in any way. If you
+add C<--type=html> in addition to C<--files-from>, the C<--type> will
+be ignored.
-=head2 Why is it called ack if it's called ack-grep?
-The name of the program is "ack". Some packagers have called it
-"ack-grep" when creating packages because there's already a package
-out there called "ack" that has nothing to do with this ack.
+=item B<--[no]filter>
-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.
+Forces ack to act as if it were receiving input via a pipe.
-To do that, run this with F<sudo> or as root:
+=item B<--[no]follow>
- ln -s /usr/bin/ack-grep /usr/bin/ack
+Follow or don't follow symlinks, other than whatever starting files
+or directories were specified on the command line.
+
+This is off by default.
+
+=item B<-g I<PATTERN>>
+
+Print searchable files where the relative path + filename matches
+I<PATTERN>.
+
+Note that
+
+ ack -g foo
+
+is exactly the same as
+
+ ack -f | ack foo
+
+This means that just as ack will not search, for example, F<.jpg>
+files, C<-g> will not list F<.jpg> files either. ack is not intended
+to be a general-purpose file finder.
+
+Note also that if you have C<-i> in your .ackrc that the filenames
+to be matched will be case-insensitive as well.
+
+This option can be combined with B<--color> to make it easier to
+spot the match.
+
+=item B<--[no]group>
+
+B<--group> groups matches by file name. This is the default
+when used interactively.
+
+B<--nogroup> prints one result per line, like grep. This is the
+default when output is redirected.
+
+=item B<-H>, B<--with-filename>
+
+Print the filename for each match. This is the default unless searching
+a single explicitly specified file.
+
+=item B<-h>, B<--no-filename>
+
+Suppress the prefixing of filenames on output when multiple files are
+searched.
+
+=item B<--[no]heading>
+
+Print a filename heading above each file's results. This is the default
+when used interactively.
+
+=item B<--help>
+
+Print a short help statement.
+
+=item B<--help-types>
+
+Print all known types.
+
+=item B<--help-colors>
+
+Print a chart of various color combinations.
+
+=item B<--help-rgb-colors>
+
+Like B<--help-colors> but with more precise RGB colors.
+
+=item B<-i>, B<--ignore-case>
+
+Ignore case distinctions in PATTERN. Overrides B<--smart-case> and B<-I>.
+
+=item B<-I>, B<--no-ignore-case>
+
+Turns on case distinctions in PATTERN. Overrides B<--smart-case> and B<-i>.
+
+=item B<--ignore-ack-defaults>
+
+Tells ack to completely ignore the default definitions provided with ack.
+This is useful in combination with B<--create-ackrc> if you I<really> want
+to customize ack.
+
+=item B<--[no]ignore-dir=I<DIRNAME>>, B<--[no]ignore-directory=I<DIRNAME>>
+
+Ignore directory (as CVS, .svn, etc are ignored). May be used
+multiple times to ignore multiple directories. For example, mason
+users may wish to include B<--ignore-dir=data>. The B<--noignore-dir>
+option allows users to search directories which would normally be
+ignored (perhaps to research the contents of F<.svn/props> directories).
+
+The I<DIRNAME> must always be a simple directory name. Nested
+directories like F<foo/bar> are NOT supported. You would need to
+specify B<--ignore-dir=foo> and then no files from any foo directory
+are taken into account by ack unless given explicitly on the command
+line.
+
+=item B<--ignore-file=I<FILTER:ARGS>>
+
+Ignore files matching I<FILTER:ARGS>. The filters are specified
+identically to file type filters as seen in L</"Defining your own types">.
+
+=item B<-k>, B<--known-types>
+
+Limit selected files to those with types that ack knows about.
+
+=item B<-l>, B<--files-with-matches>
+
+Only print the filenames of matching files, instead of the matching text.
+
+=item B<-L>, B<--files-without-matches>
+
+Only print the filenames of files that do I<NOT> match.
+
+=item B<--match I<PATTERN>>
+
+Specify the I<PATTERN> explicitly. This is helpful if you don't want to put the
+regex as your first argument, e.g. when executing multiple searches over the
+same set of files.
+
+ # search for foo and bar in given files
+ ack file1 t/file* --match foo
+ ack file1 t/file* --match bar
+
+=item B<-m=I<NUM>>, B<--max-count=I<NUM>>
+
+Print only I<NUM> matches out of each file. If you want to stop ack
+after printing the first match of any kind, use the B<-1> options.
+
+=item B<--man>
+
+Print this manual page.
+
+=item B<-n>, B<--no-recurse>
+
+No descending into subdirectories.
+
+=item B<--not=PATTERN>
+
+Specifies a I<PATTERN> that must NOT be true on a given line for a match to
+occur. This option can be repeated.
+
+If you want to find all the lines with "dogs" but not if "cats" or "fish"
+appear on the line, use:
+
+ ack dogs --not cats --not fish
+
+Note that the options that affect "dogs" also affect "cats" and "fish", so
+if you have
+
+ ack -i -w dogs --not cats
+
+then the search for both "dogs" and "cats" will be case-insensitive and be
+word-limited.
+
+See also the other two boolean options C<--and> and C<--or>, neither of
+which can be used with C<--not>.
+
+=item B<-o>
+
+Show only the part of each line matching PATTERN (turns off text
+highlighting). This is exactly the same as C<--output=$&>.
+
+=item B<--or=PATTERN>
+
+Specifies a I<PATTERN> that MAY be found on a given line for a match to
+occur. This option can be repeated.
+
+If you want to find all the lines with "dogs" or "cats", use:
+
+ ack dogs --or cats
+
+Note that the options that affect "dogs" also affect "cats", so if you have
+
+ ack -i -w dogs --or cats
+
+then the search for both "dogs" and "cats" will be case-insensitive and be
+word-limited.
+
+See also the other two boolean options C<--and> and C<--not>, neither of
+which can be used with C<--or>.
+
+=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.
+
+I<expr> may contain the strings "\n", "\r" and "\t", which will be
+expanded to their corresponding characters line feed, carriage return
+and tab, respectively.
+
+I<expr> may also contain the following Perl special variables:
+
+=over 4
+
+=item C<$1> through C<$9>
+
+The subpattern from the corresponding set of capturing parentheses.
+If your pattern is C<(.+) and (.+)>, and the string is "this and
+that', then C<$1> is "this" and C<$2> is "that".
+
+=item C<$_>
+
+The contents of the line in the file.
+
+=item C<$.>
+
+The number of the line in the file.
+
+=item C<$&>, C<$`> and C<$'>
+
+C<$&> is the string matched by the pattern, C<$`> is what
+precedes the match, and C<$'> is what follows it. If the pattern
+is C<gra(ph|nd)> and the string is "lexicographic", then C<$&> is
+"graph", C<$`> is "lexico" and C<$'> is "ic".
+
+Use of these variables in your output will slow down the pattern
+matching.
+
+=item C<$+>
+
+The match made by the last parentheses that matched in the pattern.
+For example, if your pattern is C<Version: (.+)|Revision: (.+)>,
+then C<$+> will contain whichever set of parentheses matched.
+
+=item C<$f>
+
+C<$f> is available, in C<--output> only, to insert the filename.
+This is a stand-in for the discovered C<$filename> usage in old C<< ack2 --output >>,
+which is disallowed with C<ack3> improved security.
+
+The intended usage is to provide the grep or compile-error syntax needed for editor/IDE go-to-line integration,
+e.g. C<--output=$f:$.:$_> or C<--output=$f\t$.\t$&>
+
+=back
+
+=item B<--pager=I<program>>, B<--nopager>
+
+B<--pager> directs ack's output through I<program>. This can also be specified
+via the C<ACK_PAGER> and C<ACK_PAGER_COLOR> environment variables.
+
+Using --pager does not suppress grouping and coloring like piping
+output on the command-line does.
+
+B<--nopager> cancels any setting in F<~/.ackrc>, C<ACK_PAGER> or C<ACK_PAGER_COLOR>.
+No output will be sent through a pager.
+
+=item B<--passthru>
+
+Prints all lines, whether or not they match the expression. Highlighting
+will still work, though, so it can be used to highlight matches while
+still seeing the entire file, as in:
+
+ # Watch a log file, and highlight a certain IP address.
+ $ tail -f ~/access.log | ack --passthru 123.45.67.89
+
+=item B<--print0>
+
+Only works in conjunction with B<-f>, B<-g>, B<-l> or B<-c>, options
+that only list filenames. The filenames are output separated with a
+null byte instead of the usual newline. This is helpful when dealing
+with filenames that contain whitespace, e.g.
+
+ # Remove all files of type HTML.
+ ack -f --html --print0 | xargs -0 rm -f
+
+=item B<-p[N]>, B<--proximate[=N]>
+
+Groups together match lines that are within N lines of each other.
+This is useful for visually picking out matches that appear close
+to other matches.
+
+For example, if you got these results without the C<--proximate> option,
+
+ 15: First match
+ 18: Second match
+ 19: Third match
+ 37: Fourth match
+
+they would look like this with C<--proximate=1>
+
+ 15: First match
+
+ 18: Second match
+ 19: Third match
+
+ 37: Fourth match
+
+and this with C<--proximate=3>.
+
+ 15: First match
+ 18: Second match
+ 19: Third match
+
+ 37: Fourth match
+
+If N is omitted, N is set to 1.
+
+=item B<-P>
+
+Negates the effect of the B<--proximate> option. Shortcut for B<--proximate=0>.
+
+=item B<-Q>, B<--literal>
+
+Quote all metacharacters in PATTERN, it is treated as a literal.
+
+=item B<-r>, B<-R>, B<--recurse>
+
+Recurse into sub-directories. This is the default and just here for
+compatibility with grep. You can also use it for turning B<--no-recurse> off.
+
+=item B<--range-start=PATTERN>, B<--range-end=PATTERN>
+
+Specifies patterns that mark the start and end of a range. See
+L<MATCHING IN A RANGE OF LINES> for details.
+
+=item B<-s>
+
+Suppress error messages about nonexistent or unreadable files. This is taken
+from fgrep.
+
+=item B<-S>, B<--[no]smart-case>, B<--no-smart-case>
+
+Ignore case in the search strings if PATTERN contains no uppercase
+characters. This is similar to C<smartcase> in the vim text editor.
+The options overrides B<-i> and B<-I>.
+
+B<-S> is a synonym for B<--smart-case>.
+
+B<-i> always overrides this option.
+
+=item B<--sort-files>
+
+Sorts the found files lexicographically. Use this if you want your file
+listings to be deterministic between runs of I<ack>.
+
+=item B<--show-types>
+
+Outputs the filetypes that ack associates with each file.
+
+Works with B<-f> and B<-g> options.
+
+=item B<-t TYPE>, B<--type=TYPE>, B<--TYPE>
+
+Specify the types of files to include in the search.
+TYPE is a filetype, like I<perl> or I<xml>. B<--type=perl> can
+also be specified as B<--perl>, although this is deprecated.
+
+Type inclusions can be repeated and are ORed together.
+
+See I<ack --help-types> for a list of valid types.
+
+=item B<-T TYPE>, B<--type=noTYPE>, B<--noTYPE>
+
+Specifies the type of files to exclude from the search. B<--type=noperl>
+can be done as B<--noperl>, although this is deprecated.
+
+If a file is of both type "foo" and "bar", specifying both B<--type=foo>
+and B<--type=nobar> will exclude the file, because an exclusion takes
+precedence over an inclusion.
+
+=item B<--type-add I<TYPE>:I<FILTER>:I<ARGS>>
+
+Files with the given ARGS applied to the given FILTER
+are recognized as being of (the existing) type TYPE.
+See also L</"Defining your own types">.
+
+=item B<--type-set I<TYPE>:I<FILTER>:I<ARGS>>
+
+Files with the given ARGS applied to the given FILTER are recognized as
+being of type TYPE. This replaces an existing definition for type TYPE. See
+also L</"Defining your own types">.
+
+=item B<--type-del I<TYPE>>
+
+The filters associated with TYPE are removed from Ack, and are no longer considered
+for searches.
+
+=item B<--[no]underline>
+
+Turns on underlining of matches, where "underlining" is printing a line of
+carets under the match.
+
+ $ ack -u foo
+ peanuts.txt
+ 17: Come kick the football you fool
+ ^^^ ^^^
+ 623: Price per square foot
+ ^^^
+
+This is useful if you're dumping the results of an ack run into a text
+file or printer that doesn't support ANSI color codes.
+
+The setting of underline does not affect highlighting of matches.
+
+=item B<-v>, B<--invert-match>
+
+Invert match: select non-matching lines.
+
+=item B<--version>
+
+Display version and copyright information.
+
+=item B<-w>, B<--word-regexp>
+
+Force PATTERN to match only whole words.
+
+=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.
+
+Note that the list of files is B<not> filtered in any way. If you add
+C<--type=html> in addition to C<-x>, the C<--type> will be ignored.
+
+=item B<-1>
+
+Stops after reporting first match of any kind. This is different
+from B<--max-count=1> or B<-m1>, where only one match per file is
+shown. Also, B<-1> works with B<-f> and B<-g>, where B<-m> does
+not.
+
+=item B<--thpppt>
+
+Display the all-important Bill The Cat logo. Note that the exact
+spelling of B<--thpppppt> is not important. It's checked against
+a regular expression.
+
+=item B<--bar>
+
+Check with the admiral for traps.
+
+=item B<--cathy>
+
+Chocolate, Chocolate, Chocolate!
+
+=back
+
+=head1 THE .ackrc FILE
+
+The F<.ackrc> file contains command-line options that are prepended
+to the command line before processing. Multiple options may live
+on multiple lines. Lines beginning with a # are ignored. A F<.ackrc>
+might look like this:
+
+ # Always sort the files
+ --sort-files
+
+ # Always color, even if piping to another program
+ --color
+
+ # Use "less -r" as my pager
+ --pager=less -r
+
+Note that arguments with spaces in them do not need to be quoted,
+as they are not interpreted by the shell. Basically, each I<line>
+in the F<.ackrc> file is interpreted as one element of C<@ARGV>.
+
+F<ack> looks in several locations for F<.ackrc> files; the searching
+process is detailed in L</"ACKRC LOCATION SEMANTICS">. These
+files are not considered if B<--noenv> is specified on the command line.
+
+=head1 Defining your own types
+
+ack allows you to define your own types in addition to the predefined
+types. This is done with command line options that are best put into
+an F<.ackrc> file - then you do not have to define your types over and
+over again. In the following examples the options will always be shown
+on one command line so that they can be easily copy & pasted.
+
+File types can be specified both with the I<--type=xxx> option,
+or the file type as an option itself. For example, if you create
+a filetype of "cobol", you can specify I<--type=cobol> or simply
+I<--cobol>. File types must be at least two characters long. This
+is why the C language is I<--cc> and the R language is I<--rr>.
+
+I<ack --perl foo> searches for foo in all perl files. I<ack --help-types>
+tells you, that perl files are files ending
+in .pl, .pm, .pod or .t. So what if you would like to include .xs
+files as well when searching for --perl files? I<ack --type-add perl:ext:xs --perl foo>
+does this for you. B<--type-add> appends
+additional extensions to an existing type.
+
+If you want to define a new type, or completely redefine an existing
+type, then use B<--type-set>. I<ack --type-set eiffel:ext:e,eiffel> defines
+the type I<eiffel> to include files with
+the extensions .e or .eiffel. So to search for all eiffel files
+containing the word Bertrand use I<ack --type-set eiffel:ext:e,eiffel --eiffel Bertrand>.
+As usual, you can also write B<--type=eiffel>
+instead of B<--eiffel>. Negation also works, so B<--noeiffel> excludes
+all eiffel files from a search. Redefining also works: I<ack --type-set cc:ext:c,h>
+and I<.xs> files no longer belong to the type I<cc>.
+
+When defining your own types in the F<.ackrc> file you have to use
+the following:
+
+ --type-set=eiffel:ext:e,eiffel
+
+or writing on separate lines
+
+ --type-set
+ eiffel:ext:e,eiffel
+
+The following does B<NOT> work in the F<.ackrc> file:
+
+ --type-set eiffel:ext:e,eiffel
+
+In order to see all currently defined types, use I<--help-types>, e.g.
+I<ack --type-set backup:ext:bak --type-add perl:ext:perl --help-types>
+
+In addition to filtering based on extension, ack offers additional
+filter types. The generic syntax is
+I<--type-set TYPE:FILTER:ARGS>; I<ARGS> depends on the value
+of I<FILTER>.
+
+=over 4
+
+=item is:I<FILENAME>
+
+I<is> filters match the target filename exactly. It takes exactly one
+argument, which is the name of the file to match.
+
+Example:
+
+ --type-set make:is:Makefile
+
+=item ext:I<EXTENSION>[,I<EXTENSION2>[,...]]
+
+I<ext> filters match the extension of the target file against a list
+of extensions. No leading dot is needed for the extensions.
+
+Example:
+
+ --type-set perl:ext:pl,pm,t
+
+=item match:I<PATTERN>
+
+I<match> filters match the target filename against a regular expression.
+The regular expression is made case-insensitive for the search.
+
+Example:
+
+ --type-set make:match:/(gnu)?makefile/
+
+=item firstlinematch:I<PATTERN>
+
+I<firstlinematch> matches the first line of the target file against a
+regular expression. Like I<match>, the regular expression is made
+case insensitive.
+
+Example:
+
+ --type-add perl:firstlinematch:/perl/
+
+=back
+
+=head1 ACK COLORS
+
+ack allows customization of the colors it uses when presenting matches
+onscreen. It uses the colors available in Perl's L<Term::ANSIColor>
+module, which provides the following listed values. Note that case does not
+matter when using these values.
+
+There are four different colors ack uses:
+
+ Aspect Option Env. variable Default
+ -------- ----------------- ------------------ ---------------
+ filename --color-filename ACK_COLOR_FILENAME black on_yellow
+ match --color-match ACK_COLOR_MATCH bold green
+ line no. --color-lineno ACK_COLOR_LINENO bold yellow
+ column no. --color-colno ACK_COLOR_COLNO bold yellow
+
+The column number column is only used if the column number is shown because
+of the --column option.
+
+Colors may be specified by command-line option, such as
+C<ack --color-filename='red on_white'>, or by setting an environment
+variable, such as C<ACK_COLOR_FILENAME='red on_white'>. Options for colors
+can be set in your ACKRC file (See "THE .ackrc FILE").
+
+ack can understand the following colors for the foreground:
+
+ black red green yellow blue magenta cyan white
+
+The optional background color is specified by prepending "on_" to one of
+the foreground colors:
+
+ on_black on_red on_green on_yellow on_blue on_magenta on_cyan on_white
+
+Each of the foreground colors can be modified with the following
+attributes, which may or may not be supported by your terminal:
+
+ bold faint italic underline blink reverse concealed
+
+Any combinations of modifiers can be added to the foreground color. If your
+terminal supports it, and you enjoy visual punishment, you can specify:
+
+ ack --color-filename="blink italic underline bold red on_yellow"
+
+For charts of the colors and what they look like, run C<ack --help-colors>
+and C<ack --help-rgb-colors>.
+
+If the eight standard colors, in their bold, faint and unmodified states,
+aren't enough for you to choose from, you can also specify colors by their
+RGB values. They are specified as "rgbXYZ" where X, Y, and Z are values
+between 0 and 5 giving the intensity of red, green and blue, respectively.
+Therefore, "rgb500" is pure red, "rgb505" is purple, and so on.
+
+Background colors can be specified with the "on_" prefix prepended on an
+RGB color, so that "on_rgb505" would be a purple background.
+
+The modifier attributes of blink, italic, underscore and so on may or may
+not work on the RGB colors.
+
+For a chart of the 216 possible RGB colors, run C<ack --help-rgb-colors>.
+
+=head1 ENVIRONMENT VARIABLES
+
+For commonly-used ack options, environment variables can make life
+much easier. These variables are ignored if B<--noenv> is specified
+on the command line.
+
+=over 4
+
+=item ACKRC
+
+Specifies the location of the user's F<.ackrc> file. If this file doesn't
+exist, F<ack> looks in the default location.
+
+=item ACK_COLOR_COLNO
+
+Color specification for the column number in ack's output. By default, the
+column number is not shown. You have to enable it with the B<--column>
+option. See the section "ack Colors" above.
+
+=item ACK_COLOR_FILENAME
+
+Color specification for the filename in ack's output. See the section "ack
+Colors" above.
+
+=item ACK_COLOR_LINENO
+
+Color specification for the line number in ack's output. See the section
+"ack Colors" above.
+
+=item ACK_COLOR_MATCH
+
+Color specification for the matched text in ack's output. See the section
+"ack Colors" above.
+
+=item ACK_PAGER
+
+Specifies a pager program, such as C<more>, C<less> or C<most>, to which
+ack will send its output.
+
+Using C<ACK_PAGER> does not suppress grouping and coloring like
+piping output on the command-line does, except that on Windows
+ack will assume that C<ACK_PAGER> does not support color.
+
+C<ACK_PAGER_COLOR> overrides C<ACK_PAGER> if both are specified.
+
+=item ACK_PAGER_COLOR
+
+Specifies a pager program that understands ANSI color sequences.
+Using C<ACK_PAGER_COLOR> does not suppress grouping and coloring
+like piping output on the command-line does.
+
+If you are not on Windows, you never need to use C<ACK_PAGER_COLOR>.
+
+=back
+
+=head1 ACK & OTHER TOOLS
+
+=head2 Simple vim integration
+
+F<ack> integrates easily with the Vim text editor. Set this in your
+F<.vimrc> to use F<ack> instead of F<grep>:
+
+ set grepprg=ack\ -k
+
+That example uses C<-k> to search through only files of the types ack
+knows about, but you may use other default flags. Now you can search
+with F<ack> and easily step through the results in Vim:
+
+ :grep Dumper perllib
+
+=head2 Editor integration
+
+Many users have integrated ack into their preferred text editors.
+For details and links, see L<https://beyondgrep.com/more-tools/>.
+
+=head2 Shell and Return Code
+
+For greater compatibility with I<grep>, I<ack> in normal use returns
+shell return or exit code of 0 only if something is found and 1 if
+no match is found.
+
+(Shell exit code 1 is C<$?=256> in perl with C<system> or backticks.)
+
+The I<grep> code 2 for errors is not used.
+
+If C<-f> or C<-g> are specified, then 0 is returned if at least one
+file is found. If no files are found, then 1 is returned.
+
+=cut
+
+=head1 DEBUGGING ACK PROBLEMS
+
+If ack gives you output you're not expecting, start with a few simple steps.
+
+=head2 Try it with B<--noenv>
+
+Your environment variables and F<.ackrc> may be doing things you're
+not expecting, or forgotten you specified. Use B<--noenv> to ignore
+your environment and F<.ackrc>.
+
+=head2 Use B<-f> to see what files have been selected for searching
+
+Ack's B<-f> was originally added as a debugging tool. If ack is
+not finding matches you think it should find, run F<ack -f> to see
+what files have been selected. You can also add the C<--show-types>
+options to show the type of each file selected.
+
+=head2 Use B<--dump>
+
+This lists the ackrc files that are loaded and the options loaded
+from them. You may be loading an F<.ackrc> file that you didn't know
+you were loading.
+
+=head1 ACKRC LOCATION SEMANTICS
+
+Ack can load its configuration from many sources. The following list
+specifies the sources Ack looks for configuration files; each one
+that is found is loaded in the order specified here, and
+each one overrides options set in any of the sources preceding
+it. (For example, if I set --sort-files in my user ackrc, and
+--nosort-files on the command line, the command line takes
+precedence)
+
+=over 4
+
+=item *
+
+Defaults are loaded from App::Ack::ConfigDefaults. This can be omitted
+using C<--ignore-ack-defaults>.
+
+=item * Global ackrc
+
+Options are then loaded from the global ackrc. This is located at
+C</etc/ackrc> on Unix-like systems.
+
+Under Windows XP and earlier, the global ackrc is at
+C<C:\Documents and Settings\All Users\Application Data\ackrc>
+
+Under Windows Vista/7, the global ackrc is at
+C<C:\ProgramData\ackrc>
+
+The C<--noenv> option prevents all ackrc files from being loaded.
+
+=item * User ackrc
+
+Options are then loaded from the user's ackrc. This is located at
+C<$HOME/.ackrc> on Unix-like systems.
+
+Under Windows XP and earlier, the user's ackrc is at
+C<C:\Documents and Settings\$USER\Application Data\ackrc>.
+
+Under Windows Vista/7, the user's ackrc is at
+C<C:\Users\$USER\AppData\Roaming\ackrc>.
+
+If you want to load a different user-level ackrc, it may be specified
+with the C<$ACKRC> environment variable.
+
+The C<--noenv> option prevents all ackrc files from being loaded.
+
+=item * Project ackrc
+
+Options are then loaded from the project ackrc. The project ackrc is
+the first ackrc file with the name C<.ackrc> or C<_ackrc>, first searching
+in the current directory, then the parent directory, then the grandparent
+directory, etc. This can be omitted using C<--noenv>.
+
+=item * --ackrc
+
+The C<--ackrc> option may be included on the command line to specify an
+ackrc file that can override all others. It is consulted even if C<--noenv>
+is present.
+
+=item * Command line
+
+Options are then loaded from the command line.
+
+=back
+
+=head1 BUGS & ENHANCEMENTS
+
+ack is based at GitHub at L<https://github.com/beyondgrep/ack3>
+
+Please report any bugs or feature requests to the issues list at
+GitHub: L<https://github.com/beyondgrep/ack3/issues>.
+
+Please include the operating system that you're using; the output of
+the command C<ack --version>; and any customizations in your F<.ackrc>
+you may have.
+
+To suggest enhancements, please submit an issue at
+L<https://github.com/beyondgrep/ack3/issues>. Also read the
+F<DEVELOPERS.md> file in the ack code repository.
+
+Also, feel free to discuss your issues on the ack mailing
+list at L<https://groups.google.com/group/ack-users>.
+
+=head1 SUPPORT
+
+Support for and information about F<ack> can be found at:
+
+=over 4
+
+=item * The ack homepage
+
+L<https://beyondgrep.com/>
+
+=item * Source repository
+
+L<https://github.com/beyondgrep/ack3>
+
+=item * The ack issues list at GitHub
+
+L<https://github.com/beyondgrep/ack3/issues>
+
+=item * The ack announcements mailing list
+
+L<https://groups.google.com/group/ack-announcement>
+
+=item * The ack users' mailing list
+
+L<https://groups.google.com/group/ack-users>
+
+=item * The ack development mailing list
+
+L<https://groups.google.com/group/ack-users>
+
+=back
+
+=head1 COMMUNITY
+
+There are ack mailing lists and a Slack channel for ack. See
+L<https://beyondgrep.com/community/> for details.
+
+=head1 FAQ
+
+This is the Frequently Asked Questions list for ack.
+
+=head2 Can I stop using grep now?
+
+Many people find I<ack> to be better than 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. For example, you might be looking through huge
+log files and not using regular expressions. In that case, I<grep>
+will probably perform better.
+
+=head2 Why isn't ack finding a match in (some file)?
+
+First, take a look and see if ack is even looking at the file. ack is
+intelligent in what files it will search and which ones it won't, but
+sometimes that can be surprising.
+
+Use the C<-f> switch, with no regex, to see a list of files that ack
+will search for you. If your file doesn't show up in the list of files
+that C<ack -f> shows, then ack never looks in it.
+
+=head2 Wouldn't it be great if F<ack> did search & replace?
+
+No, ack will always be read-only. Perl has a perfectly good way
+to do search & replace in files, using the C<-i>, C<-p> and C<-n>
+switches.
+
+You can certainly use ack to select your files to update. For
+example, to change all "foo" to "bar" in all PHP files, you can do
+this from the Unix shell:
+
+ $ perl -i -p -e's/foo/bar/g' $(ack -f --php)
+
+=head2 Can I make ack recognize F<.xyz> files?
+
+Yes! Please see L</"Defining your own types"> in the ack manual.
+
+=head2 Will you make ack recognize F<.xyz> files by default?
+
+We might, depending on how widely-used the file format is.
+
+Submit an issue at in the GitHub issue queue at
+L<https://github.com/beyondgrep/ack3/issues>. Explain what the file format
+is, where we can find out more about it, and what you have been using
+in your F<.ackrc> to support it.
+
+Please do not bother creating a pull request. The code for filetypes
+is trivial compared to the rest of the process we go through.
+
+=head2 Why is it called ack if it's called ack-grep?
+
+The name of the program is "ack". Some packagers have called it
+"ack-grep" when creating packages because there's already a package
+out there called "ack" that has nothing to do with this ack.
+
+I suggest you make a symlink named F<ack> that points to F<ack-grep>
+because one of the crucial benefits of ack is having a name that's
+so short and simple to type.
+
+To do that, run this with F<sudo> or as root:
+
+ ln -s /usr/bin/ack-grep /usr/bin/ack
+
+Alternatively, you could use a shell alias:
+
+ # bash/zsh
+ alias ack=ack-grep
+
+ # csh
+ alias ack ack-grep
=head2 What does F<ack> mean?
-Nothing. I wanted a name that was easy to type and that you could
-pronounce as a single syllable.
+Nothing. I wanted a name that was easy to type and that you could
+pronounce as a single syllable.
+
+=head2 Can I do multi-line regexes?
+
+No, ack does not support regexes that match multiple lines. Doing
+so would require reading in the entire file at a time.
+
+If you want to see lines near your match, use the C<--A>, C<--B>
+and C<--C> switches for displaying context.
+
+=head2 Why is ack telling me I have an invalid option when searching for C<+foo>?
+
+ack treats command line options beginning with C<+> or C<-> as options; if you
+would like to search for these, you may prefix your search term with C<--> or
+use the C<--match> option. (However, don't forget that C<+> is a regular
+expression metacharacter!)
+
+=head2 Why does C<"ack '.{40000,}'"> fail? Isn't that a valid regex?
+
+The Perl language limits the repetition quantifier to 32K. You
+can search for C<.{32767}> but not C<.{32768}>.
+
+=head2 Ack does "X" and shouldn't, should it?
+
+We try to remain as close to grep's behavior as possible, so when in
+doubt, see what grep does! If there's a mismatch in functionality there,
+please submit an issue to GitHub, and/or bring it up on the ack-users
+mailing list.
+
+=cut
+
+=head1 ACKNOWLEDGEMENTS
+
+How appropriate to have I<ack>nowledgements!
+
+Thanks to everyone who has contributed to ack in any way, including
+Geraint Edwards,
+Loren Howard,
+Yaroslav Halchenko,
+Thiago Perrotta,
+Thomas Gossler,
+Kieran Mace,
+Volker Glave,
+Axel Beckert,
+Eric Pement,
+Gabor Szabo,
+Frieder Bluemle,
+Grzegorz Kaczmarczyk,
+Dan Book,
+Tomasz Konojacki,
+Salomon Smeke,
+M. Scott Ford,
+Anders Eriksson,
+H.Merijn Brand,
+Duke Leto,
+Gerhard Poul,
+Ethan Mallove,
+Marek Kubica,
+Ray Donnelly,
+Nikolaj Schumacher,
+Ed Avis,
+Nick Morrott,
+Austin Chamberlin,
+Varadinsky,
+SE<eacute>bastien FeugE<egrave>re,
+Jakub Wilk,
+Pete Houston,
+Stephen Thirlwall,
+Jonah Bishop,
+Chris Rebert,
+Denis Howe,
+RaE<uacute>l GundE<iacute>n,
+James McCoy,
+Daniel Perrett,
+Steven Lee,
+Jonathan Perret,
+Fraser Tweedale,
+RaE<aacute>l GundE<aacute>n,
+Steffen Jaeckel,
+Stephan Hohe,
+Michael Beijen,
+Alexandr Ciornii,
+Christian Walde,
+Charles Lee,
+Joe McMahon,
+John Warwick,
+David Steinbrunner,
+Kara Martens,
+Volodymyr Medvid,
+Ron Savage,
+Konrad Borowski,
+Dale Sedivic,
+Michael McClimon,
+Andrew Black,
+Ralph Bodenner,
+Shaun Patterson,
+Ryan Olson,
+Shlomi Fish,
+Karen Etheridge,
+Olivier Mengue,
+Matthew Wild,
+Scott Kyle,
+Nick Hooey,
+Bo Borgerson,
+Mark Szymanski,
+Marq Schneider,
+Packy Anderson,
+JR Boyens,
+Dan Sully,
+Ryan Niebur,
+Kent Fredric,
+Mike Morearty,
+Ingmar Vanhassel,
+Eric Van Dewoestine,
+Sitaram Chamarty,
+Adam James,
+Richard Carlsson,
+Pedro Melo,
+AJ Schuster,
+Phil Jackson,
+Michael Schwern,
+Jan Dubois,
+Christopher J. Madsen,
+Matthew Wickline,
+David Dyck,
+Jason Porritt,
+Jjgod Jiang,
+Thomas Klausner,
+Uri Guttman,
+Peter Lewis,
+Kevin Riggle,
+Ori Avtalion,
+Torsten Blix,
+Nigel Metheringham,
+GE<aacute>bor SzabE<oacute>,
+Tod Hagan,
+Michael Hendricks,
+E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason,
+Piers Cawley,
+Stephen Steneker,
+Elias Lutfallah,
+Mark Leighton Fisher,
+Matt Diephouse,
+Christian Jaeger,
+Bill Sully,
+Bill Ricker,
+David Golden,
+Nilson Santos F. Jr,
+Elliot Shank,
+Merijn Broeren,
+Uwe Voelker,
+Rick Scott,
+Ask BjE<oslash>rn Hansen,
+Jerry Gay,
+Will Coleda,
+Mike O'Regan,
+Slaven ReziE<0x107>,
+Mark Stosberg,
+David Alan Pisoni,
+Adriano Ferreira,
+James Keenan,
+Leland Johnson,
+Ricardo Signes,
+Pete Krawczyk and
+Rob Hoelz.
+
+=head1 AUTHOR
+
+Andy Lester, C<< <andy at petdance.com> >>
+
+=head1 COPYRIGHT & LICENSE
+
+Copyright 2005-2024 Andy Lester.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the Artistic License v2.0.
+
+See https://www.perlfoundation.org/artistic-license-20.html or the LICENSE.md
+file that comes with the ack distribution.
+
+=cut
+
+1;
+}
+{
+package App::Ack;
+
+use warnings;
+use strict;
+
+
+our $VERSION;
+our $COPYRIGHT;
+BEGIN {
+ $VERSION = 'v3.8.1'; # Check https://beyondgrep.com/ for updates
+ $COPYRIGHT = 'Copyright 2005-2024 Andy Lester.';
+}
+our $STANDALONE = 0;
+our $ORIGINAL_PROGRAM_NAME;
+
+our $fh;
+
+BEGIN {
+ $fh = *STDOUT;
+}
+
+
+our %types;
+our %type_wanted;
+our %mappings;
+our %ignore_dirs;
+
+our $is_filter_mode;
+our $output_to_pipe;
+
+our $is_windows;
+
+our $debug_nopens = 0;
+
+# Line ending, changes to "\0" if --print0.
+our $ors = "\n";
+
+BEGIN {
+ # These have to be checked before any filehandle diddling.
+ $output_to_pipe = not -t *STDOUT;
+ $is_filter_mode = -p STDIN;
+
+ $is_windows = ($^O eq 'MSWin32');
+}
+
+
+sub warn {
+ return CORE::warn( _my_program(), ': ', @_, "\n" );
+}
+
+
+sub die {
+ return CORE::die( _my_program(), ': ', @_, "\n" );
+}
+
+sub _my_program {
+ require File::Basename;
+ return File::Basename::basename( $0 );
+}
+
+
+sub thpppt {
+ my $y = q{_ /|,\\'!.x',=(www)=, U };
+ $y =~ tr/,x!w/\nOo_/;
+
+ App::Ack::print( "$y ack $_[0]!\n" );
+ exit 0;
+}
+
+sub ackbar {
+ my $x;
+ $x = <<'_BAR';
+ 6?!I'7!I"?%+!
+ 3~!I#7#I"7#I!?!+!="+"="+!:!
+ 2?#I!7!I!?#I!7!I"+"=%+"=#
+ 1?"+!?*+!=#~"=!+#?"="+!
+ 0?"+!?"I"?&+!="~!=!~"=!+%="+"
+ /I!+!?)+!?!+!=$~!=!~!="+!="+"?!="?!
+ .?%I"?%+%='?!=#~$="
+ ,,!?%I"?(+$=$~!=#:"~$:!~!
+ ,I!?!I!?"I"?!+#?"+!?!+#="~$:!~!:!~!:!,!:!,":#~!
+ +I!?&+!="+!?#+$=!~":!~!:!~!:!,!:#,!:!,%:"
+ *+!I!?!+$=!+!=!+!?$+#=!~":!~":#,$:",#:!,!:!
+ *I!?"+!?!+!=$+!?#+#=#~":$,!:",!:!,&:"
+ )I!?$=!~!=#+"?!+!=!+!=!~!="~!:!~":!,'.!,%:!~!
+ (=!?"+!?!=!~$?"+!?!+!=#~"=",!="~$,$.",#.!:!=!
+ (I"+"="~"=!+&=!~"=!~!,!~!+!=!?!+!?!=!I!?!+"=!.",!.!,":!
+ %I$?!+!?!=%+!~!+#~!=!~#:#=!~!+!~!=#:!,%.!,!.!:"
+ $I!?!=!?!I!+!?"+!=!~!=!~!?!I!?!=!+!=!~#:",!~"=!~!:"~!=!:",&:" '-/
+ $?!+!I!?"+"=!+"~!,!:"+#~#:#,"=!~"=!,!~!,!.",!:".!:! */! !I!t!'!s! !a! !g!r!e!p!!! !/!
+ $+"=!+!?!+"~!=!:!~!:"I!+!,!~!=!:!~!,!:!,$:!~".&:"~!,# (-/
+ %~!=!~!=!:!.!+"~!:!,!.!,!~!=!:$.!,":!,!.!:!~!,!:!=!.#="~!,!:" ./!
+ %=!~!?!+"?"+!=!~",!.!:!?!~!.!:!,!:!,#.!,!:","~!:!=!~!=!:",!~! ./!
+ %+"~":!~!=#~!:!~!,!.!~!:",!~!=!~!.!:!,!.",!:!,":!=":!.!,!:!7! -/!
+ %~",!:".#:!=!:!,!:"+!:!~!:!.!,!~!,!.#,!.!,$:"~!,":"~!=! */!
+ &=!~!=#+!=!~",!.!:",#:#,!.",+:!,!.",!=!+!?!
+ &~!=!~!=!~!:"~#:",!.!,#~!:!.!+!,!.",$.",$.#,!+!I!?!
+ &~!="~!:!~":!~",!~!=!~":!,!:!~!,!:!,&.$,#."+!?!I!?!I!
+ &~!=!~!=!+!,!:!~!:!=!,!:!~&:$,!.!,".!,".!,#."~!+!?$I!
+ &~!=!~!="~!=!:!~":!,!~%:#,!:",!.!,#.",#I!7"I!?!+!?"I"
+ &+!I!7!:#~"=!~!:!,!:"~$.!=!.!,!~!,$.#,!~!7!I#?!+!?"I"7!
+ %7#?!+!~!:!=!~!=!~":!,!:"~":#.!,)7#I"?"I!7&
+ %7#I!=":!=!~!:"~$:"~!:#,!:!,!:!~!:#,!7#I!?#7)
+ $7$+!,!~!=#~!:!~!:!~$:#,!.!~!:!=!,":!7#I"?#7+=!?!
+ $7#I!~!,!~#=!~!:"~!:!,!:!,#:!=!~",":!7$I!?#I!7*+!=!+"
+ "I!7$I!,":!,!.!=":$,!:!,$:$7$I!+!?"I!7+?"I!7!I!7!,!
+ !,!7%I!:",!."~":!,&.!,!:!~!I!7$I!+!?"I!7,?!I!7',!
+ !7(,!.#~":!,%.!,!7%I!7!?#I"7,+!?!7*
+7+:!,!~#,"=!7'I!?#I"7/+!7+
+77I!+!7!?!7!I"71+!7,
+_BAR
+
+ return _pic_decode($x);
+}
+
+sub cathy {
+ my $x = <<'CATHY';
+ 0+!--+!
+ 0|! "C!H!O!C!O!L!A!T!E!!! !|!
+ 0|! "C!H!O!C!O!L!A!T!E!!! !|!
+ 0|! "C!H!O!C!O!L!A!T!E!!! !|!
+ 0|! $A"C!K!!! $|!
+ 0+!--+!
+ 6\! 1:!,!.! !
+ 7\! /.!M!~!Z!M!~!
+ 8\! /~!D! "M! !
+ 4.! $\! /M!~!.!8! +.!M# 4
+ 0,!.! (\! .~!M!N! ,+!I!.!M!.! 3
+ /?!O!.!M!:! '\! .O!.! +~!Z!=!N!.! 4
+ ..! !D!Z!.!Z!.! '\! 9=!M".! 6
+ /.! !.!~!M".! '\! 8~! 9
+ 4M!.! /.!7!N!M!.! F
+ 4.! &:!M! !N"M# !M"N!M! #D!M&=! =
+ :M!7!M#:! !~!M!7!,!$!M!:! #.! !O!N!.!M!:!M# ;
+ 8Z!M"~!N!$!D!.!N!?! !I!N!.! (?!M! !M!,!D!M".! 9
+ (?!Z!M!N!:! )=!M!O!8!.!M!+!M! !M!,! !O!M! +,!M!.!M!~!Z!N!M!:! &:!~! 0
+ &8!7!.!~!M"D!M!,! &M!?!=!8! !M!,!O! !M!+! !+!O!.!M! $M#~! !.!8!M!Z!.!M! !O!M"Z! %:!~!M!Z!M!Z!.! +
+ &:!M!7!,! *M!.!Z!M! !8"M!.!M!~! !.!M!.!=! #~!8!.!M! !7!M! "N!Z#I! !D!M!,!M!.! $."M!,! !M!.! *
+ 2$!O! "N! !.!M!I! !7" "M! "+!O! !~!M! !d!O!.!7!I!M!.! !.!O!=!M!.! !M",!M!.! %.!$!O!D! +
+ 1~!O! "M!+! !8!$! "M! "?!O! %Z!8!D!M!?!8!I!O!7!M! #M!.!M! "M",!M! 4
+ 07!~! ".!8! !.!M! "I!+! !.!M! &Z!D!.!7!=!M! !:!.!M! #:!8"+! !.!+!8! !8! 3
+ /~!M! #N! !~!M!$! !.!M! !.!M" &~!M! "~!M!O! "D! $M! !8! "M!,!M!+!D!.! 1
+ #.! #?!M!N!.! #~!O! $M!.!7!$! "?" !?!~!M! '7!8!?!M!.!+!M"O! $?"$!D! !.!O! !$!7!I!.! 0
+ $,!M!:!O!?! ".! !?!=! $=!:!O! !M! "M! !M! !+!$! (.! +.!M! !M!.! !8! !+"Z!~! $:!M!$! !.! '
+ #.!8!.!I!$! $7!I! %M" !=!M! !~!M!D! "7!I! .I!O! %?!=!,!D! !,!M! !D!~!8!~! %D!M! (
+ #.!M"?! $=!O! %=!N! "8!.! !Z!M! #M!~! (M!:! #.!M" &O! !M!.! !?!,! !8!.!N!~! $8!N!M!,!.! %
+ *$!O! &M!,! "O! !.!M!.! #M! (~!M( &O!.! !7! "M! !.!M!.!M!,! #.!M! !M! &
+ )=!8!.! $.!M!O!.! "$!.!I!N! !I!M# (7!M(I! %D"Z!M! "=!I! "M! !M!:! #~!D! '
+ )D! &8!N!:! ".!O! !M!="M! "M! (7!M) %." !M!D!."M!.! !$!=! !M!,! +
+ (M! &+!.!M! #Z!7!O!M!.!~!8! +,!M#D!?!M#D! #.!Z!M#,!Z!?! !~!N! "N!.! !M! +
+ 'D!:! %$!D! !?! #M!Z! !8!.! !M"?!7!?!7! '+!I!D! !?!O!:!M!:! ":!M!:! !M!7".!M! "8!+! !:!D! !.!M! *
+ %.!O!:! $.!O!+! !D!.! #M! "M!.!+!N!I!Z! "7!M!N!M!N!?!I!7!Z!=!M'D"~! #M!.!8!$! !:! !.!M! "N!?! !,!O! )
+ !.!?!M!:!M!I! %8!,! "M!.! #M! "N! !M!.! !M!.! !+!~! !.!M!.! ':!M! $M! $M!Z!$! !M!.! "D! "M! "?!M! (
+ !7!8! !+!I! ".! "$!=! ":!$! "+! !M!.! !O! !M!I!M".! !=!~! ",!O! '=!M! $$!,! #N!:! ":!8!.! !D!~! !,!M!.! !:!M!.! &
+ !:!,!.! &Z" #D! !.!8!."M!.! !8!?!Z!M!.!M! #Z!~! !?!M!Z!.! %~!O!.!8!$!N!8!O!I!:!~! !+! #M!.! !.!M!.! !+!M! ".!~!M!+! $
+ !.! 'D!I! #?!M!.!M!,! !.!Z! !.!8! #M&O!I!?! (~!I!M"." !M!Z!.! !M!N!.! "+!$!.! "M!.! !M!?!.! "8!M! $
+ (O!8! $M! !M!.! ".!:! !+!=! #M! #.!M! !+" *$!M":!.! !M!~! "M!7! #M! #7!Z! "M"$!M!.! !.! #
+ '$!Z! #.!7!+!M! $.!,! !+!:! #N! #.!M!.!+!M! +D!M! #=!N! ":!O! #=!M! #Z!D! $M!I! %
+ $,! ".! $.!M" %$!.! !?!~! "+!7!." !.!M!,! !M! *,!N!M!.$M!?! "D!,! #M!.! #N! +
+ ,M!Z! &M! "I!,! "M! %I!M! !?!=!.! (Z!8!M! $:!M!.! !,!M! $D! #.!M!.! )
+ +8!O! &.!8! "I!,! !~!M! &N!M! !M!D! '?!N!O!." $?!7! "?!~! #M!.! #I!D!.! (
+ 3M!,! "N!.! !D" &.!+!M!.! !M":!.":!M!7!M!D! 'M!.! "M!.! "M!,! $I! )
+ 3I! #M! "M!,! !:! &.!M" ".!,! !.!$!M!I! #.! !:! !.!M!?! "N!+! ".! /
+ 1M!,! #.!M!8!M!=!.! +~!N"O!Z"~! *+!M!.! "M! 2
+ 0.!M! &M!.! 8:! %.!M!Z! "M!=! *O!,! %
+ 0?!$! &N! )." .,! %."M! ":!M!.! 0
+ 0N!:! %?!O! #.! ..! &,! &.!D!,! "N!I! 0
+CATHY
+ return _pic_decode($x);
+}
+
+sub _pic_decode {
+ my($compressed) = @_;
+ $compressed =~ s/(.)(.)/$1x(ord($2)-32)/eg;
+ App::Ack::print( $compressed );
+ exit 0;
+}
+
+
+sub show_help {
+ App::Ack::print( <<"END_OF_HELP" );
+Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES]
+
+Search for PATTERN in each source file in the tree from the current
+directory on down. If any files or directories are specified, then
+only those files and directories are checked. ack may also search
+STDIN, but only if no file or directory arguments are specified,
+or if one of them is "-".
+
+Default switches may be specified in an .ackrc file. If you want no dependency
+on the environment, turn it off with --noenv.
+
+File select actions:
+ -f Only print the files selected, without
+ searching. The PATTERN must not be specified.
+ -g Same as -f, but only select files matching
+ PATTERN.
+
+File listing actions:
+ -l, --files-with-matches Print filenames with at least one match
+ -L, --files-without-matches Print filenames with no matches
+ -c, --count Print filenames and count of matching lines
+
+Searching:
+ -i, --ignore-case Ignore case distinctions in PATTERN
+ -S, --[no]smart-case Ignore case distinctions in PATTERN,
+ only if PATTERN contains no upper case.
+ Ignored if -i or -I are specified.
+ -I, --no-ignore-case Turns on case-sensitivity in PATTERN.
+ Negates -i and --smart-case.
+ -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
+ --range-start PATTERN Specify PATTERN as the start of a match range.
+ --range-end PATTERN Specify PATTERN as the end of a match range.
+ --match PATTERN Specify PATTERN explicitly. Typically omitted.
+ --and PATTERN Specifies PATTERN that MUST also be found on
+ the line for a match to occur. Repeatable.
+ --or PATTERN Specifies PATTERN that MAY also be found on
+ the line for a match to occur. Repeatable.
+ --not PATTERN Specifies PATTERN that must NOT be found on
+ the line for a match to occur. Repeatable.
+
+Search output:
+ --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
+ -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
+ --[no]column Show the column number of the first match
+
+ -A NUM, --after-context=NUM Print NUM lines of trailing context after
+ matching lines.
+ -B NUM, --before-context=NUM Print NUM lines of leading context before
+ matching lines.
+ -C [NUM], --context[=NUM] Print NUM lines (default 2) of output context.
+
+ --print0 Print null byte as separator between filenames,
+ only works with -f, -g, -l, -L or -c.
+
+ -s Suppress error messages about nonexistent or
+ unreadable files.
+
+
+File presentation:
+ --pager=COMMAND Pipes all ack output through COMMAND. For
+ example, --pager="less -R". Ignored if output
+ is redirected.
+ --nopager Do not send output through a pager. Cancels
+ any setting in ~/.ackrc, ACK_PAGER or
+ ACK_PAGER_COLOR.
+ --[no]heading Print a filename heading above each file's
+ results. (default: on when used interactively)
+ --[no]break Print a break between results from different
+ files. (default: on when used interactively)
+ --group Same as --heading --break
+ --nogroup Same as --noheading --nobreak
+ -p, --proximate=LINES Separate match output with blank lines unless
+ they are within LINES lines from each other.
+ -P, --proximate=0 Negates --proximate.
+ --[no]underline Print a line of carets under the matched text.
+ --[no]color, --[no]colour Highlight the matching text (default: on unless
+ output is redirected, or on Windows)
+ --color-filename=COLOR
+ --color-match=COLOR
+ --color-colno=COLOR
+ --color-lineno=COLOR Set the color for filenames, matches, line and
+ column numbers.
+ --help-colors Show a list of possible color combinations.
+ --help-rgb-colors Show a list of advanced RGB colors.
+ --flush Flush output immediately, even when ack is used
+ non-interactively (when output goes to a pipe or
+ file).
+
+
+File finding:
+ --sort-files Sort the found files lexically.
+ --show-types Show which types each file has.
+ --files-from=FILE Read the list of files to search from FILE.
+ -x Read the list of files to search from STDIN.
+
+File inclusion/exclusion:
+ --[no]ignore-dir=name Add/remove directory from list of ignored dirs
+ --[no]ignore-directory=name Synonym for ignore-dir
+ --ignore-file=FILTER:ARGS 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.
+
+File type inclusion/exclusion:
+ -t X, --type=X Include only X files, where X is a filetype,
+ e.g. python, html, markdown, etc
+ -T X, --type=noX Exclude X files, where X is a filetype.
+ -k, --known-types Include only files of types that ack recognizes.
+ --help-types Display all known types, and how they're defined.
+
+File type specification:
+ --type-set=TYPE:FILTER:ARGS Files with the given ARGS applied to the given
+ FILTER are recognized as being of type TYPE.
+ This replaces an existing definition for TYPE.
+ --type-add=TYPE:FILTER:ARGS Files with the given ARGS applied to the given
+ FILTER are recognized as being type TYPE.
+ --type-del=TYPE Removes all filters associated with TYPE.
+
+Miscellaneous:
+ --version Display version & copyright
+ --[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.
+ --dump Dump information on which options are loaded
+ and where they're defined.
+ --[no]filter Force ack to treat standard input as a pipe
+ (--filter) or tty (--nofilter)
+ --help This help
+ --man Print the manual.
+ --help-types Display all known types, and how they're defined.
+ --help-colors Show a list of possible color combinations.
+ --help-rgb-colors Show a list of advanced RGB colors.
+ --thpppt Bill the Cat
+ --bar The warning admiral
+ --cathy Chocolate! Chocolate! Chocolate!
+
+Filter specifications:
+ If FILTER is "ext", ARGS is a list of extensions checked against the
+ file's extension.
+ If FILTER is "is", ARGS must match the file's name exactly.
+ If FILTER is "match", ARGS is matched as a case-insensitive regex
+ against the filename.
+ If FILTER is "firstlinematch", ARGS is matched as a regex the first
+ line of the file's contents.
+
+Exit status is 0 if match, 1 if no match.
+
+ack's home page is at https://beyondgrep.com/
+
+The full ack manual is available by running "ack --man".
+
+This is version $App::Ack::VERSION of ack. Run "ack --version" for full version info.
+END_OF_HELP
+
+ return;
+ }
+
+
+
+sub show_help_types {
+ App::Ack::print( <<'END_OF_HELP' );
+Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES]
+
+The following is the list of filetypes supported by ack. You can specify a
+filetype to include with -t TYPE or --type=TYPE. You can exclude a
+filetype with -T TYPE or --type=noTYPE.
+
+Note that some files may appear in multiple types. For example, a file
+called Rakefile is both Ruby (--type=ruby) and Rakefile (--type=rakefile).
+
+END_OF_HELP
+
+ my @types = keys %App::Ack::mappings;
+ my $maxlen = 0;
+ for ( @types ) {
+ $maxlen = length if $maxlen < length;
+ }
+ for my $type ( sort @types ) {
+ next if $type =~ /^-/; # Stuff to not show
+ my $ext_list = $mappings{$type};
+
+ if ( ref $ext_list ) {
+ $ext_list = join( '; ', map { $_->to_string } @{$ext_list} );
+ }
+ App::Ack::print( sprintf( " %-*.*s %s\n", $maxlen, $maxlen, $type, $ext_list ) );
+ }
+
+ return;
+}
+
+
+
+sub show_help_colors {
+ App::Ack::print( <<'END_OF_HELP' );
+ack allows customization of the colors it uses when presenting matches
+onscreen. See the "ACK COLORS" section of the ack manual (ack --man).
+
+Here is a chart of how various color combinations appear: Each of the eight
+foreground colors, on each of the eight background colors or no background
+color, with and without the bold modifier.
+
+Run ack --help-rgb-colors for a chart of the RGB colors.
+
+END_OF_HELP
+
+ _show_color_grid();
+
+ return;
+}
+
+
+
+sub show_help_rgb {
+ App::Ack::print( <<'END_OF_HELP' );
+ack allows customization of the colors it uses when presenting matches
+onscreen. See the "ACK COLORS" section of the ack manual (ack --man).
+
+Colors may be specified as "rgbNNN" where "NNN" is a triplet of digits
+from 0 to 5 specifying the intensity of red, green and blue, respectively.
+
+Here is a grid of the 216 possible values for NNN.
+
+END_OF_HELP
+
+ _show_rgb_grid();
+
+ App::Ack::say( 'Here are the 216 possible colors with the "reverse" modifier applied.', "\n" );
+
+ _show_rgb_grid( 'reverse' );
+
+ return;
+}
+
+
+sub _show_color_grid {
+ my $cell_width = 7;
+
+ my @fg_colors = qw( black red green yellow blue magenta cyan white );
+ my @bg_colors = map { "on_$_" } @fg_colors;
+
+ App::Ack::say(
+ _color_cell( '' ),
+ map { _color_cell( $_ ) } @fg_colors
+ );
+
+ App::Ack::say(
+ _color_cell( '' ),
+ map { _color_cell( '-' x $cell_width ) } @fg_colors
+ );
+
+ for my $bg ( '', @bg_colors ) {
+ App::Ack::say(
+ _color_cell( '' ),
+ ( map { _color_cell( $_, "$_ $bg" ) } @fg_colors ),
+ $bg
+ );
+
+ App::Ack::say(
+ _color_cell( 'bold' ),
+ ( map { _color_cell( $_, "bold $_ $bg" ) } @fg_colors ),
+ $bg
+ );
+ App::Ack::say();
+ }
+
+ return;
+}
+
+
+sub _color_cell {
+ my $text = shift;
+ my $color = shift;
+
+ my $cell_width = 7;
+ $text = sprintf( '%-*s', $cell_width, $text );
+
+ return ($color ? Term::ANSIColor::colored( $text, $color ) : $text) . ' ';
+}
+
+
+sub _show_rgb_grid {
+ my $modifier = shift // '';
+
+ my $grid = <<'HERE';
+544 544 544 544 544 554 554 554 554 554 454 454 454 454 454 455 455 455 455 455 445 445 445 445 445 545 545 545 545 545
+533 533 533 543 543 553 553 553 453 453 353 353 353 354 354 355 355 355 345 345 335 335 335 435 435 535 535 535 534 534
+511 521 531 531 541 551 451 451 351 251 151 152 152 153 154 155 145 145 135 125 115 215 215 315 415 515 514 514 513 512
+500 510 520 530 540 550 450 350 250 150 050 051 052 053 054 055 045 035 025 015 005 105 205 305 405 505 504 503 502 501
+400 410 410 420 430 440 340 340 240 140 040 041 041 042 043 044 034 034 024 014 004 104 104 204 304 404 403 403 402 401
+300 300 310 320 320 330 330 230 130 130 030 030 031 032 032 033 033 023 013 013 003 003 103 203 203 303 303 302 301 301
+200 200 200 210 210 220 220 220 120 120 020 020 020 021 021 022 022 022 012 012 002 002 002 102 102 202 202 202 201 201
+100 100 100 100 100 110 110 110 110 110 010 010 010 010 010 011 011 011 011 011 001 001 001 001 001 101 101 101 101 101
+
+522 522 532 542 542 552 552 452 352 352 252 252 253 254 254 255 255 245 235 235 225 225 325 425 425 525 525 524 523 523
+
+411 411 421 431 431 441 441 341 241 241 141 141 142 143 143 144 144 134 124 124 114 114 214 314 314 414 414 413 412 412
+
+422 422 432 432 432 442 442 442 342 342 242 242 242 243 243 244 244 244 234 234 224 224 224 324 324 424 424 424 423 423
+
+311 311 311 321 321 331 331 331 231 231 131 131 131 132 132 133 133 133 123 123 113 113 113 213 213 313 313 313 312 312
+
+433 433 433 433 433 443 443 443 443 443 343 343 343 343 343 344 344 344 344 344 334 334 334 334 334 434 434 434 434 434
+211 211 211 211 211 221 221 221 221 221 121 121 121 121 121 122 122 122 122 122 112 112 112 112 112 212 212 212 212 212
+
+322 322 322 322 322 332 332 332 332 332 232 232 232 232 232 233 233 233 233 233 223 223 223 223 223 323 323 323 323 323
+
+555 555 555 555 555 555 555 555 555 555 555 555 555 555 555 555 555 555 555 555 555 555 555 555 555 555 555 555 555 555
+444 444 444 444 444 444 444 444 444 444 444 444 444 444 444 444 444 444 444 444 444 444 444 444 444 444 444 444 444 444
+333 333 333 333 333 333 333 333 333 333 333 333 333 333 333 333 333 333 333 333 333 333 333 333 333 333 333 333 333 333
+222 222 222 222 222 222 222 222 222 222 222 222 222 222 222 222 222 222 222 222 222 222 222 222 222 222 222 222 222 222
+111 111 111 111 111 111 111 111 111 111 111 111 111 111 111 111 111 111 111 111 111 111 111 111 111 111 111 111 111 111
+000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000
+HERE
+
+ $grid =~ s/(\d\d\d)/Term::ANSIColor::colored( "$1", "$modifier rgb$1" )/eg;
+
+ App::Ack::say( $grid );
+
+ return;
+}
+
+
+sub show_man {
+ require Pod::Usage;
+ Pod::Usage::pod2usage({
+ -input => $App::Ack::ORIGINAL_PROGRAM_NAME,
+ -verbose => 2,
+ -exitval => 0,
+ });
+
+ return;
+}
+
+
+
+sub get_version_statement {
+ require Config;
+
+ my $copyright = $App::Ack::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 $perl_ver = sprintf( 'v%vd', $^V );
+
+ my $build_type = $App::Ack::STANDALONE ? 'standalone version' : 'standard build';
+
+ return <<"END_OF_VERSION";
+ack $App::Ack::VERSION ($build_type)
+Running under Perl $perl_ver at $this_perl
+
+$copyright
+
+This program is free software. You may modify or distribute it
+under the terms of the Artistic License v2.0.
+END_OF_VERSION
+}
+
+
+sub print { print {$fh} @_; return; }
+sub say { print {$fh} @_, $ors; return; }
+sub print_blank_line { print {$fh} "\n"; return; }
+
+sub set_up_pager {
+ my $command = shift;
+
+ return if App::Ack::output_to_pipe();
+
+ my $pager;
+ if ( not open( $pager, '|-', $command ) ) {
+ App::Ack::die( qq{Unable to pipe to pager "$command": $!} );
+ }
+ $fh = $pager;
+
+ return;
+}
+
+
+sub output_to_pipe {
+ return $output_to_pipe;
+}
+
+
+sub exit_from_ack {
+ my $nmatches = shift;
+
+ my $rc = $nmatches ? 0 : 1;
+ exit $rc;
+}
+
+
+sub show_types {
+ my $file = shift;
+
+ my @types = filetypes( $file );
+ my $arrow = @types ? ' => ' : ' =>';
+ App::Ack::say( $file->name, $arrow, join( ',', @types ) );
+
+ return;
+}
+
+
+sub filetypes {
+ my ( $file ) = @_;
+
+ my @matches;
+
+ foreach my $k (keys %App::Ack::mappings) {
+ my $filters = $App::Ack::mappings{$k};
+
+ foreach my $filter (@{$filters}) {
+ # Clone the file.
+ my $clone = $file->clone;
+ if ( $filter->filter($clone) ) {
+ push @matches, $k;
+ last;
+ }
+ }
+ }
+
+ # https://metacpan.org/pod/distribution/Perl-Critic/lib/Perl/Critic/Policy/Subroutines/ProhibitReturnSort.pm
+ @matches = sort @matches;
+ return @matches;
+}
+
+
+sub is_lowercase {
+ my $pat = shift;
+
+ # The simplest case.
+ return 1 if lc($pat) eq $pat;
+
+ # If we have capitals, then go clean up any metacharacters that might have capitals.
+
+ # Get rid of any literal backslashes first to avoid confusion.
+ $pat =~ s/\\\\//g;
+
+ my $metacharacter = qr{
+ |\\A # Beginning of string
+ |\\B # Not word boundary
+ |\\c[a-zA-Z] # Control characters
+ |\\D # Non-digit character
+ |\\G # End-of-match position of prior match
+ |\\H # Not horizontal whitespace
+ |\\K # Keep to the left
+ |\\N(\{.+?\})? # Anything but \n, OR Unicode sequence
+ |\\[pP]\{.+?\} # Named property and negation
+ |\\[pP][A-Z] # Named property and negation, single-character shorthand
+ |\\R # Linebreak
+ |\\S # Non-space character
+ |\\V # Not vertical whitespace
+ |\\W # Non-word character
+ |\\X # ???
+ |\\x[0-9A-Fa-f]{2} # Hex sequence
+ |\\Z # End of string
+ }x;
+ $pat =~ s/$metacharacter//g;
+
+ my $name = qr/[_A-Za-z][_A-Za-z0-9]*?/;
+ # Eliminate named captures.
+ $pat =~ s/\(\?'$name'//g;
+ $pat =~ s/\(\?<$name>//g;
+
+ # Eliminate named backreferences.
+ $pat =~ s/\\k'$name'//g;
+ $pat =~ s/\\k<$name>//g;
+ $pat =~ s/\\k\{$name\}//g;
+
+ # Now with those metacharacters and named things removed, now see if it's lowercase.
+ return 1 if lc($pat) eq $pat;
+
+ return 0;
+}
+
+
+# Returns a regex object based on a string and command-line options.
+# Dies when the regex $str is undefined (i.e. not given on command line).
+
+sub build_regex {
+ my $str = shift;
+ my $opt = shift;
+
+ # Check for lowercaseness before we do any modifications.
+ my $regex_is_lc = App::Ack::is_lowercase( $str );
+
+ if ( $opt->{Q} ) {
+ $str = quotemeta( $str );
+ }
+ else {
+ # Compile the regex to see if it dies or throws warnings.
+ local $SIG{__WARN__} = sub { CORE::die @_ }; # Anything that warns becomes a die.
+ my $scratch_regex = eval { qr/$str/ };
+ if ( not $scratch_regex ) {
+ my $err = $@;
+ chomp $err;
+
+ if ( $err =~ m{^(.+?); marked by <-- HERE in m/(.+?) <-- HERE} ) {
+ my ($why, $where) = ($1,$2);
+ my $pointy = ' ' x (6+length($where)) . '^---HERE';
+ App::Ack::die( "Invalid regex '$str'\nRegex: $str\n$pointy $why" );
+ }
+ else {
+ App::Ack::die( "Invalid regex '$str'\n$err" );
+ }
+ }
+ }
+
+ my $scan_str = $str;
+
+ # Whole words only.
+ if ( $opt->{w} ) {
+ my $ok = 1;
+
+ if ( $str =~ /^\\[wd]/ ) {
+ # Explicit \w is good.
+ }
+ else {
+ # Can start with \w, (, [ or dot.
+ if ( $str !~ /^[\w\(\[\.]/ ) {
+ $ok = 0;
+ }
+ }
+
+ # Can end with \w, }, ), ], +, *, or dot.
+ if ( $str !~ /[\w\}\)\]\+\*\?\.]$/ ) {
+ $ok = 0;
+ }
+ # ... unless it's escaped.
+ elsif ( $str =~ /\\[\}\)\]\+\*\?\.]$/ ) {
+ $ok = 0;
+ }
+
+ if ( !$ok ) {
+ App::Ack::die( '-w will not do the right thing if your regex does not begin and end with a word character.' );
+ }
+
+ if ( $str =~ /^\w+$/ ) {
+ # No need for fancy regex if it's a simple word.
+ $str = sprintf( '\b(?:%s)\b', $str );
+ }
+ else {
+ $str = sprintf( '(?:^|\b|\s)\K(?:%s)(?=\s|\b|$)', $str );
+ }
+ }
+
+ if ( $opt->{i} || ($opt->{S} && $regex_is_lc) ) {
+ $_ = "(?i)$_" for ( $str, $scan_str );
+ }
+
+ my $scan_regex = undef;
+ my $regex = eval { qr/$str/ };
+ if ( $regex ) {
+ if ( $scan_str !~ /\$/ ) {
+ # No line_scan is possible if there's a $ in the regex.
+ $scan_regex = eval { qr/$scan_str/m };
+ }
+ }
+ else {
+ my $err = $@;
+ chomp $err;
+ App::Ack::die( "Invalid regex '$str':\n $err" );
+ }
+
+ return ($regex, $scan_regex);
+}
+
+
+sub build_all_regexes {
+ my $opt_regex = shift;
+ my $opt = shift;
+
+ my $re_match;
+ my $re_not;
+ my $re_hilite;
+ my $re_scan;
+
+ my @parts;
+
+ # AND: alpha and beta
+ if ( @parts = @{$opt->{and}} ) {
+ my @match_parts;
+ my @hilite_parts;
+
+ for my $part ( @parts ) {
+ my ($match, undef) = build_regex( $part, $opt );
+ push @match_parts, "(?=.*$match)";
+ push @hilite_parts, $match;
+ }
+
+ my ($match, $scan) = build_regex( $opt_regex, $opt );
+ push @match_parts, ".*$match";
+ push @hilite_parts, $match;
+
+ $re_match = join( '', @match_parts );
+ $re_hilite = join( '|', @hilite_parts );
+ $re_scan = $scan;
+ }
+ # OR: alpha OR beta
+ elsif ( @parts = @{$opt->{or}} ) {
+ my @match_parts;
+ my @scan_parts;
+
+ for my $part ( $opt_regex, @parts ) {
+ my ($match, $scan) = build_regex( $part, $opt );
+ push @match_parts, $match;
+ push @scan_parts, $scan;
+ }
+
+ $re_match = join( '|', @match_parts );
+ $re_hilite = $re_match;
+ $re_scan = join( '|', @scan_parts );
+ }
+ # NOT: alpha NOT beta
+ elsif ( @parts = @{$opt->{not}} ) {
+ ($re_match, $re_scan) = build_regex( $opt_regex, $opt );
+ $re_hilite = $re_match;
+
+ my @not_parts;
+ for my $part ( @parts ) {
+ (my $re, undef) = build_regex( $part, $opt );
+ push @not_parts, $re;
+ }
+ $re_not = join( '|', @not_parts );
+ }
+ # No booleans.
+ else {
+ ($re_match, $re_scan) = build_regex( $opt_regex, $opt );
+ $re_hilite = $re_match;
+ }
+
+ return ($re_match, $re_not, $re_hilite, $re_scan);
+}
+
+
+1; # End of App::Ack
+}
+{
+package App::Ack::ConfigDefault;
+
+use warnings;
+use strict;
+
+
+
+
+sub options {
+ return split( /\n/, _options_block() );
+}
+
+
+sub options_clean {
+ return grep { /./ && !/^#/ } options();
+}
+
+
+sub _options_block {
+ my $lines = <<'HERE';
+# This is the default ackrc for ack version ==VERSION==.
+
+# There are four different ways to match
+#
+# is: Match the filename exactly
+#
+# ext: Match the extension of the filename exactly
+#
+# match: Match the filename against a Perl regular expression
+#
+# firstlinematch: Match the first 250 characters of the first line
+# of text against a Perl regular expression. This is only for
+# the --type-add option.
+
+
+### Directories to ignore
+
+# Bazaar
+# https://bazaar.canonical.com/
+--ignore-directory=is:.bzr
+
+# Codeville
+# http://freshmeat.sourceforge.net/projects/codeville
+--ignore-directory=is:.cdv
+
+# Interface Builder (Xcode)
+# https://en.wikipedia.org/wiki/Interface_Builder
+--ignore-directory=is:~.dep
+--ignore-directory=is:~.dot
+--ignore-directory=is:~.nib
+--ignore-directory=is:~.plst
+
+# Git
+# https://git-scm.com/
+--ignore-directory=is:.git
+# When submodules are used, .git is a file.
+--ignore-file=is:.git
+
+# Mercurial
+# https://www.mercurial-scm.org/
+--ignore-directory=is:.hg
+
+# Quilt
+# https://directory.fsf.org/wiki/Quilt
+--ignore-directory=is:.pc
+
+# Subversion
+# https://subversion.apache.org/
+--ignore-directory=is:.svn
+
+# Monotone
+# https://www.monotone.ca/
+--ignore-directory=is:_MTN
+
+# CVS
+# https://savannah.nongnu.org/projects/cvs
+--ignore-directory=is:CVS
+
+# RCS
+# https://www.gnu.org/software/rcs/
+--ignore-directory=is:RCS
+
+# SCCS
+# https://en.wikipedia.org/wiki/Source_Code_Control_System
+--ignore-directory=is:SCCS
+
+# darcs
+# http://darcs.net/
+--ignore-directory=is:_darcs
+
+# Vault/Fortress
+--ignore-directory=is:_sgbak
+
+# autoconf
+# https://www.gnu.org/software/autoconf/
+--ignore-directory=is:autom4te.cache
+
+# Perl module building
+--ignore-directory=is:blib
+--ignore-directory=is:_build
+
+# Perl Devel::Cover module's output directory
+# https://metacpan.org/release/Devel-Cover
+--ignore-directory=is:cover_db
+
+# Node modules created by npm
+--ignore-directory=is:node_modules
+
+# CMake cache
+# https://www.cmake.org/
+--ignore-directory=is:CMakeFiles
+
+# Eclipse workspace folder
+# https://eclipse.org/
+--ignore-directory=is:.metadata
+
+# Cabal (Haskell) sandboxes
+# https://www.haskell.org/cabal/users-guide/installing-packages.html
+--ignore-directory=is:.cabal-sandbox
+
+# Python caches
+# https://docs.python.org/3/tutorial/modules.html
+--ignore-directory=is:__pycache__
+--ignore-directory=is:.pytest_cache
+
+# macOS Finder remnants
+--ignore-directory=is:__MACOSX
+--ignore-file=is:.DS_Store
+
+### Files to ignore
+
+# Backup files
+--ignore-file=ext:bak
+--ignore-file=match:/~$/
+
+# Emacs swap files
+--ignore-file=match:/^#.+#$/
+
+# vi/vim swap files https://www.vim.org/
+--ignore-file=match:/[._].*[.]swp$/
+
+# core dumps
+--ignore-file=match:/core[.]\d+$/
+
+# minified JavaScript
+--ignore-file=match:/[.-]min[.]js$/
+--ignore-file=match:/[.]js[.]min$/
+
+# minified CSS
+--ignore-file=match:/[.]min[.]css$/
+--ignore-file=match:/[.]css[.]min$/
+
+# JS and CSS source maps
+--ignore-file=match:/[.]js[.]map$/
+--ignore-file=match:/[.]css[.]map$/
+
+# PDFs, because they pass Perl's -T detection
+--ignore-file=ext:pdf
+
+# Common graphics, just as an optimization
+--ignore-file=ext:gif,jpg,jpeg,png
+
+# Common archives, as an optimization
+--ignore-file=ext:gz,tar,tgz,zip
+
+# Python compiled modules
+--ignore-file=ext:pyc,pyd,pyo
+
+# Python's pickle serialization format
+# https://docs.python.org/2/library/pickle.html#example
+# https://docs.python.org/3.7/library/pickle.html#examples
+--ignore-file=ext:pkl,pickle
+
+# C extensions
+--ignore-file=ext:so
+
+# Compiled gettext files
+--ignore-file=ext:mo
+
+# Visual Studio user and workspace settings
+# https://code.visualstudio.com/docs/getstarted/settings
+--ignore-dir=is:.vscode
+
+### Filetypes defined
+
+# Makefiles
+# https://www.gnu.org/s/make/
+--type-add=make:ext:mk
+--type-add=make:ext:mak
+--type-add=make:is:makefile
+--type-add=make:is:Makefile
+--type-add=make:is:Makefile.Debug
+--type-add=make:is:Makefile.Release
+--type-add=make:is:GNUmakefile
+
+# Rakefiles
+# https://rake.rubyforge.org/
+--type-add=rake:is:Rakefile
+
+# CMake
+# https://cmake.org/
+--type-add=cmake:is:CMakeLists.txt
+--type-add=cmake:ext:cmake
+
+# Bazel build tool
+# https://docs.bazel.build/versions/master/skylark/bzl-style.html
+--type-add=bazel:ext:bzl
+# https://docs.bazel.build/versions/master/guide.html#bazelrc-the-bazel-configuration-file
+--type-add=bazel:ext:bazelrc
+# https://docs.bazel.build/versions/master/build-ref.html#BUILD_files
+--type-add=bazel:is:BUILD
+# https://docs.bazel.build/versions/master/build-ref.html#workspace
+--type-add=bazel:is:WORKSPACE
+
+
+# Actionscript
+--type-add=actionscript:ext:as,mxml
+
+# Ada
+# https://www.adaic.org/
+--type-add=ada:ext:ada,adb,ads
+
+# ASP
+# https://docs.microsoft.com/en-us/previous-versions/office/developer/server-technologies/aa286483(v=msdn.10)
+--type-add=asp:ext:asp
+
+# ASP.Net
+# https://dotnet.microsoft.com/apps/aspnet
+--type-add=aspx:ext:master,ascx,asmx,aspx,svc
+
+# Assembly
+--type-add=asm:ext:asm,s
+
+# DOS/Windows batch
+--type-add=batch:ext:bat,cmd
+
+# ColdFusion
+# https://en.wikipedia.org/wiki/ColdFusion
+--type-add=cfmx:ext:cfc,cfm,cfml
+
+# Clojure
+# https://clojure.org/
+--type-add=clojure:ext:clj,cljs,edn,cljc
+
+# C
+# .xs are Perl C files
+--type-add=cc:ext:c,h,xs
+
+# C header files
+--type-add=hh:ext:h
+
+# CoffeeScript
+# https://coffeescript.org/
+--type-add=coffeescript:ext:coffee
+
+# C++
+--type-add=cpp:ext:cpp,cc,cxx,m,hpp,hh,h,hxx
+
+# C++ header files
+--type-add=hpp:ext:hpp,hh,h,hxx
+
+# C#
+--type-add=csharp:ext:cs
+
+# Crystal-lang
+# https://crystal-lang.org/
+--type-add=crystal:ext:cr,ecr
+
+# CSS
+# https://www.w3.org/Style/CSS/
+--type-add=css:ext:css
+
+# Dart
+# https://dart.dev/
+--type-add=dart:ext:dart
+
+# Delphi
+# https://en.wikipedia.org/wiki/Embarcadero_Delphi
+--type-add=delphi:ext:pas,int,dfm,nfm,dof,dpk,dproj,groupproj,bdsgroup,bdsproj
+
+# Elixir
+# https://elixir-lang.org/
+--type-add=elixir:ext:ex,exs
+
+# Elm
+# https://elm-lang.org
+--type-add=elm:ext:elm
+
+# Emacs Lisp
+# https://www.gnu.org/software/emacs
+--type-add=elisp:ext:el
+
+# Erlang
+# https://www.erlang.org/
+--type-add=erlang:ext:erl,hrl
+
+# Fortran
+# https://en.wikipedia.org/wiki/Fortran
+--type-add=fortran:ext:f,f77,f90,f95,f03,for,ftn,fpp
+
+# Go
+# https://golang.org/
+--type-add=go:ext:go
+
+# Groovy
+# https://www.groovy-lang.org/
+--type-add=groovy:ext:groovy,gtmpl,gpp,grunit,gradle
+
+# GSP
+# https://gsp.grails.org/
+--type-add=gsp:ext:gsp
+
+# Haskell
+# https://www.haskell.org/
+--type-add=haskell:ext:hs,lhs
+
+# HTML
+--type-add=html:ext:htm,html,xhtml
+
+# Jade
+# http://jade-lang.com/
+--type-add=jade:ext:jade
+
+# Java
+# https://www.oracle.com/technetwork/java/index.html
+--type-add=java:ext:java,properties
+
+# JavaScript
+--type-add=js:ext:js
+
+# JSP
+# https://www.oracle.com/technetwork/java/javaee/jsp/index.html
+--type-add=jsp:ext:jsp,jspx,jspf,jhtm,jhtml
+
+# JSON
+# https://json.org/
+--type-add=json:ext:json
+
+# Kotlin
+# https://kotlinlang.org/
+--type-add=kotlin:ext:kt,kts
+
+# Less
+# http://www.lesscss.org/
+--type-add=less:ext:less
+
+# Common Lisp
+# https://common-lisp.net/
+--type-add=lisp:ext:lisp,lsp
+
+# Lua
+# https://www.lua.org/
+--type-add=lua:ext:lua
+--type-add=lua:firstlinematch:/^#!.*\blua(jit)?/
+
+# Markdown
+# https://en.wikipedia.org/wiki/Markdown
+--type-add=markdown:ext:md,markdown
+# We understand that there are many ad hoc extensions for markdown
+# that people use. .md and .markdown are the two that ack recognizes.
+# You are free to add your own in your ackrc file.
+
+# Matlab
+# https://en.wikipedia.org/wiki/MATLAB
+--type-add=matlab:ext:m
+
+# Objective-C
+--type-add=objc:ext:m,h
+
+# Objective-C++
+--type-add=objcpp:ext:mm,h
+
+# OCaml
+# https://ocaml.org/
+--type-add=ocaml:ext:ml,mli,mll,mly
+
+# Perl
+# https://perl.org/
+--type-add=perl:ext:pl,pm,pod,t,psgi
+--type-add=perl:firstlinematch:/^#!.*\bperl/
+
+# Perl tests
+--type-add=perltest:ext:t
+
+# Perl's Plain Old Documentation format, POD
+--type-add=pod:ext:pod
+
+# PHP
+# https://www.php.net/
+--type-add=php:ext:php,phpt,php3,php4,php5,phtml
+--type-add=php:firstlinematch:/^#!.*\bphp/
+
+# Plone
+# https://plone.org/
+--type-add=plone:ext:pt,cpt,metadata,cpy,py
+
+# PowerShell
+# https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_scripts
+# https://learn.microsoft.com/en-us/powershell/scripting/developer/module/understanding-a-windows-powershell-module
+--type-add=powershell:ext:ps1,psm1
+
+# PureScript
+# https://www.purescript.org
+--type-add=purescript:ext:purs
+
+# Python
+# https://www.python.org/
+--type-add=python:ext:py
+--type-add=python:firstlinematch:/^#!.*\bpython/
+
+# Pytest
+# https://pytest.org/
+# Pytest files are *.py files that start with test_ or end with _test.py
+# https://docs.pytest.org/en/stable/explanation/goodpractices.html#test-discovery
+--type-add=pytest:match:_test\.py$
+--type-add=pytest:match:^test_.*\.py$
+
+# R
+# https://www.r-project.org/
+# https://r4ds.had.co.nz/r-markdown.html
+--type-add=rr:ext:R,Rmd
+
+# reStructured Text
+# https://docutils.sourceforge.io/rst.html
+--type-add=rst:ext:rst
+
+# Ruby
+# https://www.ruby-lang.org/
+--type-add=ruby:ext:rb,rhtml,rjs,rxml,erb,rake,spec
+--type-add=ruby:is:Rakefile
+--type-add=ruby:firstlinematch:/^#!.*\bruby/
+
+# Rust
+# https://www.rust-lang.org/
+--type-add=rust:ext:rs
+
+# Sass
+# https://sass-lang.com
+--type-add=sass:ext:sass,scss
+
+# Scala
+# https://www.scala-lang.org/
+--type-add=scala:ext:scala,sbt
+
+# Scheme
+# https://groups.csail.mit.edu/mac/projects/scheme/
+--type-add=scheme:ext:scm,ss
+
+# Shell
+--type-add=shell:ext:sh,bash,csh,tcsh,ksh,zsh,fish
+--type-add=shell:firstlinematch:/^#!.*\b(?:ba|t?c|k|z|fi)?sh\b/
+
+# Smalltalk
+# http://www.smalltalk.org/
+--type-add=smalltalk:ext:st
+
+# Smarty
+# https://www.smarty.net/
+--type-add=smarty:ext:tpl
+
+# SQL
+# https://www.iso.org/standard/45498.html
+--type-add=sql:ext:sql,ctl
+
+# Stylus
+# http://stylus-lang.com/
+--type-add=stylus:ext:styl
+
+# SVG
+# https://en.wikipedia.org/wiki/Scalable_Vector_Graphics
+--type-add=svg:ext:svg
+
+# Swift
+# https://developer.apple.com/swift/
+--type-add=swift:ext:swift
+--type-add=swift:firstlinematch:/^#!.*\bswift/
+
+# Tcl
+# https://www.tcl.tk/
+--type-add=tcl:ext:tcl,itcl,itk
+
+# Terraform
+# https://github.com/hashicorp/terraform
+--type-add=terraform=.tf,.tfvars
+
+# TeX & LaTeX
+# https://www.latex-project.org/
+--type-add=tex:ext:tex,cls,sty
+
+# Template Toolkit (Perl)
+# http//template-toolkit.org/
+--type-add=ttml:ext:tt,tt2,ttml
+
+# TOML
+# https://toml.io/
+--type-add=toml:ext:toml
+
+# TypeScript
+# https://www.typescriptlang.org/
+--type-add=ts:ext:ts,tsx
+
+# Visual Basic
+--type-add=vb:ext:bas,cls,frm,ctl,vb,resx
+
+# Verilog
+--type-add=verilog:ext:v,vh,sv
+
+# VHDL
+# http://www.eda.org/twiki/bin/view.cgi/P1076/WebHome
+--type-add=vhdl:ext:vhd,vhdl
+
+# Vim
+# https://www.vim.org/
+--type-add=vim:ext:vim
+
+# XML
+# https://www.w3.org/TR/REC-xml/
+--type-add=xml:ext:xml,dtd,xsd,xsl,xslt,ent,wsdl
+--type-add=xml:firstlinematch:/<[?]xml/
+
+# YAML
+# https://yaml.org/
+--type-add=yaml:ext:yaml,yml
+HERE
+ $lines =~ s/==VERSION==/$App::Ack::VERSION/sm;
+
+ return $lines;
+}
+
+1;
+}
+{
+package App::Ack::ConfigFinder;
+
+
+use strict;
+use warnings;
+
+use Cwd 3.00 ();
+use File::Spec 3.00 ();
+
+use if ($^O eq 'MSWin32'), 'Win32';
+
+
+sub new {
+ my ( $class ) = @_;
+
+ return bless {}, $class;
+}
+
+
+sub _remove_redundancies {
+ my @configs = @_;
+
+ my %seen;
+ my @uniq;
+ foreach my $config (@configs) {
+ my $path = $config->{path};
+ my $key = -e $path ? Cwd::realpath( $path ) : $path;
+ if ( not $App::Ack::is_windows ) {
+ # On Unix, uniquify on inode.
+ my ($dev, $inode) = (stat $key)[0, 1];
+ $key = "$dev:$inode" if defined $dev;
+ }
+ push( @uniq, $config ) unless $seen{$key}++;
+ }
+ return @uniq;
+}
+
+
+sub _check_for_ackrc {
+ return unless defined $_[0];
+
+ my @files = grep { -f }
+ map { File::Spec->catfile(@_, $_) }
+ qw(.ackrc _ackrc);
+
+ App::Ack::die( File::Spec->catdir(@_) . ' contains both .ackrc and _ackrc. Please remove one of those files.' )
+ if @files > 1;
+
+ return wantarray ? @files : $files[0];
+} # end _check_for_ackrc
+
+
+
+sub find_config_files {
+ my @config_files;
+
+ if ( $App::Ack::is_windows ) {
+ push @config_files, map { +{ path => File::Spec->catfile($_, 'ackrc') } } (
+ Win32::GetFolderPath(Win32::CSIDL_COMMON_APPDATA()),
+ Win32::GetFolderPath(Win32::CSIDL_APPDATA()),
+ );
+ }
+ else {
+ push @config_files, { path => '/etc/ackrc' };
+ }
+
+
+ if ( $ENV{'ACKRC'} && -f $ENV{'ACKRC'} ) {
+ push @config_files, { path => $ENV{'ACKRC'} };
+ }
+ else {
+ push @config_files, map { +{ path => $_ } } _check_for_ackrc($ENV{'HOME'});
+ }
+
+ my $cwd = Cwd::getcwd();
+ return () unless defined $cwd;
+
+ # XXX This should go through some untainted cwd-fetching function, and not get untainted brute-force like this.
+ $cwd =~ /(.+)/;
+ $cwd = $1;
+ my @dirs = File::Spec->splitdir( $cwd );
+ while ( @dirs ) {
+ my $ackrc = _check_for_ackrc(@dirs);
+ if ( defined $ackrc ) {
+ push @config_files, { project => 1, path => $ackrc };
+ last;
+ }
+ pop @dirs;
+ }
+
+ # We only test for existence here, so if the file is deleted out from under us, this will fail later.
+ return _remove_redundancies( @config_files );
+}
+
+1;
+}
+{
+package App::Ack::ConfigLoader;
+
+use strict;
+use warnings;
+use 5.010;
+
+use File::Spec 3.00 ();
+use Getopt::Long 2.38 ();
+use Text::ParseWords 3.1 ();
+
+sub configure_parser {
+ my @opts = @_;
+
+ my @standard = qw(
+ default
+ bundling
+ no_auto_help
+ no_auto_version
+ no_ignore_case
+ );
+ Getopt::Long::Configure( @standard, @opts );
+
+ return;
+}
+
+
+sub _generate_ignore_dir {
+ my ( $option_name, $opt ) = @_;
+
+ my $is_inverted = $option_name =~ /^--no/;
+
+ return sub {
+ my ( undef, $dir ) = @_;
+
+ $dir = _remove_directory_separator( $dir );
+ if ( $dir !~ /:/ ) {
+ $dir = 'is:' . $dir;
+ }
+
+ my ( $filter_type, $args ) = split /:/, $dir, 2;
+
+ if ( $filter_type eq 'firstlinematch' ) {
+ App::Ack::die( qq{Invalid filter specification "$filter_type" for option '$option_name'} );
+ }
+
+ my $filter = App::Ack::Filter->create_filter($filter_type, split(/,/, $args));
+ my $collection;
+
+ my $previous_inversion_matches = $opt->{idirs} && !($is_inverted xor $opt->{idirs}[-1]->is_inverted());
+
+ if ( $previous_inversion_matches ) {
+ $collection = $opt->{idirs}[-1];
+
+ if ( $is_inverted ) {
+ # This relies on invert of an inverted filter to return the original.
+ $collection = $collection->invert();
+ }
+ }
+ else {
+ $collection = App::Ack::Filter::Collection->new();
+ push @{ $opt->{idirs} }, $is_inverted ? $collection->invert() : $collection;
+ }
+
+ $collection->add($filter);
+
+ if ( $filter_type eq 'is' ) {
+ $collection->add(App::Ack::Filter::IsPath->new($args));
+ }
+ };
+}
+
+
+sub _remove_directory_separator {
+ my $path = shift;
+
+ state $dir_sep_chars = $App::Ack::is_windows ? quotemeta( '\\/' ) : quotemeta( File::Spec->catfile( '', '' ) );
+
+ $path =~ s/[$dir_sep_chars]$//;
+
+ return $path;
+}
+
+
+sub _process_filter_spec {
+ my ( $spec ) = @_;
+
+ if ( $spec =~ /^(\w+):(\w+):(.*)/ ) {
+ my ( $type_name, $ext_type, $arguments ) = ( $1, $2, $3 );
+
+ return ( $type_name,
+ App::Ack::Filter->create_filter($ext_type, split(/,/, $arguments)) );
+ }
+ elsif ( $spec =~ /^(\w+)=(.*)/ ) { # Check to see if we have ack1-style argument specification.
+ my ( $type_name, $extensions ) = ( $1, $2 );
+
+ my @extensions = split(/,/, $extensions);
+ foreach my $extension ( @extensions ) {
+ $extension =~ s/^[.]//;
+ }
+
+ return ( $type_name, App::Ack::Filter->create_filter('ext', @extensions) );
+ }
+ else {
+ App::Ack::die( "Invalid filter specification '$spec'" );
+ }
+}
+
+
+sub _uninvert_filter {
+ my ( $opt, @filters ) = @_;
+
+ return unless defined $opt->{filters} && @filters;
+
+ # Loop through all the registered filters. If we hit one that
+ # matches this extension and it's inverted, we need to delete it from
+ # the options.
+ for ( my $i = 0; $i < @{ $opt->{filters} }; $i++ ) {
+ my $opt_filter = @{ $opt->{filters} }[$i];
+
+ # XXX Do a real list comparison? This just checks string equivalence.
+ if ( $opt_filter->is_inverted() && "$opt_filter->{filter}" eq "@filters" ) {
+ splice @{ $opt->{filters} }, $i, 1;
+ $i--;
+ }
+ }
-=head2 Can I do multi-line regexes?
+ return;
+}
-No, ack does not support regexes that match multiple lines. Doing
-so would require reading in the entire file at a time.
-If you want to see lines near your match, use the C<--A>, C<--B>
-and C<--C> switches for displaying context.
+sub _process_filetypes {
+ my ( $opt, $arg_sources ) = @_;
-=head1 AUTHOR
+ my %additional_specs;
-Andy Lester, C<< <andy at petdance.com> >>
+ my $add_spec = sub {
+ my ( undef, $spec ) = @_;
-=head1 BUGS
+ my ( $name, $filter ) = _process_filter_spec($spec);
-Please report any bugs or feature requests to the issues list at
-Github: L<http://github.com/petdance/ack/issues>
+ push @{ $App::Ack::mappings{$name} }, $filter;
-=head1 ENHANCEMENTS
+ $additional_specs{$name . '!'} = sub {
+ my ( undef, $value ) = @_;
-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.
+ my @filters = @{ $App::Ack::mappings{$name} };
+ if ( not $value ) {
+ @filters = map { $_->invert() } @filters;
+ }
+ else {
+ _uninvert_filter( $opt, @filters );
+ }
-There is a list of enhancements I want to make to F<ack> in the ack
-issues list at Github: L<http://github.com/petdance/ack/issues>
+ push @{ $opt->{'filters'} }, @filters;
+ };
+ };
-Patches are always welcome, but patches with tests get the most
-attention.
+ my $set_spec = sub {
+ my ( undef, $spec ) = @_;
-=head1 SUPPORT
+ my ( $name, $filter ) = _process_filter_spec($spec);
-Support for and information about F<ack> can be found at:
+ $App::Ack::mappings{$name} = [ $filter ];
-=over 4
+ $additional_specs{$name . '!'} = sub {
+ my ( undef, $value ) = @_;
-=item * The ack homepage
+ my @filters = @{ $App::Ack::mappings{$name} };
+ if ( not $value ) {
+ @filters = map { $_->invert() } @filters;
+ }
-L<http://betterthangrep.com/>
+ push @{ $opt->{'filters'} }, @filters;
+ };
+ };
-=item * The ack issues list at Github
+ my $delete_spec = sub {
+ my ( undef, $name ) = @_;
-L<http://github.com/petdance/ack/issues>
+ delete $App::Ack::mappings{$name};
+ delete $additional_specs{$name . '!'};
+ };
-=item * AnnoCPAN: Annotated CPAN documentation
+ my %type_arg_specs = (
+ 'type-add=s' => $add_spec,
+ 'type-set=s' => $set_spec,
+ 'type-del=s' => $delete_spec,
+ );
-L<http://annocpan.org/dist/ack>
+ configure_parser( 'no_auto_abbrev', 'pass_through' );
+ foreach my $source (@{$arg_sources}) {
+ my $args = $source->{contents};
-=item * CPAN Ratings
+ if ( ref($args) ) {
+ # $args are modified in place, so no need to munge $arg_sources
+ Getopt::Long::GetOptionsFromArray( $args, %type_arg_specs );
+ }
+ else {
+ ( undef, $source->{contents} ) =
+ Getopt::Long::GetOptionsFromString( $args, %type_arg_specs );
+ }
+ }
-L<http://cpanratings.perl.org/d/ack>
+ $additional_specs{'k|known-types'} = sub {
+ my @filters = map { @{$_} } values(%App::Ack::mappings);
-=item * Search CPAN
+ push @{ $opt->{'filters'} }, @filters;
+ };
-L<http://search.cpan.org/dist/ack>
+ return \%additional_specs;
+}
-=item * Git source repository
-L<http://github.com/petdance/ack>
+sub get_arg_spec {
+ my ( $opt, $extra_specs ) = @_;
-=back
-=head1 ACKNOWLEDGEMENTS
+ sub _type_handler {
+ my ( $getopt, $value ) = @_;
-How appropriate to have I<ack>nowledgements!
+ my $cb_value = 1;
+ if ( $value =~ s/^no// ) {
+ $cb_value = 0;
+ }
-Thanks to everyone who has contributed to ack in any way, including
-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
-and Pete Krawczyk.
+ my $callback;
+ {
+ no warnings;
+ $callback = $extra_specs->{ $value . '!' };
+ }
-=head1 COPYRIGHT & LICENSE
+ if ( $callback ) {
+ $callback->( $getopt, $cb_value );
+ }
+ else {
+ App::Ack::die( "Unknown type '$value'" );
+ }
-Copyright 2005-2011 Andy Lester.
+ return;
+ }
-This program is free software; you can redistribute it and/or modify
-it under the terms of the Artistic License v2.0.
+ $opt->{and} = [];
+ $opt->{or} = [];
+ $opt->{not} = [];
+
+ return {
+ 1 => sub { $opt->{1} = $opt->{m} = 1 },
+ 'and=s' => $opt->{and},
+ 'A|after-context:-1' => sub { shift; $opt->{A} = _context_value(shift) },
+ 'B|before-context:-1' => sub { shift; $opt->{B} = _context_value(shift) },
+ 'C|context:-1' => sub { shift; $opt->{B} = $opt->{A} = _context_value(shift) },
+ 'break!' => \$opt->{break},
+ 'c|count' => \$opt->{c},
+ 'color|colour!' => \$opt->{color},
+ 'color-match=s' => \$ENV{ACK_COLOR_MATCH},
+ 'color-filename=s' => \$ENV{ACK_COLOR_FILENAME},
+ 'color-colno=s' => \$ENV{ACK_COLOR_COLNO},
+ 'color-lineno=s' => \$ENV{ACK_COLOR_LINENO},
+ 'column!' => \$opt->{column},
+ 'create-ackrc' => sub { say for ( '--ignore-ack-defaults', App::Ack::ConfigDefault::options() ); exit; },
+ 'debug' => \$opt->{debug},
+ '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 => sub { $| = 1 },
+ 'follow!' => \$opt->{follow},
+ g => \$opt->{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' => sub { $opt->{i} = 1; $opt->{S} = 0; },
+ 'I|no-ignore-case' => sub { $opt->{i} = 0; $opt->{S} = 0; },
+ 'ignore-directory|ignore-dir=s' => _generate_ignore_dir('--ignore-dir', $opt),
+ 'ignore-file=s' => sub {
+ my ( undef, $file ) = @_;
+
+ my ( $filter_type, $args ) = split /:/, $file, 2;
+
+ my $filter = App::Ack::Filter->create_filter($filter_type, split(/,/, $args//''));
+
+ if ( !$opt->{ifiles} ) {
+ $opt->{ifiles} = App::Ack::Filter::Collection->new();
+ }
+ $opt->{ifiles}->add($filter);
+ },
+ 'l|files-with-matches'
+ => \$opt->{l},
+ 'L|files-without-matches'
+ => \$opt->{L},
+ 'm|max-count=i' => \$opt->{m},
+ 'match=s' => \$opt->{regex},
+ 'n|no-recurse' => \$opt->{n},
+ o => sub { $opt->{output} = '$&' },
+ 'output=s' => \$opt->{output},
+ 'pager:s' => sub {
+ my ( undef, $value ) = @_;
+
+ $opt->{pager} = $value || $ENV{PAGER};
+ },
+ 'noignore-directory|noignore-dir=s' => _generate_ignore_dir('--noignore-dir', $opt),
+ 'nopager' => sub { $opt->{pager} = undef },
+ 'not=s' => $opt->{not},
+ 'or=s' => $opt->{or},
+ 'passthru' => \$opt->{passthru},
+ 'print0' => \$opt->{print0},
+ 'p|proximate:1' => \$opt->{p},
+ 'P' => sub { $opt->{p} = 0 },
+ 'Q|literal' => \$opt->{Q},
+ 'r|R|recurse' => sub { $opt->{n} = 0 },
+ 'range-start=s' => \$opt->{range_start},
+ 'range-end=s' => \$opt->{range_end},
+ 'range-invert!' => \$opt->{range_invert},
+ 's' => \$opt->{s},
+ 'show-types' => \$opt->{show_types},
+ 'S|smart-case!' => sub { my (undef,$value) = @_; $opt->{S} = $value; $opt->{i} = 0 if $value; },
+ 'sort-files' => \$opt->{sort_files},
+ 't|type=s' => \&_type_handler,
+ 'T=s' => sub { my ($getopt,$value) = @_; $value="no$value"; _type_handler($getopt,$value); },
+ 'underline!' => \$opt->{underline},
+ 'v|invert-match' => \$opt->{v},
+ 'w|word-regexp' => \$opt->{w},
+ 'x' => sub { $opt->{files_from} = '-' },
+
+ 'help' => sub { App::Ack::show_help(); exit; },
+ 'help-types' => sub { App::Ack::show_help_types(); exit; },
+ 'help-colors' => sub { App::Ack::show_help_colors(); exit; },
+ 'help-rgb-colors' => sub { App::Ack::show_help_rgb(); exit; },
+ $extra_specs ? %{$extra_specs} : (),
+ }; # arg_specs
+}
-=cut
-package File::Next;
-use strict;
-use warnings;
+sub _context_value {
+ my $val = shift;
+ # Contexts default to 2.
+ return (!defined($val) || ($val < 0)) ? 2 : $val;
+}
-our $VERSION = '1.06';
+sub _process_other {
+ my ( $opt, $extra_specs, $arg_sources ) = @_;
+ my $argv_source;
+ my $is_help_types_active;
-use File::Spec ();
+ foreach my $source (@{$arg_sources}) {
+ if ( $source->{name} eq 'ARGV' ) {
+ $argv_source = $source->{contents};
+ last;
+ }
+ }
+ if ( $argv_source ) { # This *should* always be true, but you never know...
+ configure_parser( 'pass_through' );
+ Getopt::Long::GetOptionsFromArray( [ @{$argv_source} ],
+ 'help-types' => \$is_help_types_active,
+ );
+ }
-our $name; # name of the current file
-our $dir; # dir of the current file
+ my $arg_specs = get_arg_spec( $opt, $extra_specs );
-our %files_defaults;
-our %skip_dirs;
+ configure_parser();
+ foreach my $source (@{$arg_sources}) {
+ my ( $source_name, $args ) = @{$source}{qw/name contents/};
-BEGIN {
- %files_defaults = (
- file_filter => undef,
- descend_filter => undef,
- error_handler => sub { CORE::die @_ },
- sort_files => undef,
- follow_symlinks => 1,
- );
- %skip_dirs = map {($_,1)} (File::Spec->curdir, File::Spec->updir);
-}
+ my $args_for_source = { %{$arg_specs} };
+ if ( $source->{is_ackrc} ) {
+ my $illegal = sub {
+ my $name = shift;
+ App::Ack::die( "Option --$name is forbidden in .ackrc files." );
+ };
-sub files {
- ($_[0] eq __PACKAGE__) && die 'File::Next::files must not be invoked as File::Next->files';
+ $args_for_source = {
+ %{$args_for_source},
+ 'output=s' => $illegal,
+ 'match=s' => $illegal,
+ };
+ }
+ if ( $source->{project} ) {
+ my $illegal = sub {
+ my $name = shift;
+ App::Ack::die( "Option --$name is forbidden in project .ackrc files." );
+ };
- my ($parms,@queue) = _setup( \%files_defaults, @_ );
- my $filter = $parms->{file_filter};
+ $args_for_source = {
+ %{$args_for_source},
+ 'pager:s' => $illegal,
+ };
+ }
- return sub {
- while (@queue) {
- my ($dir,$file,$fullpath) = splice( @queue, 0, 3 );
- if ( -f $fullpath ) {
- if ( $filter ) {
- local $_ = $file;
- local $File::Next::dir = $dir;
- local $File::Next::name = $fullpath;
- next if not $filter->();
- }
- return wantarray ? ($dir,$file,$fullpath) : $fullpath;
- }
- elsif ( -d _ ) {
- unshift( @queue, _candidate_files( $parms, $fullpath ) );
+ my $ret;
+ if ( ref($args) ) {
+ $ret = Getopt::Long::GetOptionsFromArray( $args, %{$args_for_source} );
+ }
+ else {
+ ( $ret, $source->{contents} ) =
+ Getopt::Long::GetOptionsFromString( $args, %{$args_for_source} );
+ }
+ if ( !$ret ) {
+ if ( !$is_help_types_active ) {
+ my $where = $source_name eq 'ARGV' ? 'on command line' : "in $source_name";
+ App::Ack::die( "Invalid option $where" );
}
- } # while
+ }
+ if ( $opt->{noenv_seen} ) {
+ App::Ack::die( "--noenv found in $source_name" );
+ }
+ }
- return;
- }; # iterator
+ # XXX We need to check on a -- in the middle of a non-ARGV source
+
+ return;
}
+sub _explode_sources {
+ my ( $sources ) = @_;
+ my @new_sources;
+ my %opt;
+ my $arg_spec = get_arg_spec( \%opt, {} );
+ my $dummy_sub = sub {};
+ my $add_type = sub {
+ my ( undef, $arg ) = @_;
+ if ( $arg =~ /(\w+)=/) {
+ $arg_spec->{$1} = $dummy_sub;
+ }
+ else {
+ ( $arg ) = split /:/, $arg;
+ $arg_spec->{$arg} = $dummy_sub;
+ }
+ };
-sub sort_standard($$) { return $_[0]->[1] cmp $_[1]->[1] }
-sub sort_reverse($$) { return $_[1]->[1] cmp $_[0]->[1] }
+ my $del_type = sub {
+ my ( undef, $arg ) = @_;
-sub reslash {
- my $path = shift;
+ delete $arg_spec->{$arg};
+ };
- my @parts = split( /\//, $path );
+ configure_parser( 'pass_through' );
+ foreach my $source (@{$sources}) {
+ my ( $name, $options ) = @{$source}{qw/name contents/};
+ if ( ref($options) ne 'ARRAY' ) {
+ $source->{contents} = $options =
+ [ Text::ParseWords::shellwords($options) ];
+ }
- return $path if @parts < 2;
+ for my $j ( 0 .. @{$options}-1 ) {
+ next unless $options->[$j] =~ /^-/;
+ my @chunk = ( $options->[$j] );
+ push @chunk, $options->[$j] while ++$j < @{$options} && $options->[$j] !~ /^-/;
+ $j--;
+
+ my @copy = @chunk;
+ Getopt::Long::GetOptionsFromArray( [@chunk],
+ 'type-add=s' => $add_type,
+ 'type-set=s' => $add_type,
+ 'type-del=s' => $del_type,
+ %{$arg_spec}
+ );
+
+ push @new_sources, {
+ name => $name,
+ contents => \@copy,
+ };
+ }
+ }
- return File::Spec->catfile( @parts );
+ return \@new_sources;
}
+sub _compare_opts {
+ my ( $a, $b ) = @_;
-sub _setup {
- my $defaults = shift;
- my $passed_parms = ref $_[0] eq 'HASH' ? {%{+shift}} : {}; # copy parm hash
+ my $first_a = $a->[0];
+ my $first_b = $b->[0];
- my %passed_parms = %{$passed_parms};
+ $first_a =~ s/^--?//;
+ $first_b =~ s/^--?//;
- my $parms = {};
- for my $key ( keys %{$defaults} ) {
- $parms->{$key} =
- exists $passed_parms{$key}
- ? delete $passed_parms{$key}
- : $defaults->{$key};
+ return $first_a cmp $first_b;
+}
+
+
+sub _dump_options {
+ my ( $sources ) = @_;
+
+ $sources = _explode_sources($sources);
+
+ my %opts_by_source;
+ my @source_names;
+
+ foreach my $source (@{$sources}) {
+ my $name = $source->{name};
+ if ( not $opts_by_source{$name} ) {
+ $opts_by_source{$name} = [];
+ push @source_names, $name;
+ }
+ push @{$opts_by_source{$name}}, $source->{contents};
}
- # 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" );
+ foreach my $name (@source_names) {
+ my $contents = $opts_by_source{$name};
+
+ say $name;
+ say '=' x length($name);
+ say ' ', join(' ', @{$_}) for sort { _compare_opts($a, $b) } @{$contents};
}
- # 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;
+ return;
+}
+
+
+sub _remove_default_options_if_needed {
+ my ( $sources ) = @_;
+
+ my $default_index;
+
+ foreach my $index ( 0 .. $#{$sources} ) {
+ if ( $sources->[$index]{'name'} eq 'Defaults' ) {
+ $default_index = $index;
+ last;
+ }
}
- my @queue;
- for ( @_ ) {
- my $start = reslash( $_ );
- if (-d $start) {
- push @queue, ($start,undef,$start);
+ return $sources unless defined $default_index;
+
+ my $should_remove = 0;
+
+ configure_parser( 'no_auto_abbrev', 'pass_through' );
+
+ foreach my $index ( $default_index + 1 .. $#{$sources} ) {
+ my $args = $sources->[$index]->{contents};
+
+ if (ref($args)) {
+ Getopt::Long::GetOptionsFromArray( $args,
+ 'ignore-ack-defaults' => \$should_remove,
+ );
}
else {
- push @queue, (undef,$start,$start);
+ ( undef, $sources->[$index]{contents} ) = Getopt::Long::GetOptionsFromString( $args,
+ 'ignore-ack-defaults' => \$should_remove,
+ );
}
}
- return ($parms,@queue);
+ return $sources unless $should_remove;
+
+ my @copy = @{$sources};
+ splice @copy, $default_index, 1;
+ return \@copy;
}
-sub _candidate_files {
- my $parms = shift;
- my $dir = shift;
+sub process_args {
+ my $arg_sources = \@_;
- my $dh;
- if ( !opendir $dh, $dir ) {
- $parms->{error_handler}->( "$dir: $!" );
- return;
+ my %opt = (
+ pager => $ENV{ACK_PAGER_COLOR} || $ENV{ACK_PAGER},
+ );
+
+ $arg_sources = _remove_default_options_if_needed($arg_sources);
+
+ # Check for --dump early.
+ foreach my $source (@{$arg_sources}) {
+ if ( $source->{name} eq 'ARGV' ) {
+ my $dump;
+ configure_parser( 'pass_through' );
+ Getopt::Long::GetOptionsFromArray( $source->{contents},
+ 'dump' => \$dump,
+ );
+ if ( $dump ) {
+ _dump_options($arg_sources);
+ exit(0);
+ }
+ }
}
- my @newfiles;
- my $descend_filter = $parms->{descend_filter};
- my $follow_symlinks = $parms->{follow_symlinks};
- my $sort_sub = $parms->{sort_files};
+ my $type_specs = _process_filetypes(\%opt, $arg_sources);
- for my $file ( grep { !exists $skip_dirs{$_} } readdir $dh ) {
- my $has_stat;
+ _check_for_mutex_options( $type_specs );
- # Only do directory checking if we have a descend_filter
- my $fullpath = File::Spec->catdir( $dir, $file );
- if ( !$follow_symlinks ) {
- next if -l $fullpath;
- $has_stat = 1;
- }
+ _process_other(\%opt, $type_specs, $arg_sources);
+ while ( @{$arg_sources} ) {
+ my $source = shift @{$arg_sources};
+ my $args = $source->{contents};
- if ( $descend_filter ) {
- if ( $has_stat ? (-d _) : (-d $fullpath) ) {
- local $File::Next::dir = $fullpath;
- local $_ = $file;
- next if not $descend_filter->();
+ # All of our sources should be transformed into an array ref
+ if ( ref($args) ) {
+ my $source_name = $source->{name};
+ if ( $source_name eq 'ARGV' ) {
+ @ARGV = @{$args};
+ }
+ elsif (@{$args}) {
+ App::Ack::die( "Source '$source_name' has extra arguments!" );
}
}
- if ( $sort_sub ) {
- push( @newfiles, [ $dir, $file, $fullpath ] );
+ else {
+ App::Ack::die( 'The impossible has occurred!' );
+ }
+ }
+ my $filters = ($opt{filters} ||= []);
+
+ # Throw the default filter in if no others are selected.
+ if ( not grep { !$_->is_inverted() } @{$filters} ) {
+ push @{$filters}, App::Ack::Filter::Default->new();
+ }
+ return \%opt;
+}
+
+
+sub retrieve_arg_sources {
+ my @arg_sources;
+
+ my $noenv;
+ my $ackrc;
+
+ configure_parser( 'no_auto_abbrev', 'pass_through' );
+ Getopt::Long::GetOptions(
+ 'noenv' => \$noenv,
+ 'ackrc=s' => \$ackrc,
+ );
+
+ my @files;
+
+ if ( !$noenv ) {
+ my $finder = App::Ack::ConfigFinder->new;
+ @files = $finder->find_config_files;
+ }
+ if ( $ackrc ) {
+ # We explicitly use open so we get a nice error message.
+ # XXX This is a potential race condition!.
+ if ( open my $fh, '<', $ackrc ) {
+ close $fh;
}
else {
- push( @newfiles, $dir, $file, $fullpath );
+ App::Ack::die( "Unable to load ackrc '$ackrc': $!" );
}
+ push( @files, { path => $ackrc } );
}
- closedir $dh;
- if ( $sort_sub ) {
- return map { @{$_} } sort $sort_sub @newfiles;
+ push @arg_sources, {
+ name => 'Defaults',
+ contents => [ App::Ack::ConfigDefault::options_clean() ],
+ };
+
+ foreach my $file ( @files) {
+ my @lines = read_rcfile($file->{path});
+ if ( @lines ) {
+ push @arg_sources, {
+ name => $file->{path},
+ contents => \@lines,
+ project => $file->{project},
+ is_ackrc => 1,
+ };
+ }
}
- return @newfiles;
-}
+ push @arg_sources, {
+ name => 'ARGV',
+ contents => [ @ARGV ],
+ };
+ return @arg_sources;
+}
-1; # End of File::Next
-package App::Ack;
-use warnings;
-use strict;
+sub read_rcfile {
+ my $file = shift;
+ return unless defined $file && -e $file;
+ my @lines;
+ open( my $fh, '<', $file ) or App::Ack::die( "Unable to read $file: $!" );
+ while ( defined( my $line = <$fh> ) ) {
+ chomp $line;
+ $line =~ s/^\s+//;
+ $line =~ s/\s+$//;
-our $VERSION;
-our $COPYRIGHT;
-BEGIN {
- $VERSION = '1.96';
- $COPYRIGHT = 'Copyright 2005-2011 Andy Lester.';
-}
+ next if $line eq '';
+ next if $line =~ /^\s*#/;
-our $fh;
+ push( @lines, $line );
+ }
+ close $fh or App::Ack::die( "Unable to close $file: $!" );
-BEGIN {
- $fh = *STDOUT;
+ return @lines;
}
-our %types;
-our %type_wanted;
-our %mappings;
-our %ignore_dirs;
-
-our $input_from_pipe;
-our $output_to_pipe;
-
-our $dir_sep_chars;
-our $is_cygwin;
-our $is_windows;
+# Verifies no mutex options were passed. Dies if they were.
+sub _check_for_mutex_options {
+ my $type_specs = shift;
-use File::Spec ();
-use File::Glob ':glob';
-use Getopt::Long ();
+ my $mutex = mutex_options();
-BEGIN {
- %ignore_dirs = (
- '.bzr' => 'Bazaar',
- '.cdv' => 'Codeville',
- '~.dep' => 'Interface Builder',
- '~.dot' => 'Interface Builder',
- '~.nib' => 'Interface Builder',
- '~.plst' => 'Interface Builder',
- '.git' => 'Git',
- '.hg' => 'Mercurial',
- '.pc' => 'quilt',
- '.svn' => 'Subversion',
- _MTN => 'Monotone',
- blib => 'Perl module building',
- CVS => 'CVS',
- RCS => 'RCS',
- SCCS => 'SCCS',
- _darcs => 'darcs',
- _sgbak => 'Vault/Fortress',
- 'autom4te.cache' => 'autoconf',
- 'cover_db' => 'Devel::Cover',
- _build => 'Module::Build',
- );
+ my ($raw,$used) = _options_used( $type_specs );
- %mappings = (
- actionscript => [qw( as mxml )],
- ada => [qw( ada adb ads )],
- asm => [qw( asm s )],
- batch => [qw( bat cmd )],
- binary => q{Binary files, as defined by Perl's -B op (default: off)},
- cc => [qw( c h xs )],
- cfmx => [qw( cfc cfm cfml )],
- clojure => [qw( clj )],
- cpp => [qw( cpp cc cxx m hpp hh h hxx )],
- csharp => [qw( cs )],
- css => [qw( css )],
- delphi => [qw( pas int dfm nfm dof dpk dproj groupproj bdsgroup bdsproj )],
- elisp => [qw( el )],
- erlang => [qw( erl hrl )],
- fortran => [qw( f f77 f90 f95 f03 for ftn fpp )],
- go => [qw( go )],
- groovy => [qw( groovy gtmpl gpp grunit )],
- haskell => [qw( hs lhs )],
- hh => [qw( h )],
- html => [qw( htm html shtml xhtml )],
- java => [qw( java properties )],
- js => [qw( js )],
- jsp => [qw( jsp jspx jhtm jhtml )],
- lisp => [qw( lisp lsp )],
- lua => [qw( lua )],
- make => q{Makefiles (including *.mk and *.mak)},
- mason => [qw( mas mhtml mpl mtxt )],
- objc => [qw( m h )],
- objcpp => [qw( mm h )],
- ocaml => [qw( ml mli )],
- parrot => [qw( pir pasm pmc ops pod pg tg )],
- perl => [qw( pl pm pm6 pod t )],
- php => [qw( php phpt php3 php4 php5 phtml)],
- plone => [qw( pt cpt metadata cpy py )],
- python => [qw( py )],
- rake => q{Rakefiles},
- ruby => [qw( rb rhtml rjs rxml erb rake spec )],
- scala => [qw( scala )],
- scheme => [qw( scm ss )],
- shell => [qw( sh bash csh tcsh ksh zsh )],
- skipped => q{Files, but not directories, normally skipped by ack (default: off)},
- smalltalk => [qw( st )],
- sql => [qw( sql ctl )],
- tcl => [qw( tcl itcl itk )],
- tex => [qw( tex cls sty )],
- text => q{Text files, as defined by Perl's -T op (default: off)},
- tt => [qw( tt tt2 ttml )],
- vb => [qw( bas cls frm ctl vb resx )],
- verilog => [qw( v vh sv )],
- vhdl => [qw( vhd vhdl )],
- vim => [qw( vim )],
- yaml => [qw( yaml yml )],
- xml => [qw( xml dtd xsl xslt ent )],
- );
+ my @used = sort { lc $a cmp lc $b } keys %{$used};
- while ( my ($type,$exts) = each %mappings ) {
- if ( ref $exts ) {
- for my $ext ( @{$exts} ) {
- push( @{$types{$ext}}, $type );
+ for my $i ( @used ) {
+ for my $j ( @used ) {
+ next if $i eq $j;
+ if ( $mutex->{$i}{$j} ) {
+ my $x = $raw->[ $used->{$i} ];
+ my $y = $raw->[ $used->{$j} ];
+ App::Ack::die( "Options '$x' and '$y' can't be used together." );
}
}
}
- # add manually Makefile extensions
- push @{$types{$_}}, 'make' for qw{ mk mak };
-
- # These have to be checked before any filehandle diddling.
- $output_to_pipe = not -t *STDOUT;
- $input_from_pipe = -p STDIN;
- $is_cygwin = ($^O eq 'cygwin');
- $is_windows = ($^O =~ /MSWin32/);
- $dir_sep_chars = $is_windows ? quotemeta( '\\/' ) : quotemeta( File::Spec->catfile( '', '' ) );
+ return;
}
-sub read_ackrc {
- my @files = ( $ENV{ACKRC} );
- my @dirs =
- $is_windows
- ? ( $ENV{HOME}, $ENV{USERPROFILE} )
- : ( '~', $ENV{HOME} );
- for my $dir ( grep { defined } @dirs ) {
- for my $file ( '.ackrc', '_ackrc' ) {
- push( @files, bsd_glob( "$dir/$file", GLOB_TILDE ) );
- }
+# Processes the command line option and returns a hash of the options that were
+# used on the command line, using their full name. "--prox" shows up in the hash as "--proximate".
+sub _options_used {
+ my $type_specs = shift;
+
+ my %dummy_opt;
+ my $real_spec = get_arg_spec( \%dummy_opt, $type_specs );
+
+ # The real argument parsing doesn't check for --type-add, --type-del or --type-set because
+ # they get removed by the argument processing. We have to account for them here.
+ my $sub_dummy = sub {};
+ $real_spec = {
+ %{$real_spec},
+ 'type-add=s' => $sub_dummy,
+ 'type-del=s' => $sub_dummy,
+ 'type-set=s' => $sub_dummy,
+ 'ignore-ack-defaults' => $sub_dummy,
+ };
+
+ my %parsed;
+ my @raw;
+ my %spec_capture_parsed;
+ my %spec_capture_raw;
+
+
+ # Capture the %parsed hash.
+ CAPTURE_PARSED: {
+ my $parsed_pos = 0;
+ my $sub_count = sub {
+ my $arg = shift;
+ $arg = "$arg";
+ $parsed{$arg} = $parsed_pos++;
+ };
+ %spec_capture_parsed = (
+ '<>' => sub { $parsed_pos++ }, # Bump forward one pos for non-options.
+ map { $_ => $sub_count } keys %{$real_spec}
+ );
}
- for my $filename ( @files ) {
- if ( defined $filename && -e $filename ) {
- open( my $fh, '<', $filename ) or App::Ack::die( "$filename: $!\n" );
- my @lines = grep { /./ && !/^\s*#/ } <$fh>;
- chomp @lines;
- close $fh or App::Ack::die( "$filename: $!\n" );
- # get rid of leading and trailing whitespaces
- for ( @lines ) {
- s/^\s+//;
- s/\s+$//;
+ # Capture the @raw array.
+ CAPTURE_RAW: {
+ my $raw_pos = 0;
+ %spec_capture_raw = (
+ '<>' => sub { $raw_pos++ }, # Bump forward one pos for non-options.
+ );
+
+ my $sub_count = sub {
+ my $arg = shift;
+
+ $arg = "$arg";
+ $raw[$raw_pos] = length($arg) == 1 ? "-$arg" : "--$arg";
+ $raw_pos++;
+ };
+
+ for my $opt_spec ( keys %{$real_spec} ) {
+ my $negatable;
+ my $type;
+ my $default;
+
+ $negatable = ($opt_spec =~ s/!$//);
+
+ if ( $opt_spec =~ s/(=[si])$// ) {
+ $type = $1;
}
+ if ( $opt_spec =~ s/(:.+)$// ) {
+ $default = $1;
+ }
+
+ my @aliases = split( /\|/, $opt_spec );
+ for my $alias ( @aliases ) {
+ $alias .= $type if defined $type;
+ $alias .= $default if defined $default;
+ $alias .= '!' if $negatable;
- return @lines;
+ $spec_capture_raw{$alias} = $sub_count;
+ }
}
}
- return;
+ # Parse @ARGV twice, once with each capture spec.
+ configure_parser( 'pass_through' ); # Ignore invalid options.
+ Getopt::Long::GetOptionsFromArray( [@ARGV], %spec_capture_raw );
+ Getopt::Long::GetOptionsFromArray( [@ARGV], %spec_capture_parsed );
+
+ return (\@raw,\%parsed);
}
-sub get_command_line_options {
- my %opt = (
- pager => $ENV{ACK_PAGER_COLOR} || $ENV{ACK_PAGER},
- );
+sub mutex_options {
+ # This list is machine-generated by dev/crank-mutex. Do not modify it by hand.
- my $getopt_specs = {
- 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|all-types' => \$opt{all},
- '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},
- 'env!' => sub { }, # ignore this option, it is handled beforehand
- f => \$opt{f},
- flush => \$opt{flush},
- 'follow!' => \$opt{follow},
- 'g=s' => sub { shift; $opt{G} = shift; $opt{f} = 1 },
- 'G=s' => \$opt{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},
- 'invert-file-match' => \$opt{invert_file_match},
- 'lines=s' => sub { shift; my $val = shift; push @{$opt{lines}}, $val },
- 'l|files-with-matches' => \$opt{l},
- 'L|files-without-matches' => sub { $opt{l} = $opt{v} = 1 },
- '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},
- 'nopager' => sub { $opt{pager} = undef },
- 'passthru' => \$opt{passthru},
- 'print0' => \$opt{print0},
- 'Q|literal' => \$opt{Q},
- 'r|R|recurse' => sub { $opt{n} = 0 },
- 'show-types' => \$opt{show_types},
- 'smart-case!' => \$opt{smart_case},
- 'sort-files' => \$opt{sort_files},
- 'u|unrestricted' => \$opt{u},
- 'v|invert-match' => \$opt{v},
- 'w|word-regexp' => \$opt{w},
-
- 'ignore-dirs=s' => sub { shift; my $dir = remove_dir_sep( shift ); $ignore_dirs{$dir} = '--ignore-dirs' },
- 'noignore-dirs=s' => sub { shift; my $dir = remove_dir_sep( shift ); delete $ignore_dirs{$dir} },
-
- 'version' => sub { print_version_statement(); exit; },
- 'help|?:s' => sub { shift; show_help(@_); exit; },
- 'help-types'=> sub { show_help_types(); exit; },
- 'man' => sub {
- require Pod::Usage;
- Pod::Usage::pod2usage({
- -verbose => 2,
- -exitval => 0,
- });
+ return {
+ 1 => {
+ m => 1,
+ passthru => 1,
+ },
+ A => {
+ L => 1,
+ c => 1,
+ f => 1,
+ g => 1,
+ l => 1,
+ o => 1,
+ output => 1,
+ p => 1,
+ passthru => 1,
+ },
+ B => {
+ L => 1,
+ c => 1,
+ f => 1,
+ g => 1,
+ l => 1,
+ o => 1,
+ output => 1,
+ p => 1,
+ passthru => 1,
+ },
+ C => {
+ L => 1,
+ c => 1,
+ f => 1,
+ g => 1,
+ l => 1,
+ o => 1,
+ output => 1,
+ p => 1,
+ passthru => 1,
+ },
+ H => {
+ L => 1,
+ f => 1,
+ g => 1,
+ l => 1,
},
+ I => {
+ f => 1,
+ },
+ L => {
+ A => 1,
+ B => 1,
+ C => 1,
+ H => 1,
+ L => 1,
+ break => 1,
+ c => 1,
+ column => 1,
+ f => 1,
+ g => 1,
+ group => 1,
+ h => 1,
+ heading => 1,
+ l => 1,
+ 'no-filename' => 1,
+ o => 1,
+ output => 1,
+ p => 1,
+ passthru => 1,
+ 'show-types' => 1,
+ v => 1,
+ 'with-filename' => 1,
+ },
+ and => {
+ g => 1,
+ not => 1,
+ or => 1,
+ },
+ break => {
+ L => 1,
+ c => 1,
+ f => 1,
+ g => 1,
+ l => 1,
+ },
+ c => {
+ A => 1,
+ B => 1,
+ C => 1,
+ L => 1,
+ break => 1,
+ column => 1,
+ f => 1,
+ g => 1,
+ group => 1,
+ heading => 1,
+ m => 1,
+ o => 1,
+ output => 1,
+ p => 1,
+ passthru => 1,
+ },
+ column => {
+ L => 1,
+ c => 1,
+ f => 1,
+ g => 1,
+ l => 1,
+ o => 1,
+ output => 1,
+ passthru => 1,
+ v => 1,
+ },
+ f => {
+ A => 1,
+ B => 1,
+ C => 1,
+ H => 1,
+ I => 1,
+ L => 1,
+ break => 1,
+ c => 1,
+ column => 1,
+ f => 1,
+ 'files-from' => 1,
+ g => 1,
+ group => 1,
+ h => 1,
+ heading => 1,
+ i => 1,
+ l => 1,
+ m => 1,
+ match => 1,
+ o => 1,
+ output => 1,
+ p => 1,
+ passthru => 1,
+ 'smart-case' => 1,
+ u => 1,
+ v => 1,
+ x => 1,
+ },
+ 'files-from' => {
+ f => 1,
+ g => 1,
+ x => 1,
+ },
+ g => {
+ A => 1,
+ B => 1,
+ C => 1,
+ H => 1,
+ L => 1,
+ and => 1,
+ break => 1,
+ c => 1,
+ column => 1,
+ f => 1,
+ 'files-from' => 1,
+ g => 1,
+ group => 1,
+ h => 1,
+ heading => 1,
+ l => 1,
+ m => 1,
+ match => 1,
+ not => 1,
+ o => 1,
+ or => 1,
+ output => 1,
+ p => 1,
+ passthru => 1,
+ u => 1,
+ x => 1,
+ },
+ group => {
+ L => 1,
+ c => 1,
+ f => 1,
+ g => 1,
+ l => 1,
+ },
+ h => {
+ L => 1,
+ f => 1,
+ g => 1,
+ l => 1,
+ },
+ heading => {
+ L => 1,
+ c => 1,
+ f => 1,
+ g => 1,
+ l => 1,
+ },
+ i => {
+ f => 1,
+ },
+ l => {
+ A => 1,
+ B => 1,
+ C => 1,
+ H => 1,
+ L => 1,
+ break => 1,
+ column => 1,
+ f => 1,
+ g => 1,
+ group => 1,
+ h => 1,
+ heading => 1,
+ l => 1,
+ 'no-filename' => 1,
+ o => 1,
+ output => 1,
+ p => 1,
+ passthru => 1,
+ 'show-types' => 1,
+ 'with-filename' => 1,
+ },
+ m => {
+ 1 => 1,
+ c => 1,
+ f => 1,
+ g => 1,
+ passthru => 1,
+ },
+ match => {
+ f => 1,
+ g => 1,
+ },
+ 'no-filename' => {
+ L => 1,
+ l => 1,
+ },
+ not => {
+ and => 1,
+ g => 1,
+ },
+ o => {
+ A => 1,
+ B => 1,
+ C => 1,
+ L => 1,
+ c => 1,
+ column => 1,
+ f => 1,
+ g => 1,
+ l => 1,
+ o => 1,
+ output => 1,
+ p => 1,
+ passthru => 1,
+ 'show-types' => 1,
+ v => 1,
+ },
+ or => {
+ and => 1,
+ g => 1,
+ },
+ output => {
+ A => 1,
+ B => 1,
+ C => 1,
+ L => 1,
+ c => 1,
+ column => 1,
+ f => 1,
+ g => 1,
+ l => 1,
+ o => 1,
+ output => 1,
+ p => 1,
+ passthru => 1,
+ 'show-types' => 1,
+ u => 1,
+ v => 1,
+ },
+ p => {
+ A => 1,
+ B => 1,
+ C => 1,
+ L => 1,
+ c => 1,
+ f => 1,
+ g => 1,
+ l => 1,
+ o => 1,
+ output => 1,
+ p => 1,
+ passthru => 1,
+ },
+ passthru => {
+ 1 => 1,
+ A => 1,
+ B => 1,
+ C => 1,
+ L => 1,
+ c => 1,
+ column => 1,
+ f => 1,
+ g => 1,
+ l => 1,
+ m => 1,
+ o => 1,
+ output => 1,
+ p => 1,
+ v => 1,
+ },
+ 'show-types' => {
+ L => 1,
+ l => 1,
+ o => 1,
+ output => 1,
+ },
+ 'smart-case' => {
+ f => 1,
+ },
+ u => {
+ f => 1,
+ g => 1,
+ output => 1,
+ },
+ v => {
+ L => 1,
+ column => 1,
+ f => 1,
+ o => 1,
+ output => 1,
+ passthru => 1,
+ },
+ 'with-filename' => {
+ L => 1,
+ l => 1,
+ },
+ x => {
+ f => 1,
+ 'files-from' => 1,
+ g => 1,
+ },
+ };
- 'type=s' => sub {
- # Whatever --type=xxx they specify, set it manually in the hash
- my $dummy = shift;
- my $type = shift;
- my $wanted = ($type =~ s/^no//) ? 0 : 1; # must not be undef later
+} # End of mutex_options()
- if ( exists $type_wanted{ $type } ) {
- $type_wanted{ $type } = $wanted;
- }
- else {
- App::Ack::die( qq{Unknown --type "$type"} );
- }
- }, # type sub
- };
- # Stick any default switches at the beginning, so they can be overridden
- # by the command line switches.
- unshift @ARGV, split( ' ', $ENV{ACK_OPTIONS} ) if defined $ENV{ACK_OPTIONS};
+1; # End of App::Ack::ConfigLoader
+}
+{
+package App::Ack::File;
- # first pass through options, looking for type definitions
- def_types_from_ARGV();
+use warnings;
+use strict;
- for my $i ( filetypes_supported() ) {
- $getopt_specs->{ "$i!" } = \$type_wanted{ $i };
- }
+use File::Spec ();
- my $parser = Getopt::Long::Parser->new();
- $parser->configure( 'bundling', 'no_ignore_case', );
- $parser->getoptions( %{$getopt_specs} ) or
- App::Ack::die( 'See ack --help, ack --help-types or ack --man for options.' );
+sub new {
+ my $class = shift;
+ my $filename = shift;
- my $to_screen = not output_to_pipe();
- my %defaults = (
- all => 0,
- color => $to_screen,
- follow => 0,
- break => $to_screen,
- heading => $to_screen,
- before_context => 0,
- after_context => 0,
- );
- if ( $is_windows && $defaults{color} && not $ENV{ACK_PAGER_COLOR} ) {
- if ( $ENV{ACK_PAGER} || not eval { require Win32::Console::ANSI } ) {
- $defaults{color} = 0;
- }
- }
- if ( $to_screen && $ENV{ACK_PAGER_COLOR} ) {
- $defaults{color} = 1;
- }
+ my $self = bless {
+ filename => $filename,
+ fh => undef,
+ }, $class;
- while ( my ($key,$value) = each %defaults ) {
- if ( not defined $opt{$key} ) {
- $opt{$key} = $value;
- }
+ if ( $self->{filename} eq '-' ) {
+ $self->{fh} = *STDIN;
}
- if ( defined $opt{m} && $opt{m} <= 0 ) {
- App::Ack::die( '-m must be greater than zero' );
- }
+ return $self;
+}
- for ( qw( before_context after_context ) ) {
- if ( defined $opt{$_} && $opt{$_} < 0 ) {
- App::Ack::die( "--$_ may not be negative" );
- }
- }
- if ( defined( my $val = $opt{output} ) ) {
- $opt{output} = eval qq[ sub { "$val" } ];
- }
- if ( defined( my $l = $opt{lines} ) ) {
- # --line=1 --line=5 is equivalent to --line=1,5
- my @lines = split( /,/, join( ',', @{$l} ) );
- # --line=1-3 is equivalent to --line=1,2,3
- @lines = map {
- my @ret;
- if ( /-/ ) {
- my ($from, $to) = split /-/, $_;
- if ( $from > $to ) {
- App::Ack::warn( "ignoring --line=$from-$to" );
- @ret = ();
- }
- else {
- @ret = ( $from .. $to );
- }
- }
- else {
- @ret = ( $_ );
- };
- @ret
- } @lines;
+sub name {
+ return $_[0]->{filename};
+}
- if ( @lines ) {
- my %uniq;
- @uniq{ @lines } = ();
- $opt{lines} = [ sort { $a <=> $b } keys %uniq ]; # numerical sort and each line occurs only once!
+
+
+sub basename {
+ my ( $self ) = @_;
+
+ return $self->{basename} //= (File::Spec->splitpath($self->name))[2];
+}
+
+
+
+sub open {
+ my ( $self ) = @_;
+
+ if ( !$self->{fh} ) {
+ if ( open $self->{fh}, '<', $self->{filename} ) {
+ # Do nothing.
}
else {
- # happens if there are only ignored --line directives
- App::Ack::die( 'All --line options are invalid.' );
+ $self->{fh} = undef;
}
}
- return \%opt;
+ return $self->{fh};
}
-sub def_types_from_ARGV {
- my @typedef;
+sub may_be_present {
+ my $self = shift;
+ my $regex = shift;
- my $parser = Getopt::Long::Parser->new();
- # pass_through => leave unrecognized command line arguments alone
- # no_auto_abbrev => otherwise -c is expanded and not left alone
- $parser->configure( 'no_ignore_case', 'pass_through', 'no_auto_abbrev' );
- $parser->getoptions(
- 'type-set=s' => sub { shift; push @typedef, ['c', shift] },
- 'type-add=s' => sub { shift; push @typedef, ['a', shift] },
- ) or App::Ack::die( 'See ack --help or ack --man for options.' );
+ # Tells if the file needs a line-by-line scan. This is a big
+ # optimization because if you can tell from the outset that the pattern
+ # is not found in the file at all, then there's no need to do the
+ # line-by-line iteration.
- for my $td (@typedef) {
- my ($type, $ext) = split /=/, $td->[1];
+ # Slurp up an entire file up to 10M, see if there are any matches
+ # in it, and if so, let us know so we can iterate over it directly.
- if ( $td->[0] eq 'c' ) {
- # type-set
- if ( exists $mappings{$type} ) {
- # can't redefine types 'make', 'skipped', 'text' and 'binary'
- App::Ack::die( qq{--type-set: Builtin type "$type" cannot be changed.} )
- if ref $mappings{$type} ne 'ARRAY';
+ # The $regex may be undef if it had a "$" in it, and is therefore unsuitable for this heuristic.
- delete_type($type);
+ my $may_be_present = 1;
+ if ( $regex && $self->open() && -f $self->{fh} ) {
+ my $buffer;
+ my $size = 10_000_000;
+ my $rc = sysread( $self->{fh}, $buffer, $size );
+ if ( !defined($rc) ) {
+ if ( $App::Ack::report_bad_filenames ) {
+ App::Ack::warn( $self->name . ": $!" );
}
+ $may_be_present = 0;
}
else {
- # type-add
+ # If we read all 10M, then we need to scan the rest.
+ # If there are any carriage returns, our results are flaky, so scan the rest.
+ if ( ($rc == $size) || (index($buffer,"\r") >= 0) ) {
+ $may_be_present = 1;
+ }
+ else {
+ if ( $buffer !~ /$regex/o ) {
+ $may_be_present = 0;
+ }
+ }
+ }
+ }
- # can't append to types 'make', 'skipped', 'text' and 'binary'
- App::Ack::die( qq{--type-add: Builtin type "$type" cannot be changed.} )
- if exists $mappings{$type} && ref $mappings{$type} ne 'ARRAY';
+ return $may_be_present;
+}
- App::Ack::warn( qq{--type-add: Type "$type" does not exist, creating with "$ext" ...} )
- unless exists $mappings{$type};
- }
- my @exts = split /,/, $ext;
- s/^\.// for @exts;
- if ( !exists $mappings{$type} || ref($mappings{$type}) eq 'ARRAY' ) {
- push @{$mappings{$type}}, @exts;
- for my $e ( @exts ) {
- push @{$types{$e}}, $type;
- }
- }
- else {
- App::Ack::die( qq{Cannot append to type "$type".} );
+sub reset {
+ my $self = shift;
+
+ if ( defined($self->{fh}) ) {
+ return unless -f $self->{fh};
+
+ if ( !seek( $self->{fh}, 0, 0 ) && $App::Ack::report_bad_filenames ) {
+ App::Ack::warn( "$self->{filename}: $!" );
}
}
}
-sub delete_type {
- my $type = shift;
- App::Ack::die( qq{Internal error: Cannot delete builtin type "$type".} )
- unless ref $mappings{$type} eq 'ARRAY';
+sub close {
+ my $self = shift;
- delete $mappings{$type};
- delete $type_wanted{$type};
- for my $ext ( keys %types ) {
- $types{$ext} = [ grep { $_ ne $type } @{$types{$ext}} ];
+ if ( $self->{fh} ) {
+ if ( !close($self->{fh}) && $App::Ack::report_bad_filenames ) {
+ App::Ack::warn( $self->name() . ": $!" );
+ }
+ $self->{fh} = undef;
}
+
+ return;
}
-sub ignoredir_filter {
- return !exists $ignore_dirs{$_} && !exists $ignore_dirs{$File::Next::dir};
+
+sub clone {
+ my ( $self ) = @_;
+
+ return __PACKAGE__->new($self->name);
}
-sub remove_dir_sep {
- my $path = shift;
- $path =~ s/[$dir_sep_chars]$//;
- return $path;
-}
+sub firstliney {
+ my ( $self ) = @_;
+ if ( !exists $self->{firstliney} ) {
+ my $fh = $self->open();
+ if ( !$fh ) {
+ if ( $App::Ack::report_bad_filenames ) {
+ App::Ack::warn( $self->name . ': ' . $! );
+ }
+ $self->{firstliney} = '';
+ }
+ else {
+ my $buffer;
+ my $rc = sysread( $fh, $buffer, 250 );
+ if ( $rc ) {
+ $buffer =~ s/[\r\n].*//s;
+ }
+ else {
+ if ( !defined($rc) ) {
+ App::Ack::warn( $self->name . ': ' . $! );
+ }
+ $buffer = '';
+ }
+ $self->{firstliney} = $buffer;
+ $self->reset;
+ }
+ }
-use constant TEXT => 'text';
+ return $self->{firstliney};
+}
-sub filetypes {
- my $filename = shift;
+1;
+}
+{
+package App::Ack::Files;
- my $basename = $filename;
- $basename =~ s{.*[$dir_sep_chars]}{};
- return 'skipped' unless is_searchable( $basename );
- my $lc_basename = lc $basename;
- return ('make',TEXT) if $lc_basename eq 'makefile' || $lc_basename eq 'gnumakefile';
- return ('rake','ruby',TEXT) if $lc_basename eq 'rakefile';
+use warnings;
+use strict;
+use 5.010;
- # If there's an extension, look it up
- if ( $filename =~ m{\.([^\.$dir_sep_chars]+)$}o ) {
- my $ref = $types{lc $1};
- return (@{$ref},TEXT) if $ref;
- }
- # At this point, we can't tell from just the name. Now we have to
- # open it and look inside.
+sub from_argv {
+ my $class = shift;
+ my $opt = shift;
+ my $start = shift;
- return unless -e $filename;
- # From Elliot Shank:
- # I can't see any reason that -r would fail on these-- the ACLs look
- # fine, and no program has any of them open, so the busted Windows
- # file locking model isn't getting in there. If I comment the if
- # statement out, everything works fine
- # So, for cygwin, don't bother trying to check for readability.
- if ( !$is_cygwin ) {
- if ( !-r $filename ) {
- App::Ack::warn( "$filename: Permission denied" );
- return;
- }
- }
+ my $self = bless {}, $class;
- return 'binary' if -B $filename;
+ my $descend_filter = $opt->{descend_filter};
- # If there's no extension, or we don't recognize it, check the shebang line
- my $fh;
- if ( !open( $fh, '<', $filename ) ) {
- App::Ack::warn( "$filename: $!" );
- return;
+ if ( $opt->{n} ) {
+ $descend_filter = sub {
+ return 0;
+ };
}
- my $header = <$fh>;
- close $fh;
- if ( $header =~ /^#!/ ) {
- return ($1,TEXT) if $header =~ /\b(ruby|lua|p(?:erl|hp|ython))-?(\d[\d.]*)?\b/;
- return ('shell',TEXT) if $header =~ /\b(?:ba|t?c|k|z)?sh\b/;
- }
- else {
- return ('xml',TEXT) if $header =~ /\Q<?xml /i;
- }
+ $self->{iter} =
+ File::Next::files( {
+ file_filter => $opt->{file_filter},
+ descend_filter => $descend_filter,
+ error_handler => _generate_error_handler(),
+ warning_handler => sub {},
+ sort_files => $opt->{sort_files},
+ follow_symlinks => $opt->{follow},
+ }, @{$start} );
- return (TEXT);
+ return $self;
}
-sub is_searchable {
- my $filename = shift;
+sub from_file {
+ my $class = shift;
+ my $opt = shift;
+ my $file = shift;
- # If these are updated, update the --help message
- return if $filename =~ /[.]bak$/;
- return if $filename =~ /~$/;
- return if $filename =~ m{^#.*#$}o;
- return if $filename =~ m{^core\.\d+$}o;
- return if $filename =~ m{[._].*\.swp$}o;
- return if $filename =~ /[.-]min\.js$/;
+ my $error_handler = _generate_error_handler();
+ my $iter =
+ File::Next::from_file( {
+ error_handler => $error_handler,
+ warning_handler => $error_handler,
+ sort_files => $opt->{sort_files},
+ }, $file ) or return undef;
- return 1;
+ return bless {
+ iter => $iter,
+ }, $class;
}
-sub build_regex {
- my $str = shift;
- my $opt = shift;
-
- defined $str or App::Ack::die( 'No regular expression found.' );
-
- $str = quotemeta( $str ) if $opt->{Q};
- if ( $opt->{w} ) {
- $str = "\\b$str" if $str =~ /^\w/;
- $str = "$str\\b" if $str =~ /\w$/;
- }
-
- my $regex_is_lc = $str eq lc $str;
- if ( $opt->{i} || ($opt->{smart_case} && $regex_is_lc) ) {
- $str = "(?i)$str";
- }
- return $str;
-}
+sub from_stdin {
+ my $class = shift;
-sub check_regex {
- my $regex = shift;
+ my $self = bless {}, $class;
- return unless defined $regex;
+ $self->{iter} = sub {
+ state $has_been_called = 0;
- eval { qr/$regex/ };
- if ($@) {
- (my $error = $@) =~ s/ at \S+ line \d+.*//;
- chomp($error);
- App::Ack::die( "Invalid regex '$regex':\n $error" );
- }
+ if ( !$has_been_called ) {
+ $has_been_called = 1;
+ return '-';
+ }
+ return;
+ };
- return;
+ return $self;
}
+sub next {
+ my $self = shift;
+ my $file = $self->{iter}->();
-sub warn {
- return CORE::warn( _my_program(), ': ', @_, "\n" );
+ return unless defined($file);
+
+ return App::Ack::File->new( $file );
}
-sub die {
- return CORE::die( _my_program(), ': ', @_, "\n" );
+sub _generate_error_handler {
+ if ( $App::Ack::report_bad_filenames ) {
+ return sub {
+ my $msg = shift;
+ App::Ack::warn( $msg );
+ };
+ }
+ else {
+ return sub {};
+ }
}
-sub _my_program {
- require File::Basename;
- return File::Basename::basename( $0 );
+1;
}
+{
+package App::Ack::Filter;
+use strict;
+use warnings;
-sub filetypes_supported {
- return keys %mappings;
-}
-
-sub _get_thpppt {
- my $y = q{_ /|,\\'!.x',=(www)=, U };
- $y =~ tr/,x!w/\nOo_/;
- return $y;
-}
+my %filter_types;
-sub _thpppt {
- my $y = _get_thpppt();
- App::Ack::print( "$y ack $_[0]!\n" );
- exit 0;
-}
-sub _key {
- my $str = lc shift;
- $str =~ s/[^a-z]//g;
+sub create_filter {
+ my ( undef, $type, @args ) = @_;
- return $str;
+ if ( my $package = $filter_types{$type} ) {
+ return $package->new(@args);
+ }
+ my $allowed_types = join( ', ', sort keys %filter_types );
+ App::Ack::die( "Unknown filter type '$type'. Type must be one of: $allowed_types." );
}
-sub show_help {
- my $help_arg = shift || 0;
+sub register_filter {
+ my ( undef, $type, $package ) = @_;
- return show_help_types() if $help_arg =~ /^types?/;
+ $filter_types{$type} = $package;
- my $ignore_dirs = _listify( sort { _key($a) cmp _key($b) } keys %ignore_dirs );
+ return;
+}
- App::Ack::print( <<"END_OF_HELP" );
-Usage: ack [OPTION]... PATTERN [FILE]
-Search for PATTERN in each source file in the tree from cwd on down.
-If [FILES] is specified, then only those files/directories are checked.
-ack may also search STDIN, but only if no FILE are specified, or if
-one of FILES is "-".
+sub invert {
+ my ( $self ) = @_;
+
+ return App::Ack::Filter::Inverse->new( $self );
+}
-Default switches may be specified in ACK_OPTIONS environment variable or
-an .ackrc file. If you want no dependency on the environment, turn it
-off with --noenv.
-Example: ack -i select
+sub is_inverted {
+ return 0;
+}
-Searching:
- -i, --ignore-case Ignore case distinctions in PATTERN
- --[no]smart-case Ignore case distinctions in PATTERN,
- only if PATTERN contains no upper case
- Ignored if -i is specified
- -v, --invert-match Invert match: select non-matching lines
- -w, --word-regexp Force PATTERN to match only whole words
- -Q, --literal Quote all metacharacters; PATTERN is literal
-Search output:
- --line=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
- -o Show only the part of a line matching PATTERN
- (turns off text highlighting)
- --passthru Print all lines, whether matching or not
- --output=expr Output the evaluation of expr for each line
- (turns off text highlighting)
- --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
- --column Show the column number of the first match
-
- -A NUM, --after-context=NUM
- Print NUM lines of trailing context after matching
- lines.
- -B NUM, --before-context=NUM
- Print NUM lines of leading context before matching
- lines.
- -C [NUM], --context[=NUM]
- Print NUM lines (default 2) of output context.
-
- --print0 Print null byte as separator between filenames,
- only works with -f, -g, -l, -L or -c.
+sub to_string {
+ return '(unimplemented to_string)';
+}
-File presentation:
- --pager=COMMAND Pipes all ack output through COMMAND. For example,
- --pager="less -R". Ignored if output is redirected.
- --nopager Do not send output through a pager. Cancels any
- setting in ~/.ackrc, ACK_PAGER or ACK_PAGER_COLOR.
- --[no]heading Print a filename heading above each file's results.
- (default: on when used interactively)
- --[no]break Print a break between results from different files.
- (default: on when used interactively)
- --group Same as --heading --break
- --nogroup Same as --noheading --nobreak
- --[no]color Highlight the matching text (default: on unless
- output is redirected, or on Windows)
- --[no]colour Same as --[no]color
- --color-filename=COLOR
- --color-match=COLOR
- --color-lineno=COLOR Set the color for filenames, matches, and line numbers.
- --flush Flush output immediately, even when ack is used
- non-interactively (when output goes to a pipe or
- file).
-File finding:
- -f Only print the files found, without searching.
- The PATTERN must not be specified.
- -g REGEX Same as -f, but only print files matching REGEX.
- --sort-files Sort the found files lexically.
- --invert-file-match Print/search handle files that do not match -g/-G.
- --show-types Show which types each file has.
+sub inspect {
+ my ( $self ) = @_;
-File inclusion/exclusion:
- -a, --all-types All file types searched;
- Ignores CVS, .svn and other ignored directories
- -u, --unrestricted All files and directories searched
- --[no]ignore-dir=name Add/Remove directory from the list of ignored dirs
- -r, -R, --recurse Recurse into subdirectories (ack's default behavior)
- -n, --no-recurse No descending into subdirectories
- -G REGEX Only search files that match REGEX
-
- --perl Include only Perl files.
- --type=perl Include only Perl files.
- --noperl Exclude Perl files.
- --type=noperl Exclude Perl files.
- See "ack --help type" for supported filetypes.
-
- --type-set TYPE=.EXTENSION[,.EXT2[,...]]
- Files with the given EXTENSION(s) are recognized as
- being of type TYPE. This replaces an existing
- definition for type TYPE.
- --type-add TYPE=.EXTENSION[,.EXT2[,...]]
- Files with the given EXTENSION(s) are recognized as
- being of (the existing) type TYPE
-
- --[no]follow Follow symlinks. Default is off.
-
- Directories ignored by default:
- $ignore_dirs
-
- Files not checked for type:
- /~\$/ - Unix backup files
- /#.+#\$/ - Emacs swap files
- /[._].*\\.swp\$/ - Vi(m) swap files
- /core\\.\\d+\$/ - core dumps
- /[.-]min\\.js\$/ - Minified javascript files
+ return ref($self);
+}
-Miscellaneous:
- --noenv Ignore environment variables and ~/.ackrc
- --help This help
- --man Man page
- --version Display version & copyright
- --thpppt Bill the Cat
+1;
+}
+{
+package App::Ack::Filter::Collection;
-Exit status is 0 if match, 1 if no match.
-This is version $VERSION of ack.
-END_OF_HELP
+use strict;
+use warnings;
+BEGIN {
+ our @ISA = 'App::Ack::Filter';
+}
- return;
- }
+sub new {
+ my ( $class ) = @_;
+ return bless {
+ groups => {},
+ ungrouped => [],
+ }, $class;
+}
+sub filter {
+ my ( $self, $file ) = @_;
-sub show_help_types {
- App::Ack::print( <<'END_OF_HELP' );
-Usage: ack [OPTION]... PATTERN [FILES]
+ for my $group (values %{$self->{groups}}) {
+ return 1 if $group->filter($file);
+ }
-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.
+ for my $filter (@{$self->{ungrouped}}) {
+ return 1 if $filter->filter($file);
+ }
-Note that some extensions may appear in multiple types. For example,
-.pod files are both Perl and Parrot.
+ return 0;
+}
-END_OF_HELP
+sub add {
+ my ( $self, $filter ) = @_;
- my @types = filetypes_supported();
- my $maxlen = 0;
- for ( @types ) {
- $maxlen = length if $maxlen < length;
+ if (exists $filter->{'groupname'}) {
+ my $group = ($self->{groups}->{$filter->{groupname}} ||= $filter->create_group());
+ $group->add($filter);
}
- 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 { ".$_" } @{$ext_list} );
- }
- App::Ack::print( sprintf( " --[no]%-*.*s %s\n", $maxlen, $maxlen, $type, $ext_list ) );
+ else {
+ push @{$self->{'ungrouped'}}, $filter;
}
return;
}
-sub _listify {
- my @whats = @_;
+sub inspect {
+ my ( $self ) = @_;
- return '' if !@whats;
+ return ref($self) . " - $self";
+}
- my $end = pop @whats;
- my $str = @whats ? join( ', ', @whats ) . " and $end" : $end;
+sub to_string {
+ my ( $self ) = @_;
- no warnings 'once';
- require Text::Wrap;
- $Text::Wrap::columns = 75;
- return Text::Wrap::wrap( '', ' ', $str );
+ return join(', ', map { "($_)" } @{$self->{ungrouped}});
}
+1;
+}
+{
+package App::Ack::Filter::Default;
-sub get_version_statement {
- require Config;
-
- my $copyright = get_copyright();
- my $this_perl = $Config::Config{perlpath};
- if ($^O ne 'VMS') {
- my $ext = $Config::Config{_exe};
- $this_perl .= $ext unless $this_perl =~ m/$ext$/i;
- }
- my $ver = sprintf( '%vd', $^V );
- return <<"END_OF_VERSION";
-ack $VERSION
-Running under Perl $ver at $this_perl
+use strict;
+use warnings;
+BEGIN {
+ our @ISA = 'App::Ack::Filter';
+}
-$copyright
+sub new {
+ my ( $class ) = @_;
-This program is free software. You may modify or distribute it
-under the terms of the Artistic License v2.0.
-END_OF_VERSION
+ return bless {}, $class;
}
+sub filter {
+ my ( undef, $file ) = @_;
-sub print_version_statement {
- App::Ack::print( get_version_statement() );
+ return -T $file->name;
+}
- return;
+1;
}
+{
+package App::Ack::Filter::Extension;
-sub get_copyright {
- return $COPYRIGHT;
+use strict;
+use warnings;
+BEGIN {
+ our @ISA = 'App::Ack::Filter';
}
-sub load_colors {
- eval 'use Term::ANSIColor ()';
+sub new {
+ my ( $class, @extensions ) = @_;
- $ENV{ACK_COLOR_MATCH} ||= 'black on_yellow';
- $ENV{ACK_COLOR_FILENAME} ||= 'bold green';
- $ENV{ACK_COLOR_LINENO} ||= 'bold yellow';
+ my $exts = join('|', map { "\Q$_\E"} @extensions);
+ my $re = qr/[.](?:$exts)$/i;
- return;
+ return bless {
+ extensions => \@extensions,
+ regex => $re,
+ groupname => 'ExtensionGroup',
+ }, $class;
}
+sub create_group {
+ return App::Ack::Filter::ExtensionGroup->new();
+}
-sub is_interesting {
- return if /^\./;
-
- my $include;
-
- for my $type ( filetypes( $File::Next::name ) ) {
- if ( defined $type_wanted{$type} ) {
- if ( $type_wanted{$type} ) {
- $include = 1;
- }
- else {
- return;
- }
- }
- }
+sub filter {
+ my ( $self, $file ) = @_;
- return $include;
+ return $file->name =~ /$self->{regex}/;
}
+sub inspect {
+ my ( $self ) = @_;
+ return ref($self) . ' - ' . $self->{regex};
+}
-# 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} @_ }
-sub print_first_filename { App::Ack::print( $_[0], "\n" ) }
-sub print_blank_line { App::Ack::print( "\n" ) }
-sub print_separator { App::Ack::print( "--\n" ) }
-sub print_filename { App::Ack::print( $_[0], $_[1] ) }
-sub print_line_no { App::Ack::print( $_[0], $_[1] ) }
-sub print_column_no { App::Ack::print( $_[0], $_[1] ) }
-sub print_count {
- my $filename = shift;
- my $nmatches = shift;
- my $ors = shift;
- my $count = shift;
- my $show_filename = shift;
+sub to_string {
+ my ( $self ) = @_;
- if ($show_filename) {
- App::Ack::print( $filename );
- App::Ack::print( ':', $nmatches ) if $count;
- }
- else {
- App::Ack::print( $nmatches ) if $count;
- }
- App::Ack::print( $ors );
+ return join( ' ', map { ".$_" } @{$self->{extensions}} );
}
-sub print_count0 {
- my $filename = shift;
- my $ors = shift;
- my $show_filename = shift;
-
- if ($show_filename) {
- App::Ack::print( $filename, ':0', $ors );
- }
- else {
- App::Ack::print( '0', $ors );
- }
+BEGIN {
+ App::Ack::Filter->register_filter(ext => __PACKAGE__);
}
+1;
+}
+{
+package App::Ack::Filter::ExtensionGroup;
-{
- my $filename;
- my $regex;
- my $display_filename;
+use strict;
+use warnings;
+BEGIN {
+ our @ISA = 'App::Ack::Filter';
+}
- my $keep_context;
+sub new {
+ my ( $class ) = @_;
- my $last_output_line; # number of the last line that has been output
- my $any_output; # has there been any output for the current file yet
- my $context_overall_output_count; # has there been any output at all
+ return bless {
+ data => {},
+ }, $class;
+}
-sub search_resource {
- my $res = shift;
- my $opt = shift;
+sub add {
+ my ( $self, $filter ) = @_;
- $filename = $res->name();
+ foreach my $ext (@{$filter->{extensions}}) {
+ $self->{data}->{lc $ext} = 1;
+ }
- my $v = $opt->{v};
- my $passthru = $opt->{passthru};
- my $max = $opt->{m};
- my $nmatches = 0;
+ return;
+}
- $display_filename = undef;
+sub filter {
+ my ( $self, $file ) = @_;
- # for --line processing
- my $has_lines = 0;
- my @lines;
- if ( defined $opt->{lines} ) {
- $has_lines = 1;
- @lines = ( @{$opt->{lines}}, -1 );
- undef $regex; # Don't match when printing matching line
- }
- else {
- $regex = qr/$opt->{regex}/;
+ if ($file->name =~ /[.]([^.]*)$/) {
+ return exists $self->{'data'}->{lc $1};
}
- # for context processing
- $last_output_line = -1;
- $any_output = 0;
- my $before_context = $opt->{before_context};
- my $after_context = $opt->{after_context};
+ return 0;
+}
- $keep_context = ($before_context || $after_context) && !$passthru;
+sub inspect {
+ my ( $self ) = @_;
- my @before;
- my $before_starts_at_line;
- my $after = 0; # number of lines still to print after a match
+ return ref($self) . " - $self";
+}
- while ( $res->next_text ) {
- # XXX Optimize away the case when there are no more @lines to find.
- # XXX $has_lines, $passthru and $v never change. Optimize.
- if ( $has_lines
- ? $. != $lines[0] # $lines[0] should be a scalar
- : $v ? m/$regex/ : !m/$regex/ ) {
- if ( $passthru ) {
- App::Ack::print( $_ );
- next;
- }
+sub to_string {
+ my ( $self ) = @_;
- if ( $keep_context ) {
- if ( $after ) {
- print_match_or_context( $opt, 0, $., $-[0], $+[0], $_ );
- $after--;
- }
- elsif ( $before_context ) {
- if ( @before ) {
- if ( @before >= $before_context ) {
- shift @before;
- ++$before_starts_at_line;
- }
- }
- else {
- $before_starts_at_line = $.;
- }
- push @before, $_;
- }
- last if $max && ( $nmatches >= $max ) && !$after;
- }
- next;
- } # not a match
+ return join(' ', map { ".$_" } sort keys %{$self->{data}});
+}
- ++$nmatches;
+1;
+}
+{
+package App::Ack::Filter::FirstLineMatch;
- # print an empty line as a divider before first line in each file (not before the first file)
- if ( !$any_output && $opt->{show_filename} && $opt->{break} && defined( $context_overall_output_count ) ) {
- App::Ack::print_blank_line();
- }
- shift @lines if $has_lines;
- if ( $res->is_binary ) {
- App::Ack::print( "Binary file $filename matches\n" );
- last;
- }
- if ( $keep_context ) {
- if ( @before ) {
- print_match_or_context( $opt, 0, $before_starts_at_line, $-[0], $+[0], @before );
- @before = ();
- $before_starts_at_line = 0;
- }
- if ( $max && $nmatches > $max ) {
- --$after;
- }
- else {
- $after = $after_context;
- }
- }
- print_match_or_context( $opt, 1, $., $-[0], $+[0], $_ );
+use strict;
+use warnings;
+BEGIN {
+ our @ISA = 'App::Ack::Filter';
+}
- last if $max && ( $nmatches >= $max ) && !$after;
- } # while
+sub new {
+ my ( $class, $re ) = @_;
- return $nmatches;
-} # search_resource()
-
-
-
-sub print_match_or_context {
- my $opt = shift; # opts array
- my $is_match = shift; # is there a match on the line?
- my $line_no = shift;
- my $match_start = shift;
- my $match_end = shift;
-
- my $color = $opt->{color};
- my $heading = $opt->{heading};
- my $show_filename = $opt->{show_filename};
- my $show_column = $opt->{column};
-
- if ( $show_filename ) {
- if ( not defined $display_filename ) {
- $display_filename =
- $color
- ? Term::ANSIColor::colored( $filename, $ENV{ACK_COLOR_FILENAME} )
- : $filename;
- if ( $heading && !$any_output ) {
- App::Ack::print_first_filename($display_filename);
- }
- }
- }
+ $re =~ s{^/|/$}{}g; # XXX validate?
+ $re = qr{$re}i;
- my $sep = $is_match ? ':' : '-';
- my $output_func = $opt->{output};
- for ( @_ ) {
- if ( $keep_context && !$output_func ) {
- if ( ( $last_output_line != $line_no - 1 ) &&
- ( $any_output || ( !$heading && defined( $context_overall_output_count ) ) ) ) {
- App::Ack::print_separator();
- }
- # to ensure separators between different files when --noheading
+ return bless {
+ regex => $re,
+ }, $class;
+}
- $last_output_line = $line_no;
- }
+# 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.
- if ( $show_filename ) {
- App::Ack::print_filename($display_filename, $sep) if not $heading;
- my $display_line_no =
- $color
- ? Term::ANSIColor::colored( $line_no, $ENV{ACK_COLOR_LINENO} )
- : $line_no;
- App::Ack::print_line_no($display_line_no, $sep);
- }
+sub filter {
+ my ( $self, $file ) = @_;
- if ( $output_func ) {
- while ( /$regex/go ) {
- App::Ack::print( $output_func->() . "\n" );
- }
- }
- else {
- if ( $color && $is_match && $regex &&
- s/$regex/Term::ANSIColor::colored( substr($_, $-[0], $+[0] - $-[0]), $ENV{ACK_COLOR_MATCH} )/eg ) {
- # At the end of the line reset the color and remove newline
- s/[\r\n]*\z/\e[0m\e[K/;
- }
- else {
- # remove any kind of newline at the end of the line
- s/[\r\n]*\z//;
- }
- if ( $show_column ) {
- App::Ack::print_column_no( $match_start+1, $sep );
- }
- App::Ack::print($_ . "\n");
- }
- $any_output = 1;
- ++$context_overall_output_count;
- ++$line_no;
- }
+ return $file->firstliney =~ /$self->{regex}/;
+}
- return;
-} # print_match_or_context()
+sub inspect {
+ my ( $self ) = @_;
-} # scope around search_resource() and print_match_or_context()
+ return ref($self) . ' - ' . $self->{regex};
+}
+
+sub to_string {
+ my ( $self ) = @_;
-TOTAL_COUNT_SCOPE: {
-my $total_count;
+ (my $re = $self->{regex}) =~ s{\([^:]*:(.*)\)$}{$1};
-sub get_total_count {
- return $total_count;
+ return "First line matches /$re/";
}
-sub reset_total_count {
- $total_count = 0;
+BEGIN {
+ App::Ack::Filter->register_filter(firstlinematch => __PACKAGE__);
}
+1;
+}
+{
+package App::Ack::Filter::Inverse;
-sub search_and_list {
- my $res = shift;
- my $opt = shift;
- my $nmatches = 0;
- my $count = $opt->{count};
- my $ors = $opt->{print0} ? "\0" : "\n"; # output record separator
- my $show_filename = $opt->{show_filename};
- my $regex = qr/$opt->{regex}/;
+use strict;
+use warnings;
+BEGIN {
+ our @ISA = 'App::Ack::Filter';
+}
- if ( $opt->{v} ) {
- while ( $res->next_text ) {
- if ( /$regex/ ) {
- return 0 unless $count;
- }
- else {
- ++$nmatches;
- }
- }
- }
- else {
- while ( $res->next_text ) {
- if ( /$regex/ ) {
- ++$nmatches;
- last unless $count;
- }
- }
- }
+sub new {
+ my ( $class, $filter ) = @_;
- if ( $opt->{show_total} ) {
- $total_count += $nmatches;
- }
- else {
- if ( $nmatches ) {
- App::Ack::print_count( $res->name, $nmatches, $ors, $count, $show_filename );
- }
- elsif ( $count && !$opt->{l} ) {
- App::Ack::print_count0( $res->name, $ors, $show_filename );
- }
- }
+ return bless {
+ filter => $filter,
+ }, $class;
+}
- return $nmatches ? 1 : 0;
-} # search_and_list()
+sub filter {
+ my ( $self, $file ) = @_;
-} # scope around $total_count
+ return !$self->{filter}->filter( $file );
+}
+sub invert {
+ my $self = shift;
+ return $self->{'filter'};
+}
-sub filetypes_supported_set {
- return grep { defined $type_wanted{$_} && ($type_wanted{$_} == 1) } filetypes_supported();
+sub is_inverted {
+ return 1;
}
+sub inspect {
+ my ( $self ) = @_;
+ my $filter = $self->{'filter'};
-sub print_files {
- my $iter = shift;
- my $opt = shift;
+ return "!$filter";
+}
- my $ors = $opt->{print0} ? "\0" : "\n";
+1;
+}
+{
+package App::Ack::Filter::Is;
- my $nmatches = 0;
- while ( defined ( my $file = $iter->() ) ) {
- App::Ack::print $file, $opt->{show_types} ? " => " . join( ',', filetypes( $file ) ) : (), $ors;
- $nmatches++;
- last if $opt->{1};
- }
- return $nmatches;
+use strict;
+use warnings;
+BEGIN {
+ our @ISA = 'App::Ack::Filter';
}
+use File::Spec 3.00 ();
-sub print_files_with_matches {
- my $iter = shift;
- my $opt = shift;
+sub new {
+ my ( $class, $filename ) = @_;
- # if we have -l and only 1 file given on command line (this means
- # show_filename is set to 0), we want to see the filename nevertheless
- $opt->{show_filename} = 1 if $opt->{l};
+ return bless {
+ filename => $filename,
+ groupname => 'IsGroup',
+ }, $class;
+}
- $opt->{show_filename} = 0 if $opt->{h};
- $opt->{show_filename} = 1 if $opt->{H};
+sub create_group {
+ return App::Ack::Filter::IsGroup->new();
+}
- # abuse options to hand in the show_total parameter to search_and_list
- $opt->{show_total} = $opt->{count} && !$opt->{show_filename};
- reset_total_count();
+sub filter {
+ my ( $self, $file ) = @_;
- my $nmatches = 0;
- while ( defined ( my $filename = $iter->() ) ) {
- my $repo = App::Ack::Repository::Basic->new( $filename );
- my $res;
- while ( $res = $repo->next_resource() ) {
- $nmatches += search_and_list( $res, $opt );
- $res->close();
- last if $nmatches && $opt->{1};
- }
- $repo->close();
- }
+ return (File::Spec->splitpath($file->name))[2] eq $self->{filename};
+}
- if ( $nmatches && $opt->{show_total} ) {
- App::Ack::print_count('', get_total_count(), "\n", 1, 0 )
- }
+sub inspect {
+ my ( $self ) = @_;
- return $nmatches;
+ return ref($self) . ' - ' . $self->{filename};
}
+sub to_string {
+ my ( $self ) = @_;
-sub print_matches {
- my $iter = shift;
- my $opt = shift;
+ return $self->{filename};
+}
- $opt->{show_filename} = 0 if $opt->{h};
- $opt->{show_filename} = 1 if $opt->{H};
+BEGIN {
+ App::Ack::Filter->register_filter(is => __PACKAGE__);
+}
- my $nmatches = 0;
- while ( defined ( my $filename = $iter->() ) ) {
- my $repo;
- my $tarballs_work = 0;
- if ( $tarballs_work && $filename =~ /\.tar\.gz$/ ) {
- App::Ack::die( 'Not working here yet' );
- require App::Ack::Repository::Tar; # XXX Error checking
- $repo = App::Ack::Repository::Tar->new( $filename );
- }
- else {
- $repo = App::Ack::Repository::Basic->new( $filename );
- }
- $repo or next;
+1;
+}
+{
+package App::Ack::Filter::IsGroup;
- while ( my $res = $repo->next_resource() ) {
- my $needs_line_scan;
- if ( $opt->{regex} && !$opt->{passthru} ) {
- $needs_line_scan = $res->needs_line_scan( $opt );
- if ( $needs_line_scan ) {
- $res->reset();
- }
- }
- else {
- $needs_line_scan = 1;
- }
- if ( $needs_line_scan ) {
- $nmatches += search_resource( $res, $opt );
- }
- $res->close();
- }
- last if $nmatches && $opt->{1};
- $repo->close();
- }
- return $nmatches;
+
+use strict;
+use warnings;
+BEGIN {
+ our @ISA = 'App::Ack::Filter';
}
+sub new {
+ my ( $class ) = @_;
-sub filetype_setup {
- my $filetypes_supported_set = filetypes_supported_set();
- # If anyone says --no-whatever, we assume all other types must be on.
- if ( !$filetypes_supported_set ) {
- for my $i ( keys %type_wanted ) {
- $type_wanted{$i} = 1 unless ( defined( $type_wanted{$i} ) || $i eq 'binary' || $i eq 'text' || $i eq 'skipped' );
- }
- }
- return;
+ return bless {
+ data => {},
+ }, $class;
}
+sub add {
+ my ( $self, $filter ) = @_;
-EXPAND_FILENAMES_SCOPE: {
- my $filter;
+ $self->{data}->{ $filter->{filename} } = 1;
+
+ return;
+}
- sub expand_filenames {
- my $argv = shift;
+sub filter {
+ my ( $self, $file ) = @_;
- my $attr;
- my @files;
+ return exists $self->{data}->{ $file->basename };
+}
- foreach my $pattern ( @{$argv} ) {
- my @results = bsd_glob( $pattern );
+sub inspect {
+ my ( $self ) = @_;
- if (@results == 0) {
- @results = $pattern; # Glob didn't match, pass it thru unchanged
- }
- elsif ( (@results > 1) or ($results[0] ne $pattern) ) {
- if (not defined $filter) {
- eval 'require Win32::File;';
- if ($@) {
- $filter = 0;
- }
- else {
- $filter = Win32::File::HIDDEN()|Win32::File::SYSTEM();
- }
- } # end unless we've tried to load Win32::File
- if ( $filter ) {
- # Filter out hidden and system files:
- @results = grep { not(Win32::File::GetAttributes($_, $attr) and $attr & $filter) } @results;
- App::Ack::warn( "$pattern: Matched only hidden files" ) unless @results;
- } # end if we can filter by file attributes
- } # end elsif this pattern got expanded
+ return ref($self) . " - $self";
+}
- push @files, @results;
- } # end foreach pattern
+sub to_string {
+ my ( $self ) = @_;
- return \@files;
- } # end expand_filenames
-} # EXPAND_FILENAMES_SCOPE
+ return join(' ', keys %{$self->{data}});
+}
+1;
+}
+{
+package App::Ack::Filter::IsPath;
-sub get_starting_points {
- my $argv = shift;
- my $opt = shift;
+use strict;
+use warnings;
+BEGIN {
+ our @ISA = 'App::Ack::Filter';
+}
- my @what;
- if ( @{$argv} ) {
- @what = @{ $is_windows ? expand_filenames($argv) : $argv };
- $_ = File::Next::reslash( $_ ) for @what;
+sub new {
+ my ( $class, $filename ) = @_;
- # Show filenames unless we've specified one single file
- $opt->{show_filename} = (@what > 1) || (!-f $what[0]);
- }
- else {
- @what = '.'; # Assume current directory
- $opt->{show_filename} = 1;
- }
+ return bless {
+ filename => $filename,
+ groupname => 'IsPathGroup',
+ }, $class;
+}
- for my $start_point (@what) {
- App::Ack::warn( "$start_point: No such file or directory" ) unless -e $start_point;
- }
- return \@what;
+sub create_group {
+ return App::Ack::Filter::IsPathGroup->new();
}
-sub _match {
- my ( $target, $expression, $invert_flag ) = @_;
+sub filter {
+ my ( $self, $file ) = @_;
- if ( $invert_flag ) {
- return $target !~ $expression;
- }
- else {
- return $target =~ $expression;
- }
+ return $file->name eq $self->{filename};
}
+sub inspect {
+ my ( $self ) = @_;
-sub get_iterator {
- my $what = shift;
- my $opt = shift;
+ return ref($self) . ' - ' . $self->{filename};
+}
- # Starting points are always searched, no matter what
- my %starting_point = map { ($_ => 1) } @{$what};
+sub to_string {
+ my ( $self ) = @_;
- my $g_regex = defined $opt->{G} ? qr/$opt->{G}/ : undef;
- my $file_filter;
+ return $self->{filename};
+}
- if ( $g_regex ) {
- $file_filter
- = $opt->{u} ? sub { _match( $File::Next::name, qr/$g_regex/, $opt->{invert_file_match} ) } # XXX Maybe this should be a 1, no?
- : $opt->{all} ? sub { $starting_point{ $File::Next::name } || ( _match( $File::Next::name, qr/$g_regex/, $opt->{invert_file_match} ) && is_searchable( $_ ) ) }
- : sub { $starting_point{ $File::Next::name } || ( _match( $File::Next::name, qr/$g_regex/, $opt->{invert_file_match} ) && is_interesting( @ _) ) }
- ;
- }
- else {
- $file_filter
- = $opt->{u} ? sub {1}
- : $opt->{all} ? sub { $starting_point{ $File::Next::name } || is_searchable( $_ ) }
- : sub { $starting_point{ $File::Next::name } || is_interesting( @_ ) }
- ;
- }
+1;
+}
+{
+package App::Ack::Filter::IsPathGroup;
- my $descend_filter
- = $opt->{n} ? sub {0}
- : $opt->{u} ? sub {1}
- : \&ignoredir_filter;
- my $iter =
- File::Next::files( {
- file_filter => $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},
- }, @{$what} );
- return $iter;
+use strict;
+use warnings;
+BEGIN {
+ our @ISA = 'App::Ack::Filter';
}
+sub new {
+ my ( $class ) = @_;
-sub set_up_pager {
- my $command = shift;
+ return bless {
+ data => {},
+ }, $class;
+}
- return if App::Ack::output_to_pipe();
+sub add {
+ my ( $self, $filter ) = @_;
- my $pager;
- if ( not open( $pager, '|-', $command ) ) {
- App::Ack::die( qq{Unable to pipe to pager "$command": $!} );
- }
- $fh = $pager;
+ $self->{data}->{ $filter->{filename} } = 1;
return;
}
+sub filter {
+ my ( $self, $file ) = @_;
-sub input_from_pipe {
- return $input_from_pipe;
+ return exists $self->{data}->{$file->name};
}
+sub inspect {
+ my ( $self ) = @_;
-
-sub output_to_pipe {
- return $output_to_pipe;
+ return ref($self) . " - $self";
}
+sub to_string {
+ my ( $self ) = @_;
-sub exit_from_ack {
- my $nmatches = shift;
+ return join(' ', keys %{$self->{data}});
+}
- my $rc = $nmatches ? 0 : 1;
- exit $rc;
+1;
}
+{
+package App::Ack::Filter::Match;
+use strict;
+use warnings;
+BEGIN {
+ our @ISA = 'App::Ack::Filter';
+}
-1; # End of App::Ack
-package App::Ack::Repository;
+sub new {
+ my ( $class, $re ) = @_;
-use warnings;
-use strict;
+ $re =~ s{^/|/$}{}g; # XXX validate?
+ $re = qr/$re/i;
-sub FAIL {
- require Carp;
- Carp::confess( 'Must be overloaded' );
+ return bless {
+ regex => $re,
+ groupname => 'MatchGroup',
+ }, $class;
}
-
-sub new {
- FAIL();
+sub create_group {
+ return App::Ack::Filter::MatchGroup->new;
}
+sub filter {
+ my ( $self, $file ) = @_;
-sub next_resource {
- FAIL();
+ return $file->basename =~ /$self->{regex}/;
}
+sub inspect {
+ my ( $self ) = @_;
-sub close {
- FAIL();
+ return ref($self) . ' - ' . $self->{regex};
}
-1;
-package App::Ack::Resource;
-
-
-use warnings;
-use strict;
+sub to_string {
+ my ( $self ) = @_;
-sub FAIL {
- require Carp;
- Carp::confess( 'Must be overloaded' );
+ return "Filename matches $self->{regex}";
}
+BEGIN {
+ App::Ack::Filter->register_filter(match => __PACKAGE__);
+}
-sub new {
- FAIL();
+1;
}
+{
+package App::Ack::Filter::MatchGroup;
-sub name {
- FAIL();
+use strict;
+use warnings;
+BEGIN {
+ our @ISA = 'App::Ack::Filter';
}
+sub new {
+ my ( $class ) = @_;
-sub is_binary {
- FAIL();
+ return bless {
+ matches => [],
+ big_re => undef,
+ }, $class;
}
+sub add {
+ my ( $self, $filter ) = @_;
+ push @{ $self->{matches} }, $filter->{regex};
-sub needs_line_scan {
- FAIL();
-}
-
+ my $re = join('|', map { "(?:$_)" } @{ $self->{matches} });
+ $self->{big_re} = qr/$re/;
-sub reset {
- FAIL();
+ return;
}
+sub filter {
+ my ( $self, $file ) = @_;
-sub next_text {
- FAIL();
+ return $file->basename =~ /$self->{big_re}/;
}
+# This class has no inspect() or to_string() method.
+# It will just use the default one unless someone writes something useful.
-sub close {
- FAIL();
+1;
}
+{
+package File::Next;
-1;
-package App::Ack::Plugin::Basic;
+use strict;
+use warnings;
+our $VERSION = '1.18';
-package App::Ack::Resource::Basic;
-use warnings;
-use strict;
+use File::Spec ();
+our $name; # name of the current file
+our $dir; # dir of the current file
-our @ISA = qw( App::Ack::Resource );
+our %files_defaults;
+our %skip_dirs;
+BEGIN {
+ %files_defaults = (
+ file_filter => undef,
+ descend_filter => undef,
+ error_handler => sub { CORE::die $_[0] },
+ warning_handler => sub { CORE::warn @_ },
+ sort_files => undef,
+ follow_symlinks => 1,
+ nul_separated => 0,
+ );
+ %skip_dirs = map {($_,1)} (File::Spec->curdir, File::Spec->updir);
+}
-sub new {
- my $class = shift;
- my $filename = shift;
- my $self = bless {
- filename => $filename,
- fh => undef,
- could_be_binary => undef,
- opened => undef,
- id => undef,
- }, $class;
+sub files {
+ die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__);
- if ( $self->{filename} eq '-' ) {
- $self->{fh} = *STDIN;
- $self->{could_be_binary} = 0;
- }
- else {
- if ( !open( $self->{fh}, '<', $self->{filename} ) ) {
- App::Ack::warn( "$self->{filename}: $!" );
- return;
- }
- $self->{could_be_binary} = 1;
- }
+ my ($parms,@queue) = _setup( \%files_defaults, @_ );
- return $self;
-}
+ my $filter = $parms->{file_filter};
+ return sub {
+ while ( my $entry = shift @queue ) {
+ my ( $dirname, $file, $fullpath, $is_dir, $is_file, $is_fifo ) = @{$entry};
+ if ( $is_file || $is_fifo ) {
+ if ( $filter ) {
+ local $_ = $file;
+ local $File::Next::dir = $dirname;
+ local $File::Next::name = $fullpath;
+ next if not $filter->();
+ }
+ return wantarray ? ($dirname,$file,$fullpath) : $fullpath;
+ }
+ if ( $is_dir ) {
+ unshift( @queue, _candidate_files( $parms, $fullpath ) );
+ }
+ } # while
+ return;
+ }; # iterator
+}
-sub name {
- my $self = shift;
- return $self->{filename};
-}
-sub is_binary {
- my $self = shift;
- if ( $self->{could_be_binary} ) {
- return -B $self->{filename};
- }
- return 0;
-}
+sub from_file {
+ die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__);
+ my ($parms,@queue) = _setup( \%files_defaults, @_ );
+ my $err = $parms->{error_handler};
+ my $warn = $parms->{warning_handler};
-sub needs_line_scan {
- my $self = shift;
- my $opt = shift;
+ my $filename = $queue[0]->[1];
- return 1 if $opt->{v};
+ if ( !defined($filename) ) {
+ $err->( 'Must pass a filename to from_file()' );
+ return undef;
+ }
- my $size = -s $self->{fh};
- if ( $size == 0 ) {
- return 0;
+ my $fh;
+ if ( $filename eq '-' ) {
+ $fh = \*STDIN;
}
- elsif ( $size > 100_000 ) {
- return 1;
+ else {
+ if ( !open( $fh, '<', $filename ) ) {
+ $err->( "Unable to open $filename: $!", $! + 0 );
+ return undef;
+ }
}
- my $buffer;
- my $rc = sysread( $self->{fh}, $buffer, $size );
- if ( not defined $rc ) {
- App::Ack::warn( "$self->{filename}: $!" );
- return 1;
- }
- return 0 unless $rc && ( $rc == $size );
+ my $filter = $parms->{file_filter};
+ return sub {
+ local $/ = $parms->{nul_separated} ? "\x00" : $/;
+ while ( my $fullpath = <$fh> ) {
+ chomp $fullpath;
+ next unless $fullpath =~ /./;
+ if ( not ( -f $fullpath || -p _ ) ) {
+ $warn->( "$fullpath: No such file" );
+ next;
+ }
- my $regex = $opt->{regex};
- return $buffer =~ /$regex/m;
-}
+ my ($volume,$dirname,$file) = File::Spec->splitpath( $fullpath );
+ if ( $filter ) {
+ local $_ = $file;
+ local $File::Next::dir = $dirname;
+ local $File::Next::name = $fullpath;
+ next if not $filter->();
+ }
+ return wantarray ? ($dirname,$file,$fullpath) : $fullpath;
+ } # while
+ close $fh;
+ return;
+ }; # iterator
+}
-sub reset {
- my $self = shift;
+sub _bad_invocation {
+ my $good = (caller(1))[3];
+ my $bad = $good;
+ $bad =~ s/(.+)::/$1->/;
+ return "$good must not be invoked as $bad";
+}
- seek( $self->{fh}, 0, 0 )
- or App::Ack::warn( "$self->{filename}: $!" );
+sub sort_standard($$) { return $_[0]->[1] cmp $_[1]->[1] }
+sub sort_reverse($$) { return $_[1]->[1] cmp $_[0]->[1] }
- return;
-}
+sub reslash {
+ my $path = shift;
+ my @parts = split( /\//, $path );
-sub next_text {
- if ( defined ($_ = readline $_[0]->{fh}) ) {
- $. = ++$_[0]->{line};
- return 1;
- }
+ return $path if @parts < 2;
- return;
+ return File::Spec->catfile( @parts );
}
-sub close {
- my $self = shift;
-
- if ( not close $self->{fh} ) {
- App::Ack::warn( $self->name() . ": $!" );
- }
- return;
-}
+sub _setup {
+ my $defaults = shift;
+ my $passed_parms = ref $_[0] eq 'HASH' ? {%{+shift}} : {}; # copy parm hash
-package App::Ack::Repository::Basic;
+ my %passed_parms = %{$passed_parms};
+ my $parms = {};
+ for my $key ( keys %{$defaults} ) {
+ $parms->{$key} =
+ exists $passed_parms{$key}
+ ? delete $passed_parms{$key}
+ : $defaults->{$key};
+ }
-our @ISA = qw( App::Ack::Repository );
+ # Any leftover keys are bogus
+ for my $badkey ( sort keys %passed_parms ) {
+ my $sub = (caller(1))[3];
+ $parms->{error_handler}->( "Invalid option passed to $sub(): $badkey" );
+ }
+ # If it's not a code ref, assume standard sort
+ if ( $parms->{sort_files} && ( ref($parms->{sort_files}) ne 'CODE' ) ) {
+ $parms->{sort_files} = \&sort_standard;
+ }
+ my @queue;
-use warnings;
-use strict;
+ for ( @_ ) {
+ my $start = reslash( $_ );
+ my $is_dir = -d $start;
+ my $is_file = -f _;
+ my $is_fifo = (-p _) || ($start =~ m{^/dev/fd});
+ push @queue,
+ $is_dir
+ ? [ $start, undef, $start, $is_dir, $is_file, $is_fifo ]
+ : [ undef, $start, $start, $is_dir, $is_file, $is_fifo ];
+ }
-sub new {
- my $class = shift;
- my $filename = shift;
+ return ($parms,@queue);
+}
- my $self = bless {
- filename => $filename,
- nexted => 0,
- }, $class;
- return $self;
-}
+sub _candidate_files {
+ my $parms = shift;
+ my $dirname = shift;
+ my $dh;
+ if ( !opendir $dh, $dirname ) {
+ $parms->{error_handler}->( "$dirname: $!", $! + 0 );
+ return;
+ }
-sub next_resource {
- my $self = shift;
+ my @newfiles;
+ my $descend_filter = $parms->{descend_filter};
+ my $follow_symlinks = $parms->{follow_symlinks};
- return if $self->{nexted};
- $self->{nexted} = 1;
+ for my $file ( grep { !exists $skip_dirs{$_} } readdir $dh ) {
+ my $fullpath = File::Spec->catdir( $dirname, $file );
+ if ( !$follow_symlinks ) {
+ next if -l $fullpath;
+ }
+ else {
+ stat($fullpath);
+ }
+ my $is_dir = -d _;
+ my $is_file = -f _;
+ my $is_fifo = (-p _) || ($fullpath =~ m{^/dev/fd});
- return App::Ack::Resource::Basic->new( $self->{filename} );
-}
+ # Only do directory checking if we have a descend_filter
+ if ( $descend_filter ) {
+ if ( $is_dir ) {
+ local $File::Next::dir = $fullpath;
+ local $_ = $file;
+ next if not $descend_filter->();
+ }
+ }
+ push @newfiles, [ $dirname, $file, $fullpath, $is_dir, $is_file, $is_fifo ];
+ }
+ closedir $dh;
+ my $sort_sub = $parms->{sort_files};
+ if ( $sort_sub ) {
+ @newfiles = sort $sort_sub @newfiles;
+ }
-sub close {
+ return @newfiles;
}
-1;
+1; # End of File::Next
+}