# Please DO NOT EDIT or send patches for it.
#
# Please take a look at the source from
-# https://github.com/beyondgrep/ack2
+# https://github.com/beyondgrep/ack3
# and submit patches against the individual files
# that build ack.
#
+$App::Ack::STANDALONE = 1;
package main;
use strict;
use warnings;
-our $VERSION = '2.22'; # Check https://beyondgrep.com/ for updates
-use 5.008008;
-use Getopt::Long 2.38 ();
-use Carp 1.04 ();
+our $VERSION = 'v3.4.0'; # Check https://beyondgrep.com/ for updates
+
+use 5.010001;
use File::Spec ();
-# XXX Don't make this so brute force
-# See also: https://github.com/beyondgrep/ack2/issues/89
-our $opt_after_context;
-our $opt_before_context;
-our $opt_output;
-our $opt_print0;
-our $opt_color;
-our $opt_heading;
-our $opt_show_filename;
-our $opt_regex;
+# Global command-line options
+our $opt_1;
+our $opt_A;
+our $opt_B;
our $opt_break;
-our $opt_count;
-our $opt_v;
-our $opt_m;
-our $opt_g;
+our $opt_color;
+our $opt_column;
+our $opt_debug;
+our $opt_c;
our $opt_f;
-our $opt_lines;
+our $opt_g;
+our $opt_heading;
our $opt_L;
our $opt_l;
+our $opt_m;
+our $opt_output;
our $opt_passthru;
-our $opt_column;
+our $opt_p;
+our $opt_range_start;
+our $opt_range_end;
+our $opt_range_invert;
+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;
-# These are all our globals.
+# The regex that we search for in each file.
+our $search_re;
+
+# Special /m version of our $search_re.
+our $scan_re;
+
+our @special_vars_used_by_opt_output;
+
+our $using_ranges;
+
+# Internal stats for debugging.
+our %stats;
MAIN: {
- $App::Ack::orig_program_name = $0;
+ $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" );
}
for my $arg ( @ARGV ) {
last if ( $arg eq '--' );
- # Get the --thpppt, --bar, --cathy checking out of the way.
+ # 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();
$arg eq '--noenv' and $env_is_usable = 0;
}
- if ( !$env_is_usable ) {
+ if ( $env_is_usable ) {
+ 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};
}
- load_colors();
-
- Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version');
- Getopt::Long::Configure('pass_through', 'no_auto_abbrev');
- Getopt::Long::GetOptions(
- 'help' => sub { App::Ack::show_help(); exit; },
- 'version' => sub { App::Ack::print_version_statement(); exit; },
- 'man' => sub { App::Ack::show_man(); exit; },
+
+ # 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';
+ }
+
+ my $p = App::Ack::ConfigLoader::opt_parser( 'no_auto_abbrev', 'pass_through' );
+ $p->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(); },
);
- Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version');
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_range_invert = $opt->{range_invert};
+ $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.' );
+ }
+
+ if ( $opt_range_start ) {
+ ($opt_range_start, undef) = build_regex( $opt_range_start, {} );
+ }
+ if ( $opt_range_end ) {
+ ($opt_range_end, undef) = build_regex( $opt_range_end, {} );
+ }
+ $using_ranges = $opt_range_start || $opt_range_end;
+
+ $App::Ack::report_bad_filenames = !$opt->{s};
+ $App::Ack::ors = $opt->{print0} ? "\0" : "\n";
+
+ 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();
+
+ 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"};
+ }
+ }
+ }
+
+ # 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;
+ ($search_re, $scan_re) = build_regex( $opt_regex, $opt );
+ $stats{search_re} = $search_re;
+ $stats{scan_re} = $scan_re;
+ }
+ else {
+ if ( $opt_f ) {
+ # No need to check for regex, since mutex options are handled elsewhere.
+ }
+ else {
+ $opt_regex //= shift @ARGV;
+ ($search_re, $scan_re) = build_regex( $opt_regex, $opt );
+ $stats{search_re} = $search_re;
+ $stats{scan_re} = $scan_re;
+ }
+ # XXX What is this checking for?
+ if ( $search_re && $search_re =~ /\n/ ) {
+ App::Ack::exit_from_ack( 0 );
+ }
+ my @start;
+ if ( not defined $opt->{files_from} ) {
+ @start = @ARGV;
+ }
+ if ( !exists($opt->{show_filename}) ) {
+ unless(@start == 1 && !(-d $start[0])) {
+ $opt_show_filename = $opt->{show_filename} = 1;
+ }
+ }
+
+ 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" );
+ }
+ }
+
+ $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};
+
+ my $nmatches;
+ if ( $opt_f || $opt_g ) {
+ $nmatches = file_loop_fg( $files );
+ }
+ elsif ( $opt_c ) {
+ $nmatches = file_loop_c( $files );
+ }
+ elsif ( $opt_l || $opt_L ) {
+ $nmatches = file_loop_lL( $files );
+ }
+ else {
+ $nmatches = file_loop_normal( $files );
+ }
+
+ if ( $opt_debug ) {
+ require List::Util;
+ my @stats = qw( search_re scan_re 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
+
+sub file_loop_fg {
+ my $files = shift;
+
+ 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);
+ }
+
+ return $nmatches;
+}
+
+
+sub file_loop_c {
+ my $files = shift;
+
+ my $total_count = 0;
+ while ( defined( my $file = $files->next ) ) {
+ my $matches_for_this_file = count_matches_in_file( $file );
+
+ if ( not $opt_show_filename ) {
+ $total_count += $matches_for_this_file;
+ next;
+ }
+
+ 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 );
+ }
+ }
+ }
+
+ if ( !$opt_show_filename ) {
+ App::Ack::say( $total_count );
+ }
+
+ return;
+}
+
+
+sub file_loop_lL {
+ my $files = shift;
+
+ my $nmatches = 0;
+ while ( defined( my $file = $files->next ) ) {
+ my $is_match = count_matches_in_file( $file, 1 );
+
+ if ( $opt_L ? !$is_match : $is_match ) {
+ App::Ack::say( $file->name );
+ ++$nmatches;
+
+ last if $opt_1;
+ last if defined($opt_m) && ($nmatches >= $opt_m);
+ }
+ }
+
+ return $nmatches;
}
+
sub _compile_descend_filter {
my ( $opt ) = @_;
$idirs = $opt->{idirs};
return sub {
- my $resource = App::Ack::Resource->new($File::Next::dir);
- return !grep { $_->filter($resource) } @{$idirs};
+ my $file = App::Ack::File->new($File::Next::dir);
+ return !grep { $_->filter($file) } @{$idirs};
};
}
return sub {
if ( $opt_g ) {
- if ( $File::Next::name =~ /$opt_regex/ && $opt_v ) {
- return 0;
+ if ( $File::Next::name =~ /$search_re/o ) {
+ return 0 if $opt_v;
}
- if ( $File::Next::name !~ /$opt_regex/ && !$opt_v ) {
- return 0;
+ else {
+ return 0 if !$opt_v;
}
}
# ack always selects files that are specified on the command
my $is_ignoring = 0;
for ( my $i = 0; $i < @dirs; $i++) {
- my $dir_rsrc = App::Ack::Resource->new(File::Spec->catfile(@dirs[0 .. $i]));
+ my $dir_rsrc = App::Ack::File->new(File::Spec->catfile(@dirs[0 .. $i]));
my $j = 0;
for my $filter (@ignore_dir_filter) {
}
}
- my $resource = App::Ack::Resource->new($File::Next::name);
+ my $file = App::Ack::File->new($File::Next::name);
- if ( $ifiles_filters && $ifiles_filters->filter($resource) ) {
+ if ( $ifiles_filters && $ifiles_filters->filter($file) ) {
return 0;
}
- my $match_found = $direct_filters->filter($resource);
+ my $match_found = $direct_filters->filter($file);
- # Don't bother invoking inverse filters unless we consider the current resource a match
- if ( $match_found && $inverse_filters->filter( $resource ) ) {
+ # 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;
};
}
-sub show_types {
- my $resource = shift;
- my $ors = shift;
-
- my @types = filetypes( $resource );
- my $types = join( ',', @types );
- my $arrow = @types ? ' => ' : ' =>';
- App::Ack::print( $resource->name, $arrow, join( ',', @types ), $ors );
-
- return;
-}
-
-# Set default colors, load Term::ANSIColor
-sub load_colors {
- eval 'use Term::ANSIColor 1.10 ()';
- eval 'use Win32::Console::ANSI' if $App::Ack::is_windows;
-
- $ENV{ACK_COLOR_MATCH} ||= 'black on_yellow';
- $ENV{ACK_COLOR_FILENAME} ||= 'bold green';
- $ENV{ACK_COLOR_LINENO} ||= 'bold yellow';
-
- return;
-}
-
-sub filetypes {
- my ( $resource ) = @_;
-
- my @matches;
-
- foreach my $k (keys %App::Ack::mappings) {
- my $filters = $App::Ack::mappings{$k};
-
- foreach my $filter (@{$filters}) {
- # Clone the resource.
- my $clone = $resource->clone;
- if ( $filter->filter($clone) ) {
- push @matches, $k;
- last;
- }
- }
- }
-
- # http://search.cpan.org/dist/Perl-Critic/lib/Perl/Critic/Policy/Subroutines/ProhibitReturnSort.pm
- @matches = sort @matches;
- return @matches;
-}
# Returns a (fairly) unique identifier for a file.
# Use this function to compare two files to see if they're
}
else {
# XXX Is this the best method? It always hits the FS.
- if( my ( $dev, $inode ) = (stat($filename))[0, 1] ) {
+ if ( my ( $dev, $inode ) = (stat($filename))[0, 1] ) {
return join(':', $dev, $inode);
}
else {
defined $str or App::Ack::die( 'No regular expression found.' );
+ if ( !$opt->{Q} ) {
+ # Compile the regex to see if it dies or throws warnings.
+ local $SIG{__WARN__} = sub { 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" );
+ }
+ }
+ }
+
+ # Check for lowercaseness before we do any modifications.
+ my $regex_is_lc = App::Ack::is_lowercase( $str );
+
$str = quotemeta( $str ) if $opt->{Q};
+
+ my $scan_str = $str;
+
+ # Whole words only.
if ( $opt->{w} ) {
- my $pristine_str = $str;
+ my $ok = 1;
- $str = "(?:$str)";
- $str = "\\b$str" if $pristine_str =~ /^\w/;
- $str = "$str\\b" if $pristine_str =~ /\w$/;
- }
+ 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.' );
+ }
- my $regex_is_lc = $str eq lc $str;
- if ( $opt->{i} || ($opt->{smart_case} && $regex_is_lc) ) {
- $str = "(?i)$str";
+ 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 );
+ }
}
- my $re = eval { qr/$str/m };
- if ( !$re ) {
- die "Invalid regex '$str':\n $@";
+ if ( $opt->{i} || ($opt->{S} && $regex_is_lc) ) {
+ $_ = "(?i)$_" for ( $str, $scan_str );
}
- return $re;
+ 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);
}
-my $match_column_number;
+my $match_colno;
{
my $after_context_pending;
# Number of latest line that got printed
-my $printed_line_no;
-
-my $is_iterating;
+my $printed_lineno;
my $is_first_match;
-my $has_printed_something;
+state $has_printed_from_any_file = 0;
-BEGIN {
- $has_printed_something = 0;
-}
-# Set up context tracking variables.
-sub set_up_line_context {
- $n_before_ctx_lines = $opt_output ? 0 : ($opt_before_context || 0);
- $n_after_ctx_lines = $opt_output ? 0 : ($opt_after_context || 0);
+sub file_loop_normal {
+ my $files = shift;
+
+ $n_before_ctx_lines = $opt_output ? 0 : ($opt_B || 0);
+ $n_after_ctx_lines = $opt_output ? 0 : ($opt_A || 0);
@before_context_buf = (undef) x $n_before_ctx_lines;
$before_context_pos = 0;
$is_first_match = 1;
- return;
-}
-
-# Adjust context tracking variables when entering a new file.
-sub set_up_line_context_for_file {
- $printed_line_no = 0;
- $after_context_pending = 0;
- if ( $opt_heading && !$opt_lines ) {
- $is_first_match = 1;
+ 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( $scan_re ) ) {
+ $file->reset();
+ }
+ else {
+ $needs_line_scan = 0;
+ }
+ }
+ if ( $needs_line_scan ) {
+ $stats{linescans}++;
+ $nmatches += print_matches_in_file( $file );
+ }
+ last if $opt_1 && $nmatches;
}
- return;
+ return $nmatches;
}
-=begin Developers
-
-This subroutine jumps through a number of optimization hoops to
-try to be fast in the more common use cases of ack. For one thing,
-in non-context tracking searches (not using -A, -B, or -C),
-conditions that normally would be checked inside the loop happen
-outside, resulting in three nearly identical loops for -v, --passthru,
-and normal searching. Any changes that happen to one should propagate
-to the others if they make sense. The non-context branches also inline
-does_match for performance reasons; any relevant changes that happen here
-must also happen there.
-
-=end Developers
-
-=cut
-
-sub print_matches_in_resource {
- my ( $resource ) = @_;
- my $max_count = $opt_m || -1;
- my $nmatches = 0;
- my $filename = $resource->name;
- my $ors = $opt_print0 ? "\0" : "\n";
+sub print_matches_in_file {
+ my $file = shift;
- my $has_printed_for_this_resource = 0;
+ my $max_count = $opt_m || -1; # Go negative for no limit so it can never reduce to 0.
+ my $nmatches = 0;
+ my $filename = $file->name;
- $is_iterating = 1;
+ my $has_printed_from_this_file = 0;
- my $fh = $resource->open();
+ my $fh = $file->open;
if ( !$fh ) {
if ( $App::Ack::report_bad_filenames ) {
App::Ack::warn( "$filename: $!" );
# Check for context before the main loop, so we don't pay for it if we don't need it.
if ( $is_tracking_context ) {
+ local $_ = undef;
+
$after_context_pending = 0;
+
+ my $in_range = range_setup();
+
while ( <$fh> ) {
- if ( does_match( $_ ) && $max_count ) {
- if ( !$has_printed_for_this_resource ) {
- if ( $opt_break && $has_printed_something ) {
- App::Ack::print_blank_line();
- }
- if ( $opt_show_filename && $opt_heading ) {
- App::Ack::print_filename( $display_filename, $ors );
- }
+ chomp;
+ $match_colno = undef;
+
+ $in_range = 1 if ( $using_ranges && !$in_range && $opt_range_start && /$opt_range_start/o );
+
+ my $does_match;
+ if ( $in_range ) {
+ if ( $opt_v ) {
+ $does_match = !/$search_re/o;
+ }
+ else {
+ if ( $does_match = /$search_re/o ) {
+ # @- = @LAST_MATCH_START
+ # @+ = @LAST_MATCH_END
+ $match_colno = $-[0] + 1;
+ }
+ }
+ }
+
+ 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_for_this_resource = 1;
+ $has_printed_from_this_file = 1;
+ $stats{linematches}++;
$nmatches++;
$max_count--;
}
- elsif ( $opt_passthru ) {
- chomp; # XXX Proper newline handling?
- # XXX Inline this call?
- if ( $opt_break && !$has_printed_for_this_resource && $has_printed_something ) {
- App::Ack::print_blank_line();
- }
- print_line_with_options( $filename, $_, $., ':' );
- $has_printed_for_this_resource = 1;
- }
else {
- chomp; # XXX Proper newline handling?
- print_line_if_context( $filename, $_, $., '-' );
+ 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;
+ }
}
+ $in_range = 0 if ( $using_ranges && $in_range && $opt_range_end && /$opt_range_end/o );
+
last if ($max_count == 0) && ($after_context_pending == 0);
}
}
- else {
- if ( $opt_passthru ) {
- local $_;
+ elsif ( $opt_passthru ) {
+ local $_ = undef;
- while ( <$fh> ) {
- $match_column_number = undef;
- if ( $opt_v ? !/$opt_regex/o : /$opt_regex/o ) {
- if ( !$opt_v ) {
- $match_column_number = $-[0] + 1;
- }
- if ( !$has_printed_for_this_resource ) {
- if ( $opt_break && $has_printed_something ) {
- App::Ack::print_blank_line();
- }
- if ( $opt_show_filename && $opt_heading ) {
- App::Ack::print_filename( $display_filename, $ors );
- }
- }
- print_line_with_context( $filename, $_, $. );
- $has_printed_for_this_resource = 1;
- $nmatches++;
- $max_count--;
+ my $in_range = range_setup();
+
+ while ( <$fh> ) {
+ chomp;
+
+ $in_range = 1 if ( $using_ranges && !$in_range && $opt_range_start && /$opt_range_start/o );
+
+ $match_colno = undef;
+ if ( $in_range && ($opt_v xor /$search_re/o) ) {
+ if ( !$opt_v ) {
+ $match_colno = $-[0] + 1;
}
- else {
- chomp; # XXX proper newline handling?
- if ( $opt_break && !$has_printed_for_this_resource && $has_printed_something ) {
+ if ( !$has_printed_from_this_file ) {
+ if ( $opt_break && $has_printed_from_any_file ) {
App::Ack::print_blank_line();
}
- print_line_with_options( $filename, $_, $., ':' );
- $has_printed_for_this_resource = 1;
+ 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();
}
- last unless $max_count != 0;
+ print_line_with_options( $filename, $_, $., '-', 1 );
+ $has_printed_from_this_file = 1;
}
+
+ $in_range = 0 if ( $using_ranges && $in_range && $opt_range_end && /$opt_range_end/o );
+
+ last if $max_count == 0;
}
- elsif ( $opt_v ) {
- local $_;
+ }
+ elsif ( $opt_v ) {
+ local $_ = undef;
- $match_column_number = undef;
- while ( <$fh> ) {
- if ( !/$opt_regex/o ) {
- if ( !$has_printed_for_this_resource ) {
- if ( $opt_break && $has_printed_something ) {
+ $match_colno = undef;
+ my $in_range = range_setup();
+
+ while ( <$fh> ) {
+ chomp;
+
+ $in_range = 1 if ( $using_ranges && !$in_range && $opt_range_start && /$opt_range_start/o );
+
+ if ( $in_range ) {
+ if ( !/$search_re/o ) {
+ 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::print_filename( $display_filename, $ors );
+ App::Ack::say( $display_filename );
}
}
print_line_with_context( $filename, $_, $. );
- $has_printed_for_this_resource = 1;
+ $has_printed_from_this_file = 1;
$nmatches++;
$max_count--;
}
- last unless $max_count != 0;
}
+
+ $in_range = 0 if ( $using_ranges && $in_range && $opt_range_end && /$opt_range_end/o );
+
+ last if $max_count == 0;
}
- else {
- local $_;
+ }
+ else { # Normal search: No context, no -v, no --passthru
+ local $_ = undef;
- while ( <$fh> ) {
- $match_column_number = undef;
- if ( /$opt_regex/o ) {
- $match_column_number = $-[0] + 1;
- if ( !$has_printed_for_this_resource ) {
- if ( $opt_break && $has_printed_something ) {
+ my $last_match_lineno;
+ my $in_range = range_setup();
+
+ while ( <$fh> ) {
+ chomp;
+
+ $in_range = 1 if ( $using_ranges && !$in_range && $opt_range_start && /$opt_range_start/o );
+
+ if ( $in_range ) {
+ $match_colno = undef;
+ if ( /$search_re/o ) {
+ $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::print_filename( $display_filename, $ors );
+ 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]+$//g;
+ s/[\r\n]+$//;
print_line_with_options( $filename, $_, $., ':' );
- $has_printed_for_this_resource = 1;
+ $has_printed_from_this_file = 1;
$nmatches++;
+ $stats{linematches}++;
$max_count--;
+ $last_match_lineno = $.;
}
- last unless $max_count != 0;
}
- }
- }
+ $in_range = 0 if ( $using_ranges && $in_range && $opt_range_end && /$opt_range_end/o );
- $is_iterating = 0;
+ last if $max_count == 0;
+ }
+ }
return $nmatches;
}
-sub print_line_with_options {
- my ( $filename, $line, $line_no, $separator ) = @_;
- $has_printed_something = 1;
- $printed_line_no = $line_no;
+sub print_line_with_options {
+ my ( $filename, $line, $lineno, $separator, $skip_coloring ) = @_;
- my $ors = $opt_print0 ? "\0" : "\n";
+ $has_printed_from_any_file = 1;
+ $printed_lineno = $lineno;
my @line_parts;
- if( $opt_color ) {
- $filename = Term::ANSIColor::colored($filename,
- $ENV{ACK_COLOR_FILENAME});
- $line_no = Term::ANSIColor::colored($line_no,
- $ENV{ACK_COLOR_LINENO});
- }
-
- if($opt_show_filename) {
- if( $opt_heading ) {
- push @line_parts, $line_no;
- }
- else {
- push @line_parts, $filename, $line_no;
+ if ( $opt_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_column ) {
- push @line_parts, get_match_column();
+ if ( $opt_heading ) {
+ push @line_parts, $lineno;
}
- }
- if( $opt_output ) {
- while ( $line =~ /$opt_regex/og ) {
- # XXX We need to stop using eval() for --output. See https://github.com/beyondgrep/ack2/issues/421
- my $output = eval $opt_output;
- App::Ack::print( join( $separator, @line_parts, $output ), $ors );
+ else {
+ push @line_parts, $filename, $lineno;
}
+ push @line_parts, $colno if $opt_column;
}
- else {
- if ( $opt_color ) {
- # This match is redundant, but we need to perfom it in order to get if capture groups are set.
- $line =~ /$opt_regex/o;
- if ( @+ > 1 ) { # If we have captures...
- while ( $line =~ /$opt_regex/og ) {
- my $offset = 0; # Additional offset for when we add stuff.
- my $previous_match_end = 0;
+ if ( $opt_output ) {
+ while ( $line =~ /$search_re/og ) {
+ my $output = $opt_output;
+ if ( @special_vars_used_by_opt_output ) {
+ no strict;
- last if $-[0] == $+[0];
+ # Stash copies of the special variables because we can't rely
+ # on them not changing in the process of doing the s///.
- for ( my $i = 1; $i < @+; $i++ ) {
- my ( $match_start, $match_end ) = ( $-[$i], $+[$i] );
-
- next unless defined($match_start);
- next if $match_start < $previous_match_end;
-
- my $substring = substr( $line,
- $offset + $match_start, $match_end - $match_start );
- my $substitution = Term::ANSIColor::colored( $substring,
- $ENV{ACK_COLOR_MATCH} );
-
- substr( $line, $offset + $match_start,
- $match_end - $match_start, $substitution );
-
- $previous_match_end = $match_end; # Offsets do not need to be applied.
- $offset += length( $substitution ) - length( $substring );
- }
-
- pos($line) = $+[0] + $offset;
- }
+ 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;
}
- else {
- my $matched = 0; # If matched, need to escape afterwards.
-
- while ( $line =~ /$opt_regex/og ) {
-
- $matched = 1;
- my ( $match_start, $match_end ) = ($-[0], $+[0]);
- next unless defined($match_start);
- last if $match_start == $match_end;
+ App::Ack::say( join( $separator, @line_parts, $output ) );
+ }
+ }
+ else {
+ my $underline = '';
- my $substring = substr( $line, $match_start,
- $match_end - $match_start );
- my $substitution = Term::ANSIColor::colored( $substring,
- $ENV{ACK_COLOR_MATCH} );
+ # We have to do underlining before any highlighting because highlighting modifies string length.
+ if ( $opt_underline && !$skip_coloring ) {
+ while ( $line =~ /$search_re/og ) {
+ my $match_start = $-[0] // next;
+ my $match_end = $+[0];
+ my $match_length = $match_end - $match_start;
+ last if $match_length <= 0;
- substr( $line, $match_start, $match_end - $match_start,
- $substitution );
+ my $spaces_needed = $match_start - length $underline;
- pos($line) = $match_end +
- (length( $substitution ) - length( $substring ));
- }
- # XXX Why do we do this?
- $line .= "\033[0m\033[K" if $matched;
+ $underline .= (' ' x $spaces_needed);
+ $underline .= ('^' x $match_length);
}
}
+ if ( $opt_color && !$skip_coloring ) {
+ my $highlighted = 0; # If highlighted, need to escape afterwards.
- push @line_parts, $line;
- App::Ack::print( join( $separator, @line_parts ), $ors );
- }
-
- return;
-}
+ while ( $line =~ /$search_re/og ) {
+ my $match_start = $-[0] // next;
+ my $match_end = $+[0];
+ my $match_length = $match_end - $match_start;
+ last if $match_length <= 0;
-sub iterate {
- my ( $resource, $cb ) = @_;
+ my $substring = substr( $line, $match_start, $match_length );
+ my $substitution = Term::ANSIColor::colored( $substring, $ENV{ACK_COLOR_MATCH} );
- $is_iterating = 1;
+ # Fourth argument replaces the string specified by the first three.
+ substr( $line, $match_start, $match_length, $substitution );
- my $fh = $resource->open();
- if ( !$fh ) {
- if ( $App::Ack::report_bad_filenames ) {
- App::Ack::warn( $resource->name . ': ' . $! );
+ # 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;
}
- return;
- }
- # Check for context before the main loop, so we don't pay for it if we don't need it.
- if ( $is_tracking_context ) {
- $after_context_pending = 0;
+ 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;
+ }
+ }
- while ( <$fh> ) {
- last unless $cb->();
- }
- }
- else {
- local $_;
+ 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;
- while ( <$fh> ) {
- last unless $cb->();
+ App::Ack::print( ' ' x $spaces_needed );
+ }
+ App::Ack::say( $underline );
}
}
- $is_iterating = 0;
-
return;
}
sub print_line_with_context {
- my ( $filename, $matching_line, $line_no ) = @_;
-
- my $ors = $opt_print0 ? "\0" : "\n";
- my $is_tracking_context = $opt_after_context || $opt_before_context;
+ my ( $filename, $matching_line, $lineno ) = @_;
- $matching_line =~ s/[\r\n]+$//g;
+ $matching_line =~ s/[\r\n]+$//;
# Check if we need to print context lines first.
- if ( $is_tracking_context ) {
- my $before_unprinted = $line_no - $printed_line_no - 1;
- if ( !$is_first_match && ( !$printed_line_no || $before_unprinted > $n_before_ctx_lines ) ) {
- App::Ack::print('--', $ors);
+ 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( '--' );
}
# We want at most $n_before_ctx_lines of context.
# Disable $opt->{column} since there are no matches in the context lines.
local $opt_column = 0;
- print_line_with_options( $filename, $line, $line_no-$before_unprinted, '-' );
+ print_line_with_options( $filename, $line, $lineno-$before_unprinted, '-' );
$before_unprinted--;
}
}
- print_line_with_options( $filename, $matching_line, $line_no, ':' );
+ print_line_with_options( $filename, $matching_line, $lineno, ':' );
# We want to get the next $n_after_ctx_lines printed.
$after_context_pending = $n_after_ctx_lines;
return;
}
-# Print the line only if it's part of a context we need to display.
-sub print_line_if_context {
- my ( $filename, $line, $line_no, $separator ) = @_;
-
- if ( $after_context_pending ) {
- # Disable $opt_column since there are no matches in the context lines.
- local $opt_column = 0;
- print_line_with_options( $filename, $line, $line_no, $separator );
- --$after_context_pending;
- }
- elsif ( $n_before_ctx_lines ) {
- # Save line for "before" context.
- $before_context_buf[$before_context_pos] = $_;
- $before_context_pos = ($before_context_pos+1) % $n_before_ctx_lines;
- }
-
- return;
-}
-
}
-# does_match() MUST have an $opt_regex set.
-
-=begin Developers
-
-This subroutine is inlined a few places in print_matches_in_resource
-for performance reasons, so any changes here must be copied there as
-well.
-
-=end Developers
-
-=cut
-
-sub does_match {
- my ( $line ) = @_;
-
- $match_column_number = undef;
-
- if ( $opt_v ) {
- return ( $line !~ /$opt_regex/o );
- }
- else {
- if ( $line =~ /$opt_regex/o ) {
- # @- = @LAST_MATCH_START
- # @+ = @LAST_MATCH_END
- $match_column_number = $-[0] + 1;
- return 1;
- }
- else {
- return;
- }
- }
+sub get_match_colno {
+ return $match_colno;
}
-sub get_match_column {
- return $match_column_number;
-}
+sub count_matches_in_file {
+ my $file = shift;
+ my $bail = shift; # True if we're just checking for existence.
-sub resource_has_match {
- my ( $resource ) = @_;
+ my $nmatches = 0;
+ my $do_scan = 1;
- my $has_match = 0;
- my $fh = $resource->open();
- if ( !$fh ) {
+ if ( !$file->open() ) {
+ $do_scan = 0;
if ( $App::Ack::report_bad_filenames ) {
- App::Ack::warn( $resource->name . ': ' . $! );
+ App::Ack::warn( $file->name . ": $!" );
}
}
else {
- while ( <$fh> ) {
- if (/$opt_regex/o xor $opt_v) {
- $has_match = 1;
- last;
+ if ( !$opt_v ) {
+ if ( !$file->may_be_present( $scan_re ) ) {
+ $do_scan = 0;
}
}
- close $fh;
}
- return $has_match;
-}
+ if ( $do_scan ) {
+ $file->reset();
-sub count_matches_in_resource {
- my ( $resource ) = @_;
+ my $in_range = range_setup();
- my $nmatches = 0;
- my $fh = $resource->open();
- if ( !$fh ) {
- if ( $App::Ack::report_bad_filenames ) {
- App::Ack::warn( $resource->name . ': ' . $! );
+ my $fh = $file->{fh};
+ if ( $using_ranges ) {
+ while ( <$fh> ) {
+ chomp;
+ $in_range = 1 if ( !$in_range && $opt_range_start && /$opt_range_start/o );
+ if ( $in_range ) {
+ if ( /$search_re/o xor $opt_v ) {
+ ++$nmatches;
+ last if $bail;
+ }
+ }
+ $in_range = 0 if ( $in_range && $opt_range_end && /$opt_range_end/o );
+ }
}
- }
- else {
- while ( <$fh> ) {
- ++$nmatches if (/$opt_regex/o xor $opt_v);
+ else {
+ while ( <$fh> ) {
+ chomp;
+ if ( /$search_re/o xor $opt_v ) {
+ ++$nmatches;
+ last if $bail;
+ }
+ }
}
- close $fh;
}
+ $file->close;
return $nmatches;
}
-sub main {
- my @arg_sources = App::Ack::ConfigLoader::retrieve_arg_sources();
- my $opt = App::Ack::ConfigLoader::process_args( @arg_sources );
+sub range_setup {
+ return !$using_ranges || (!$opt_range_start && $opt_range_end);
+}
- $opt_after_context = $opt->{after_context};
- $opt_before_context = $opt->{before_context};
- $opt_output = $opt->{output};
- $opt_print0 = $opt->{print0};
- $opt_color = $opt->{color};
- $opt_heading = $opt->{heading};
- $opt_show_filename = $opt->{show_filename};
- $opt_regex = $opt->{regex};
- $opt_break = $opt->{break};
- $opt_count = $opt->{count};
- $opt_v = $opt->{v};
- $opt_m = $opt->{m};
- $opt_g = $opt->{g};
- $opt_f = $opt->{f};
- $opt_lines = $opt->{lines};
- $opt_L = $opt->{L};
- $opt_l = $opt->{l};
- $opt_passthru = $opt->{passthru};
- $opt_column = $opt->{column};
- $App::Ack::report_bad_filenames = !$opt->{dont_report_bad_filenames};
+=pod
- if ( $opt->{flush} ) {
- $| = 1;
- }
+=encoding UTF-8
- if ( !defined($opt_color) && !$opt_g ) {
- my $windows_color = 1;
- if ( $App::Ack::is_windows ) {
- $windows_color = eval { require Win32::Console::ANSI; };
- }
- $opt_color = !App::Ack::output_to_pipe() && $windows_color;
- }
- if ( not defined $opt_heading and not defined $opt_break ) {
- $opt_heading = $opt_break = $opt->{break} = !App::Ack::output_to_pipe();
- }
+=head1 NAME
- if ( defined($opt->{H}) || defined($opt->{h}) ) {
- $opt_show_filename = $opt->{show_filename} = $opt->{H} && !$opt->{h};
- }
+ack - grep-like text finder
- if ( my $output = $opt_output ) {
- $output =~ s{\\}{\\\\}g;
- $output =~ s{"}{\\"}g;
- $opt_output = qq{"$output"};
- }
+=head1 SYNOPSIS
- my $resources;
- if ( $App::Ack::is_filter_mode && !$opt->{files_from} ) { # probably -x
- $resources = App::Ack::Resources->from_stdin( $opt );
- $opt_regex = shift @ARGV if not defined $opt_regex;
- $opt_regex = $opt->{regex} = build_regex( $opt_regex, $opt );
- }
- else {
- if ( $opt_f || $opt_lines ) {
- if ( $opt_regex ) {
- App::Ack::warn( "regex ($opt_regex) specified with -f or --lines" );
- App::Ack::exit_from_ack( 0 ); # XXX the 0 is misleading
- }
- }
- else {
- $opt_regex = shift @ARGV if not defined $opt_regex;
- $opt_regex = $opt->{regex} = build_regex( $opt_regex, $opt );
- }
- if ( $opt_regex && $opt_regex =~ /\n/ ) {
- App::Ack::exit_from_ack( 0 );
- }
- my @start;
- if ( not defined $opt->{files_from} ) {
- @start = @ARGV;
- }
- if ( !exists($opt->{show_filename}) ) {
- unless(@start == 1 && !(-d $start[0])) {
- $opt_show_filename = $opt->{show_filename} = 1;
- }
- }
+ ack [options] PATTERN [FILE...]
+ ack -f [options] [DIRECTORY...]
- if ( defined $opt->{files_from} ) {
- $resources = App::Ack::Resources->from_file( $opt, $opt->{files_from} );
- exit 1 unless $resources;
- }
- else {
- @start = ('.') unless @start;
- foreach my $target (@start) {
- if ( !-e $target && $App::Ack::report_bad_filenames) {
- App::Ack::warn( "$target: No such file or directory" );
- }
- }
+=head1 DESCRIPTION
- $opt->{file_filter} = _compile_file_filter($opt, \@start);
- $opt->{descend_filter} = _compile_descend_filter($opt);
+ack is designed as an alternative to F<grep> for programmers.
- $resources = App::Ack::Resources->from_argv( $opt, \@start );
- }
- }
- App::Ack::set_up_pager( $opt->{pager} ) if defined $opt->{pager};
+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.
- my $ors = $opt_print0 ? "\0" : "\n";
- my $only_first = $opt->{1};
+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.
- my $nmatches = 0;
- my $total_count = 0;
+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.
- set_up_line_context();
+=head1 FILE SELECTION
-RESOURCES:
- while ( my $resource = $resources->next ) {
- if ($is_tracking_context) {
- set_up_line_context_for_file();
- }
-
- if ( $opt_f ) {
- if ( $opt->{show_types} ) {
- show_types( $resource, $ors );
- }
- else {
- App::Ack::print( $resource->name, $ors );
- }
- ++$nmatches;
- last RESOURCES if defined($opt_m) && $nmatches >= $opt_m;
- }
- elsif ( $opt_g ) {
- if ( $opt->{show_types} ) {
- show_types( $resource, $ors );
- }
- else {
- local $opt_show_filename = 0; # XXX Why is this local?
-
- print_line_with_options( '', $resource->name, 0, $ors );
- }
- ++$nmatches;
- last RESOURCES if defined($opt_m) && $nmatches >= $opt_m;
- }
- elsif ( $opt_lines ) {
- my %line_numbers;
- foreach my $line ( @{ $opt_lines } ) {
- my @lines = split /,/, $line;
- @lines = map {
- /^(\d+)-(\d+)$/
- ? ( $1 .. $2 )
- : $_
- } @lines;
- @line_numbers{@lines} = (1) x @lines;
- }
-
- my $filename = $resource->name;
-
- local $opt_color = 0;
-
- iterate( $resource, sub {
- chomp;
-
- if ( $line_numbers{$.} ) {
- print_line_with_context( $filename, $_, $. );
- }
- elsif ( $opt_passthru ) {
- print_line_with_options( $filename, $_, $., ':' );
- }
- elsif ( $is_tracking_context ) {
- print_line_if_context( $filename, $_, $., '-' );
- }
- return 1;
- });
- }
- elsif ( $opt_count ) {
- my $matches_for_this_file = count_matches_in_resource( $resource );
-
- if ( not $opt_show_filename ) {
- $total_count += $matches_for_this_file;
- next RESOURCES;
- }
-
- if ( !$opt_l || $matches_for_this_file > 0) {
- if ( $opt_show_filename ) {
- App::Ack::print( $resource->name, ':', $matches_for_this_file, $ors );
- }
- else {
- App::Ack::print( $matches_for_this_file, $ors );
- }
- }
- }
- elsif ( $opt_l || $opt_L ) {
- my $is_match = resource_has_match( $resource );
-
- if ( $opt_L ? !$is_match : $is_match ) {
- App::Ack::print( $resource->name, $ors );
- ++$nmatches;
-
- last RESOURCES if $only_first;
- last RESOURCES if defined($opt_m) && $nmatches >= $opt_m;
- }
- }
- else {
- $nmatches += print_matches_in_resource( $resource, $opt );
- if ( $nmatches && $only_first ) {
- last RESOURCES;
- }
- }
- }
-
- if ( $opt_count && !$opt_show_filename ) {
- App::Ack::print( $total_count, "\n" );
- }
-
- close $App::Ack::fh;
- App::Ack::exit_from_ack( $nmatches );
-}
-
-=pod
-
-=encoding UTF-8
-
-=head1 NAME
-
-ack - grep-like text finder
-
-=head1 SYNOPSIS
-
- ack [options] PATTERN [FILE...]
- ack -f [options] [DIRECTORY...]
-
-=head1 DESCRIPTION
-
-ack is designed as an alternative to F<grep> for programmers.
-
-ack searches the named input files or directories 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.
-
-PATTERN is a Perl regular expression. Perl regular expressions
-are commonly found in other programming languages, but for the particulars
-of their behavior, please consult
-L<http://perldoc.perl.org/perlreref.html|perlreref>. If you don't know
-how to use regular expression but are interested in learning, you may
-consult L<http://perldoc.perl.org/perlretut.html|perlretut>. If you do not
-need or want ack to use regular expressions, please see the
-C<-Q>/C<--literal> option.
-
-Ack can also list files that would be searched, without actually
-searching them, to let you take advantage of ack's file-type filtering
-capabilities.
-
-=head1 FILE SELECTION
-
-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.
+If files are not specified for searching, either on the command
+line or piped in with the C<-x> option, I<ack> delves into
+subdirectories selecting files for searching.
I<ack> is intelligent about the files it searches. It knows about
certain file types, based on both the extension on the file and,
For a complete list of directories that do not get searched, run
C<ack --dump>.
-=head1 WHEN TO USE GREP
+=head1 MATCHING IN A RANGE OF LINES
+
+The C<--range-start> and C<--range-end> options let you specify ranges of
+lines to search within each file.
+
+Say you had the following file, called F<testfile>:
+
+ # This function calls print on "foo".
+ sub foo {
+ print 'foo';
+ }
+ my $print = 1;
+ sub bar {
+ print 'bar';
+ }
+ my $task = 'print';
+
+Calling C<ack print> will give us five matches:
+
+ $ ack print testfile
+ # This function calls print on "foo".
+ print 'foo';
+ my $print = 1;
+ print 'bar';
+ my $task = 'print';
+
+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<^}>.
+
+ $ ack --range-start='^sub \w+' --range-end='^}' print testfile
+ print 'foo';
+ print 'bar';
+
+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.
+
+ 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';
+
+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 unitl 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.
+
+For example, if you wanted to search all HTML files up until the first
+instance of the C<< <body> >>, you could do
-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.
+ ack foo --range-end='<body>'
-E.g., searching through huge files looking for regexes that can be
-expressed with I<grep> syntax should be quicker with I<grep>.
+Or to search after Perl's `__DATA__` or `__END__` markers, you would do
-If your script or parent program uses I<grep> C<--quiet> or C<--silent>
-or needs exit 2 on IO error, use I<grep>.
+ ack pattern --range-end='^__(END|DATA)__'
+
+It's possible for a range to start and stop on the same line. For example
+
+ --range-start='<title>' --range-end='</title>'
+
+would match this line as both the start and end of the range, making a
+one-line range.
+
+ <title>Page title</title>
+
+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.
+
+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.
+
+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:
+
+ ack google -v --html --range-start='<head' --range-end='</head>'
+
+Specifying a range to search does not affect how matches are displayed.
+The context for a match will still be the same, and
+
+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.
=head1 OPTIONS
Sets the color to be used for matches.
+=item B<--color-colno=I<color>>
+
+Sets the color to be used for column numbers.
+
=item B<--color-lineno=I<color>>
Sets the color to be used for line numbers.
files are separated by newlines. If I<FILE> is C<->, the list is loaded
from standard input.
+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.
+
+
=item B<--[no]filter>
Forces ack to act as if it were receiving input via a pipe.
Print a filename heading above each file's results. This is the default
when used interactively.
-=item B<--help>, B<-?>
+=item B<--help>
Print a short help statement.
-=item B<--help-types>, B<--help=types>
+=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
+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>
are taken into account by ack unless given explicitly on the command
line.
-=item B<--ignore-file=I<FILTERTYPE:FILTERARGS>>
+=item B<--ignore-file=I<FILTER:ARGS>>
-Ignore files matching I<FILTERTYPE:FILTERARGS>. The filters are specified
+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. This is
-equivalent to the default behavior found in ack 1.
-
-=item B<--lines=I<NUM>>
-
-Only print line I<NUM> of each file. Multiple lines can be given with multiple
-B<--lines> options or as a comma separated list (B<--lines=3,5,7>). B<--lines=4-7>
-also works. The lines are always output in ascending order, no matter the
-order given on the command line.
+Limit selected files to those with types that ack knows about.
=item B<-l>, B<--files-with-matches>
=item B<-m=I<NUM>>, B<--max-count=I<NUM>>
-Stop reading a file after I<NUM> matches.
+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>
=item B<-o>
Show only the part of each line matching PATTERN (turns off text
-highlighting)
+highlighting). This is exactly the same as C<--output=$&>.
=item B<--output=I<expr>>
Output the evaluation of I<expr> for each line (turns off text
-highlighting)
-If PATTERN matches more than once then a line is output for each non-overlapping match.
-For more information please see the section L</"Examples of F<--output>">.
+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 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>
Using --pager does not suppress grouping and coloring like piping
output on the command-line does.
-B<--nopager> cancels any setting in ~/.ackrc, C<ACK_PAGER> or C<ACK_PAGER_COLOR>.
+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>
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
+ # 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 -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.
+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
+ # 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.
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<--[no]smart-case>, B<--no-smart-case>
+=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 vim. This option is
-off by default, and ignored if C<-i> is specified.
+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.
Works with B<-f> and B<-g> options.
-=item B<--type=[no]TYPE>
+=item B<-t TYPE>, B<--type=TYPE>, B<--TYPE>
-Specify the types of files to include or exclude from a search.
+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>, and B<--type=noperl> can be done
-as B<--noperl>.
+also be specified as B<--perl>, although this is deprecated.
+
+Type inclusions can be repeated and are ORed together.
-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.
+See I<ack --help-types> for a list of valid types.
-Type specifications can be repeated and are ORed together.
+=item B<-T TYPE>, B<--type=noTYPE>, B<--noTYPE>
-See I<ack --help=types> for a list of valid types.
+Specifies the type of files to exclude from the search. B<--type=noperl>
+can be done as B<--noperl>, although this is deprecated.
-=item B<--type-add I<TYPE>:I<FILTER>:I<FILTERARGS>>
+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.
-Files with the given FILTERARGS applied to the given FILTER
+=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>>
-=item B<--type-set I<TYPE>:I<FILTER>:I<FILTERARGS>>
-
-Files with the given FILTERARGS applied to the given FILTER are recognized as
+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">.
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
+Invert match: select non-matching lines.
=item B<--version>
=item B<-w>, B<--word-regexp>
-=item B<-w>, B<--word-regexp>
-
-Turn on "words mode". This sometimes matches a whole word, but the
-semantics is quite subtle. If the passed regexp begins with a word
-character, then a word boundary is required before the match. If the
-passed regexp ends with a word character, or with a word character
-followed by newline, then a word boundary is required after the match.
-
-Thus, for example, B<-w> with the regular expression C<ox> will not
-match the strings C<box> or C<oxen>. However, if the regular
-expression is C<(ox|ass)> then it will match those strings. Because
-the regular expression's first character is C<(>, the B<-w> flag has
-no effect at the start, and because the last character is C<)>, it has
-no effect at the end.
-
-Force PATTERN to match only whole words. The PATTERN is wrapped with
-C<\b> metacharacters.
+Force PATTERN to match only whole words.
=item B<-x>
-An abbreviation for B<--files-from=->; the list of files to search are read
+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
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>
+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>
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 (like ack 1.x allowed), ack 2
-offers additional filter types. The generic syntax is
-I<--type-set TYPE:FILTER:FILTERARGS>; I<FILTERARGS> depends on the value
+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 match:I<PATTERN>
I<match> filters match the target filename against a regular expression.
-The regular expression is made case insensitive for the search.
+The regular expression is made case-insensitive for the search.
Example:
=back
-More filter types may be made available in the future.
+=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
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_OPTIONS
+=item ACK_COLOR_COLNO
-This variable specifies default options to be placed in front of
-any explicit options on the command line.
+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
-Specifies the color of the filename when it's printed in B<--group>
-mode. By default, it's "bold green".
-
-The recognized attributes are clear, reset, dark, bold, underline,
-underscore, blink, reverse, concealed black, red, green, yellow,
-blue, magenta, on_black, on_red, on_green, on_yellow, on_blue,
-on_magenta, on_cyan, and on_white. Case is not significant.
-Underline and underscore are equivalent, as are clear and reset.
-The color alone sets the foreground color, and on_color sets the
-background color.
-
-This option can also be set with B<--color-filename>.
-
-=item ACK_COLOR_MATCH
-
-Specifies the color of the matching text when printed in B<--color>
-mode. By default, it's "black on_yellow".
-
-This option can also be set with B<--color-match>.
-
-See B<ACK_COLOR_FILENAME> for the color specifications.
+Color specification for the filename in ack's output. See the section "ack
+Colors" above.
=item ACK_COLOR_LINENO
-Specifies the color of the line number when printed in B<--color>
-mode. By default, it's "bold yellow".
+Color specification for the line number in ack's output. See the section
+"ack Colors" above.
-This option can also be set with B<--color-lineno>.
+=item ACK_COLOR_MATCH
-See B<ACK_COLOR_FILENAME> for the color specifications.
+Color specification for the matched text in ack's output. See the section
+"ack Colors" above.
=item ACK_PAGER
=back
-=head1 AVAILABLE COLORS
-
-F<ack> 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.
-
-=head2 Foreground colors
-
- black red green yellow blue magenta cyan white
-
- bright_black bright_red bright_green bright_yellow
- bright_blue bright_magenta bright_cyan bright_white
-
-=head2 Background colors
-
- on_black on_red on_green on_yellow
- on_blue on_magenta on_cyan on_white
-
- on_bright_black on_bright_red on_bright_green on_bright_yellow
- on_bright_blue on_bright_magenta on_bright_cyan on_bright_white
-
=head1 ACK & OTHER TOOLS
=head2 Simple vim integration
If ack gives you output you're not expecting, start with a few simple steps.
-=head2 Use B<--noenv>
+=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
+=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
=head2 Use B<--dump>
This lists the ackrc files that are loaded and the options loaded
-from them.
-So for example you can find a list of directories that do not get searched or where filetypes are defined.
+from them. You may be loading an F<.ackrc> file that you didn't know
+you were loading.
-=head1 TIPS
+=head1 ACKRC LOCATION SEMANTICS
-=head2 Use the F<.ackrc> file.
+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)
-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.
+=over 4
-=head2 Use F<-f> for working with big codesets
+=item *
-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:
+Defaults are loaded from App::Ack::ConfigDefaults. This can be omitted
+using C<--ignore-ack-defaults>.
- # Change all "this" to "that" in all Perl files in a tree.
- ack -f --perl | xargs perl -p -i -e's/this/that/g'
+=item * Global ackrc
-or if you prefer:
+Options are then loaded from the global ackrc. This is located at
+C</etc/ackrc> on Unix-like systems.
- perl -p -i -e's/this/that/g' $(ack -f --perl)
+Under Windows XP and earlier, the global ackrc is at
+C<C:\Documents and Settings\All Users\Application Data\ackrc>
-=head2 Use F<-Q> when in doubt about metacharacters
+Under Windows Vista/7, the global ackrc is at
+C<C:\ProgramData\ackrc>
-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...
+The C<--noenv> option prevents all ackrc files from being loaded.
-=head2 Use ack to watch log files
+=item * User ackrc
-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.
+Options are then loaded from the user's ackrc. This is located at
+C<$HOME/.ackrc> on Unix-like systems.
- ack -Q aa.bb.cc.dd /path/to/access.log | ack -Q -B5 troublesome.gif
+Under Windows XP and earlier, the user's ackrc is at
+C<C:\Documents and Settings\$USER\Application Data\ackrc>.
-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.
+Under Windows Vista/7, the user's ackrc is at
+C<C:\Users\$USER\AppData\Roaming\ackrc>.
-=head2 Examples of F<--output>
+If you want to load a different user-level ackrc, it may be specified
+with the C<$ACKRC> environment variable.
-Following variables are useful in the expansion string:
+The C<--noenv> option prevents all ackrc files from being loaded.
-=over 4
+=item * Project ackrc
-=item C<$&>
+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>.
-The whole string matched by PATTERN.
+=item * --ackrc
-=item C<$1>, C<$2>, ...
+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.
-The contents of the 1st, 2nd ... bracketed group in PATTERN.
+=item * Command line
-=item C<$`>
+Options are then loaded from the command line.
-The string before the match.
+=back
-=item C<$'>
+=head1 BUGS & ENHANCEMENTS
-The string after the match.
+ack is based at GitHub at L<https://github.com/beyondgrep/ack3>
-=back
+Please report any bugs or feature requests to the issues list at
+Github: L<https://github.com/beyondgrep/ack3/issues>.
-For more details and other variables see
-L<http://perldoc.perl.org/perlvar.html#Variables-related-to-regular-expressions|perlvar>.
+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.
-This example shows how to add text around a particular pattern
-(in this case adding _ around word with "e")
+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.
- ack2.pl "\w*e\w*" quick.txt --output="$`_$&_$'"
- _The_ quick brown fox jumps over the lazy dog
- The quick brown fox jumps _over_ the lazy dog
- The quick brown fox jumps over _the_ lazy dog
+Also, feel free to discuss your issues on the ack mailing
+list at L<https://groups.google.com/group/ack-users>.
-This shows how to pick out particular parts of a match using ( ) within regular expression.
+=head1 SUPPORT
- ack '=head(\d+)\s+(.*)' --output=' $1 : $2'
- input file contains "=head1 NAME"
- output "1 : NAME"
+Support for and information about F<ack> can be found at:
-=head1 COMMUNITY
+=over 4
-There are ack mailing lists and a Slack channel for ack. See
-L<https://beyondgrep.com/community/> for details.
+=item * The ack homepage
-=head1 FAQ
+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)?
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.
-NOTE: If you're using an old ack before 2.0, it's probably because it's of
-a type that ack doesn't recognize. In ack 1.x, the searching behavior is
-driven by filetype. B<If ack 1.x doesn't know what kind of file it is,
-ack ignores the file.> You can use the C<--show-types> switch to show
-which type ack thinks each file is.
-
=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
=head2 Can I make ack recognize F<.xyz> files?
-Yes! Please see L</"Defining your own types">. If you think
-that F<ack> should recognize a type by default, please see
-L</"ENHANCEMENTS">.
+Yes! Please see L</"Defining your own types"> in the ack manual.
-=head2 There's already a program/package called ack.
+=head2 Will you make ack recognize F<.xyz> files by default?
-Yes, I know.
+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?
=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
-bring it up on the ack-users mailing list.
-
-=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 * ACK_OPTIONS
-
-Options are then loaded from the environment variable C<ACK_OPTIONS>. This can
-be omitted using C<--noenv>.
-
-=item * Command line
-
-Options are then loaded from the command line.
-
-=back
-
-=head1 DIFFERENCES BETWEEN ACK 1.X AND ACK 2.X
-
-A lot of changes were made for ack 2; here is a list of them.
-
-=head2 GENERAL CHANGES
-
-=over 4
-
-=item *
-
-When no selectors are specified, ack 1.x only searches through files that
-it can map to a file type. ack 2.x, by contrast, will search through
-every regular, non-binary file that is not explicitly ignored via
-B<--ignore-file> or B<--ignore-dir>. This is similar to the behavior of the
-B<-a/--all> option in ack 1.x.
-
-=item *
-
-A more flexible filter system has been added, so that more powerful file types
-may be created by the user. For details, please consult
-L</"Defining your own types">.
-
-=item *
-
-ack now loads multiple ackrc files; see L</"ACKRC LOCATION SEMANTICS"> for
-details.
-
-=item *
-
-ack's default filter definitions aren't special; you may tell ack to
-completely disregard them if you don't like them.
-
-=back
-
-=head2 REMOVED OPTIONS
-
-=over 4
-
-=item *
-
-Because of the change in default search behavior, the B<-a/--all> and
-B<-u/--unrestricted> options have been removed. In addition, the
-B<-k/--known-types> option was added to cause ack to behave with
-the default search behavior of ack 1.x.
-
-=item *
-
-The B<-G> option has been removed. Two regular expressions on the
-command line was considered too confusing; to simulate B<-G>'s functionality,
-you may use the new B<-x> option to pipe filenames from one invocation of
-ack into another.
-
-=item *
-
-The B<--binary> option has been removed.
-
-=item *
-
-The B<--skipped> option has been removed.
-
-=item *
-
-The B<--text> option has been removed.
-
-=item *
-
-The B<--invert-file-match> option has been removed. Instead, you may
-use B<-v> with B<-g>.
-
-=back
-
-=head2 CHANGED OPTIONS
-
-=over 4
-
-=item *
-
-The options that modify the regular expression's behavior (B<-i>, B<-w>,
-B<-Q>, and B<-v>) may now be used with B<-g>.
-
-=back
-
-=head2 ADDED OPTIONS
-
-=over 4
-
-=item *
-
-B<--files-from> was added so that a user may submit a list of filenames as
-a list of files to search.
-
-=item *
-
-B<-x> was added to tell ack to accept a list of filenames via standard input;
-this list is the list of filenames that will be used for the search.
-
-=item *
-
-B<-s> was added to tell ack to suppress error messages about non-existent or
-unreadable files.
-
-=item *
-
-B<--ignore-directory> and B<--noignore-directory> were added as aliases for
-B<--ignore-dir> and B<--noignore-dir> respectively.
-
-=item *
-
-B<--ignore-file> was added so that users may specify patterns of files to
-ignore (ex. /.*~$/).
-
-=item *
-
-B<--dump> was added to allow users to easily find out which options are
-set where.
-
-=item *
-
-B<--create-ackrc> was added so that users may create custom ackrc files based
-on the default settings loaded by ack, and so that users may easily view those
-defaults.
-
-=item *
-
-B<--type-del> was added to selectively remove file type definitions.
-
-=item *
-
-B<--ignore-ack-defaults> was added so that users may ignore ack's default
-options in favor of their own.
-
-=item *
-
-B<--bar> was added so ack users may consult Admiral Ackbar.
-
-=back
-
-=head1 AUTHOR
-
-Andy Lester, C<< <andy at petdance.com> >>
-
-=head1 BUGS
-
-Please report any bugs or feature requests to the issues list at
-Github: L<https://github.com/beyondgrep/ack2/issues>
-
-=head1 ENHANCEMENTS
-
-All enhancement requests MUST first be posted to the ack-users
-mailing list at L<http://groups.google.com/group/ack-users>. I
-will not consider a request without it first getting seen by other
-ack users. This includes requests for new filetypes.
-
-There is a list of enhancements I want to make to F<ack> in the ack
-issues list at Github: L<https://github.com/beyondgrep/ack2/issues>
-
-Patches are always welcome, but patches with tests get the most
-attention.
-
-=head1 SUPPORT
-
-Support for and information about F<ack> can be found at:
-
-=over 4
-
-=item * The ack homepage
-
-L<https://beyondgrep.com/>
-
-=item * The ack-users mailing list
-
-L<http://groups.google.com/group/ack-users>
-
-=item * The ack issues list at Github
-
-L<https://github.com/beyondgrep/ack2/issues>
-
-=item * AnnoCPAN: Annotated CPAN documentation
-
-L<http://annocpan.org/dist/ack>
-
-=item * CPAN Ratings
-
-L<http://cpanratings.perl.org/d/ack>
+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.
-=item * Search CPAN
-
-L<http://search.cpan.org/dist/ack>
-
-=item * MetaCPAN
-
-L<http://metacpan.org/release/ack>
-
-=item * Git source repository
-
-L<https://github.com/beyondgrep/ack2>
-
-=back
+=cut
=head1 ACKNOWLEDGEMENTS
How appropriate to have I<ack>nowledgements!
Thanks to everyone who has contributed to ack in any way, including
-Michele Campeotto,
+Dan Book,
+Tomasz Konojacki,
+Salomon Smeke,
+M. Scott Ford,
+Anders Eriksson,
H.Merijn Brand,
Duke Leto,
Gerhard Poul,
Pete Krawczyk and
Rob Hoelz.
+=head1 AUTHOR
+
+Andy Lester, C<< <andy at petdance.com> >>
+
=head1 COPYRIGHT & LICENSE
-Copyright 2005-2017 Andy Lester.
+Copyright 2005-2020 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 http://www.perlfoundation.org/artistic_license_2_0 or the LICENSE.md
+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;
our $VERSION;
our $COPYRIGHT;
BEGIN {
- $VERSION = '2.22';
- $COPYRIGHT = 'Copyright 2005-2017 Andy Lester.';
+ $VERSION = 'v3.4.0'; # Check https://beyondgrep.com/ for updates
+ $COPYRIGHT = 'Copyright 2005-2020 Andy Lester.';
}
+our $STANDALONE = 0;
+our $ORIGINAL_PROGRAM_NAME;
our $fh;
our $is_filter_mode;
our $output_to_pipe;
-our $dir_sep_chars;
-our $is_cygwin;
our $is_windows;
-use File::Spec 1.00015 ();
+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_cygwin = ($^O eq 'cygwin' || $^O eq 'msys');
$is_windows = ($^O eq 'MSWin32');
- $dir_sep_chars = $is_windows ? quotemeta( '\\/' ) : quotemeta( File::Spec->catfile( '', '' ) );
-}
-
-
-
-sub remove_dir_sep {
- my $path = shift;
- $path =~ s/[$dir_sep_chars]$//;
-
- return $path;
}
-
sub warn {
return CORE::warn( _my_program(), ': ', @_, "\n" );
}
}
-
-sub filetypes_supported {
- return keys %mappings;
-}
-
sub thpppt {
my $y = q{_ /|,\\'!.x',=(www)=, U };
$y =~ tr/,x!w/\nOo_/;
sub show_help {
- my $help_arg = shift || 0;
-
- return show_help_types() if $help_arg =~ /^types?/;
-
App::Ack::print( <<"END_OF_HELP" );
Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES]
STDIN, but only if no file or directory arguments are specified,
or if one of them is "-".
-Default switches may be specified in ACK_OPTIONS environment variable or
-an .ackrc file. If you want no dependency on the environment, turn it
-off with --noenv.
+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.
-Example: ack -i select
+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
- --[no]smart-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 is specified
+ 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.
Search output:
- --lines=NUM Only print line(s) NUM of each file
- -l, --files-with-matches Only print filenames containing matches
- -L, --files-without-matches Only print filenames with no matches
--output=expr Output the evaluation of expr for each line
(turns off text highlighting)
-o Show only the part of a line matching PATTERN
Same as --output='\$&'
--passthru Print all lines, whether matching or not
- --match PATTERN Specify PATTERN explicitly.
-m, --max-count=NUM Stop searching in each file after NUM matches
-1 Stop searching after one match of any kind
-H, --with-filename Print the filename for each match (default:
on unless explicitly searching a single file)
-h, --no-filename Suppress the prefixing filename on output
- -c, --count Show number of lines matching per file
--[no]column Show the column number of the first match
-A NUM, --after-context=NUM Print NUM lines of trailing context after
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
+ -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)
- --[no]colour Same as --[no]color
--color-filename=COLOR
--color-match=COLOR
- --color-lineno=COLOR Set the color for filenames, matches, and line
- numbers.
+ --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:
- -f Only print the files selected, without
- searching. The PATTERN must not be specified.
- -g Same as -f, but only select files matching
- PATTERN.
--sort-files Sort the found files lexically.
--show-types Show which types each file has.
--files-from=FILE Read the list of files to search from FILE.
File inclusion/exclusion:
--[no]ignore-dir=name Add/remove directory from list of ignored dirs
--[no]ignore-directory=name Synonym for ignore-dir
- --ignore-file=filter Add filter for ignoring files
+ --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.
- -k, --known-types Include only files of types that ack recognizes.
- --type=X Include only X files, where X is a recognized
- filetype.
- --type=noX Exclude X files.
- See "ack --help-types" for supported filetypes.
+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:FILTERARGS
- Files with the given FILTERARGS applied to the
- given FILTER are recognized as being of type
- TYPE. This replaces an existing definition for
- type TYPE.
- --type-add TYPE:FILTER:FILTERARGS
- Files with the given FILTERARGS applied to the
- given FILTER are recognized as being type TYPE.
- --type-del TYPE Removes all filters associated with TYPE.
-
+ --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.
- --help, -? This help
- --help-types Display all known types
--dump Dump information on which options are loaded
- from which RC files
+ and where they're defined.
--[no]filter Force ack to treat standard input as a pipe
(--filter) or tty (--nofilter)
- --man Man page
- --version Display version & copyright
+ --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 $VERSION of ack. Run "ack --version" for full version info.
+This is version $App::Ack::VERSION of ack. Run "ack --version" for full version info.
END_OF_HELP
return;
App::Ack::print( <<'END_OF_HELP' );
Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES]
-The following is the list of filetypes supported by ack. You can
-specify a file type with the --type=TYPE format, or the --TYPE
-format. For example, both --type=perl and --perl work.
+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 extensions may appear in multiple types. For example,
-.pod files are both Perl and Parrot.
+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 = filetypes_supported();
+ my @types = keys %App::Ack::mappings;
my $maxlen = 0;
for ( @types ) {
$maxlen = length if $maxlen < length;
if ( ref $ext_list ) {
$ext_list = join( '; ', map { $_->to_string } @{$ext_list} );
}
- App::Ack::print( sprintf( " --[no]%-*.*s %s\n", $maxlen, $maxlen, $type, $ext_list ) );
+ App::Ack::print( sprintf( " %-*.*s %s\n", $maxlen, $maxlen, $type, $ext_list ) );
}
return;
}
-sub show_man {
- require Pod::Usage;
- Pod::Usage::pod2usage({
- -input => $App::Ack::orig_program_name,
- -verbose => 2,
- -exitval => 0,
- });
-
- return;
-}
-
-
-sub get_version_statement {
- require Config;
-
- my $copyright = get_copyright();
- my $this_perl = $Config::Config{perlpath};
- if ($^O ne 'VMS') {
- my $ext = $Config::Config{_exe};
- $this_perl .= $ext unless $this_perl =~ m/$ext$/i;
- }
- my $ver = sprintf( '%vd', $^V );
- return <<"END_OF_VERSION";
-ack ${VERSION}
-Running under Perl $ver at $this_perl
+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).
-$copyright
+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.
-This program is free software. You may modify or distribute it
-under the terms of the Artistic License v2.0.
-END_OF_VERSION
-}
+Run ack --help-rgb-colors for a chart of the RGB colors.
+END_OF_HELP
-sub print_version_statement {
- App::Ack::print( get_version_statement() );
+ _show_color_grid();
return;
}
-sub get_copyright {
- return $COPYRIGHT;
-}
-
-
-sub print { print {$fh} @_; return; }
-sub print_blank_line { App::Ack::print( "\n" ); return; }
-sub print_filename { App::Ack::print( $_[0], $_[1] ); return; }
-
-sub set_up_pager {
- my $command = shift;
- return if App::Ack::output_to_pipe();
+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).
- my $pager;
- if ( not open( $pager, '|-', $command ) ) {
- App::Ack::die( qq{Unable to pipe to pager "$command": $!} );
- }
- $fh = $pager;
+Colors may be specified as "rggNNN" where "NNN" is a triplet of digits
+from 0 to 5 specifying the intensity of red, green and blue, respectively.
- return;
-}
+Here is a grid of the 216 possible values for NNN.
+END_OF_HELP
-sub output_to_pipe {
- return $output_to_pipe;
-}
+ _show_rgb_grid();
+ App::Ack::say( 'Here are the 216 possible colors with the "reverse" modifier applied.', "\n" );
-sub exit_from_ack {
- my $nmatches = shift;
+ _show_rgb_grid( 'reverse' );
- my $rc = $nmatches ? 0 : 1;
- exit $rc;
+ return;
}
+sub _show_color_grid {
+ my $cell_width = 7;
-1; # End of App::Ack
-package App::Ack::Resource;
-
-
-use warnings;
-use strict;
-use overload
- '""' => 'name';
+ 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
+ );
-sub new {
- my $class = shift;
- my $filename = shift;
+ App::Ack::say(
+ _color_cell( '' ),
+ map { _color_cell( '-' x $cell_width ) } @fg_colors
+ );
- my $self = bless {
- filename => $filename,
- fh => undef,
- opened => 0,
- }, $class;
+ for my $bg ( '', @bg_colors ) {
+ App::Ack::say(
+ _color_cell( '' ),
+ ( map { _color_cell( $_, "$_ $bg" ) } @fg_colors ),
+ $bg
+ );
- if ( $self->{filename} eq '-' ) {
- $self->{fh} = *STDIN;
- $self->{opened} = 1;
+ App::Ack::say(
+ _color_cell( 'bold' ),
+ ( map { _color_cell( $_, "bold $_ $bg" ) } @fg_colors ),
+ $bg
+ );
+ App::Ack::say();
}
- return $self;
-}
-
-
-
-sub name {
- return $_[0]->{filename};
+ return;
}
+sub _color_cell {
+ my $text = shift;
+ my $color = shift;
-sub basename {
- my ( $self ) = @_;
-
- # XXX Definedness? Pre-populate the slot with an undef?
- unless ( exists $self->{basename} ) {
- $self->{basename} = (File::Spec->splitpath($self->name))[2];
- }
+ my $cell_width = 7;
+ $text = sprintf( '%-*s', $cell_width, $text );
- return $self->{basename};
+ return ($color ? Term::ANSIColor::colored( $text, $color ) : $text) . ' ';
}
+sub _show_rgb_grid {
+ my $modifier = shift // '';
-sub open {
- my ( $self ) = @_;
-
- if ( !$self->{opened} ) {
- if ( open $self->{fh}, '<', $self->{filename} ) {
- $self->{opened} = 1;
- }
- else {
- $self->{fh} = undef;
- }
- }
-
- return $self->{fh};
-}
+ 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
-sub needs_line_scan {
- my $self = shift;
- my $opt = shift;
+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
- return 1 if $opt->{v};
+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
- my $size = -s $self->{fh};
- if ( $size == 0 ) {
- return 0;
- }
- elsif ( $size > 100_000 ) {
- return 1;
- }
+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
- my $buffer;
- my $rc = sysread( $self->{fh}, $buffer, $size );
- if ( !defined($rc) && $App::Ack::report_bad_filenames ) {
- App::Ack::warn( "$self->{filename}: $!" );
- return 1;
- }
- return 0 unless $rc && ( $rc == $size );
+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
- return $buffer =~ /$opt->{regex}/m;
-}
+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 );
-sub reset {
- my $self = shift;
+ return;
+}
- # Return if we haven't opened the file yet.
- if ( !defined($self->{fh}) ) {
- return;
- }
- if ( !seek( $self->{fh}, 0, 0 ) && $App::Ack::report_bad_filenames ) {
- App::Ack::warn( "$self->{filename}: $!" );
- }
+sub show_man {
+ require Pod::Usage;
+ Pod::Usage::pod2usage({
+ -input => $App::Ack::ORIGINAL_PROGRAM_NAME,
+ -verbose => 2,
+ -exitval => 0,
+ });
return;
}
-sub close {
- my $self = shift;
-
- # Return if we haven't opened the file yet.
- if ( !defined($self->{fh}) ) {
- return;
- }
+sub get_version_statement {
+ require Config;
- if ( !close($self->{fh}) && $App::Ack::report_bad_filenames ) {
- App::Ack::warn( $self->name() . ": $!" );
+ 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 );
- $self->{opened} = 0;
-
- return;
-}
-
+ 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
-sub clone {
- my ( $self ) = @_;
+$copyright
- return __PACKAGE__->new($self->name);
+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 firstliney {
- my ( $self ) = @_;
-
- if ( !exists $self->{firstliney} ) {
- my $fh = $self->open();
- if ( !$fh ) {
- if ( $App::Ack::report_bad_filenames ) {
- App::Ack::warn( $self->name . ': ' . $! );
- }
- return '';
- }
+sub set_up_pager {
+ my $command = shift;
- my $buffer = '';
- my $rc = sysread( $fh, $buffer, 250 );
- unless($rc) { # XXX handle this better?
- $buffer = '';
- }
- $buffer =~ s/[\r\n].*//s;
- $self->{firstliney} = $buffer;
- $self->reset;
+ return if App::Ack::output_to_pipe();
- $self->close;
+ my $pager;
+ if ( not open( $pager, '|-', $command ) ) {
+ App::Ack::die( qq{Unable to pipe to pager "$command": $!} );
}
+ $fh = $pager;
- return $self->{firstliney};
+ return;
}
-1;
-package App::Ack::Resources;
-
-
-
-use Errno qw(EACCES);
-
-use warnings;
-use strict;
-
-sub _generate_error_handler {
- my $opt = shift;
- if ( $opt->{dont_report_bad_filenames} ) {
- return sub {
- my $msg = shift;
- if ( $! == EACCES ) {
- return;
- }
- App::Ack::warn( $msg );
- };
- }
- else {
- return sub {
- my $msg = shift;
- App::Ack::warn( $msg );
- };
- }
+sub output_to_pipe {
+ return $output_to_pipe;
}
-sub from_argv {
- my $class = shift;
- my $opt = shift;
- my $start = shift;
-
- my $self = bless {}, $class;
-
- my $file_filter = undef;
- my $descend_filter = $opt->{descend_filter};
-
- if( $opt->{n} ) {
- $descend_filter = sub {
- return 0;
- };
- }
-
- $self->{iter} =
- File::Next::files( {
- file_filter => $opt->{file_filter},
- descend_filter => $descend_filter,
- error_handler => _generate_error_handler($opt),
- warning_handler => sub {},
- sort_files => $opt->{sort_files},
- follow_symlinks => $opt->{follow},
- }, @{$start} );
+sub exit_from_ack {
+ my $nmatches = shift;
- return $self;
+ my $rc = $nmatches ? 0 : 1;
+ exit $rc;
}
-sub from_file {
- my $class = shift;
- my $opt = shift;
- my $file = shift;
+sub show_types {
+ my $file = shift;
- my $iter =
- File::Next::from_file( {
- error_handler => _generate_error_handler($opt),
- warning_handler => _generate_error_handler($opt),
- sort_files => $opt->{sort_files},
- }, $file ) or return undef;
+ my @types = filetypes( $file );
+ my $arrow = @types ? ' => ' : ' =>';
+ App::Ack::say( $file->name, $arrow, join( ',', @types ) );
- return bless {
- iter => $iter,
- }, $class;
+ return;
}
-# This is for reading input lines from STDIN, not the list of files from STDIN
-sub from_stdin {
- my $class = shift;
- my $opt = shift;
- my $self = bless {}, $class;
+sub filetypes {
+ my ( $file ) = @_;
- my $has_been_called = 0;
+ my @matches;
- $self->{iter} = sub {
- if ( !$has_been_called ) {
- $has_been_called = 1;
- return '-';
+ 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;
+ }
}
- return;
- };
+ }
- return $self;
+ # https://metacpan.org/pod/distribution/Perl-Critic/lib/Perl/Critic/Policy/Subroutines/ProhibitReturnSort.pm
+ @matches = sort @matches;
+ return @matches;
}
-sub next {
- my $self = shift;
- my $file = $self->{iter}->() or return;
+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 App::Ack::Resource->new( $file );
+ return 0;
}
-1;
+
+1; # End of App::Ack
package App::Ack::ConfigDefault;
use warnings;
### Directories to ignore
# Bazaar
-# http://bazaar.canonical.com/
+# https://bazaar.canonical.com/
--ignore-directory=is:.bzr
# Codeville
-# http://freecode.com/projects/codeville
+# http://freshmeat.sourceforge.net/projects/codeville
--ignore-directory=is:.cdv
# Interface Builder (Xcode)
-# http://en.wikipedia.org/wiki/Interface_Builder
+# https://en.wikipedia.org/wiki/Interface_Builder
--ignore-directory=is:~.dep
--ignore-directory=is:~.dot
--ignore-directory=is:~.nib
--ignore-directory=is:~.plst
# Git
-# http://git-scm.com/
+# https://git-scm.com/
--ignore-directory=is:.git
-# When using submodules, .git is a file.
+# When submodules are used, .git is a file.
--ignore-file=is:.git
# Mercurial
-# http://mercurial.selenic.com/
+# https://www.mercurial-scm.org/
--ignore-directory=is:.hg
-# quilt
-# http://directory.fsf.org/wiki/Quilt
+# Quilt
+# https://directory.fsf.org/wiki/Quilt
--ignore-directory=is:.pc
# Subversion
-# http://subversion.tigris.org/
+# https://subversion.apache.org/
--ignore-directory=is:.svn
# Monotone
-# http://www.monotone.ca/
+# https://www.monotone.ca/
--ignore-directory=is:_MTN
# CVS
-# http://savannah.nongnu.org/projects/cvs
+# https://savannah.nongnu.org/projects/cvs
--ignore-directory=is:CVS
# RCS
-# http://www.gnu.org/software/rcs/
+# https://www.gnu.org/software/rcs/
--ignore-directory=is:RCS
# SCCS
-# http://en.wikipedia.org/wiki/Source_Code_Control_System
+# https://en.wikipedia.org/wiki/Source_Code_Control_System
--ignore-directory=is:SCCS
# darcs
--ignore-directory=is:_sgbak
# autoconf
-# http://www.gnu.org/software/autoconf/
+# https://www.gnu.org/software/autoconf/
--ignore-directory=is:autom4te.cache
# Perl module building
--ignore-directory=is:node_modules
# CMake cache
-# http://www.cmake.org/
+# https://www.cmake.org/
--ignore-directory=is:CMakeFiles
# Eclipse workspace folder
-# http://eclipse.org/
+# https://eclipse.org/
--ignore-directory=is:.metadata
# Cabal (Haskell) sandboxes
-# http://www.haskell.org/cabal/users-guide/installing-packages.html
+# 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
# Emacs swap files
--ignore-file=match:/^#.+#$/
-# vi/vim swap files http://vim.org/
---ignore-file=match:/[._].*\.swp$/
+# vi/vim swap files https://www.vim.org/
+--ignore-file=match:/[._].*[.]swp$/
# core dumps
---ignore-file=match:/core\.\d+$/
+--ignore-file=match:/core[.]\d+$/
# minified Javascript
--ignore-file=match:/[.-]min[.]js$/
# 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
-### Filetypes defined
+# Python compiles modules
+--ignore-file=ext:pyc,pyd,pyo
-# Perl
-# http://perl.org/
---type-add=perl:ext:pl,pm,pod,t,psgi
---type-add=perl:firstlinematch:/^#!.*\bperl/
+# C extensions
+--ignore-file=ext:so
-# Perl tests
---type-add=perltest:ext:t
+# Compiled gettext files
+--ignore-file=ext:mo
+
+### Filetypes defined
# Makefiles
-# http://www.gnu.org/s/make/
+# 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
-# http://rake.rubyforge.org/
+# https://rake.rubyforge.org/
--type-add=rake:is:Rakefile
# CMake
-# http://www.cmake.org/
+# https://cmake.org/
--type-add=cmake:is:CMakeLists.txt
--type-add=cmake:ext:cmake
--type-add=actionscript:ext:as,mxml
# Ada
-# http://www.adaic.org/
+# https://www.adaic.org/
--type-add=ada:ext:ada,adb,ads
# ASP
-# http://msdn.microsoft.com/en-us/library/aa286483.aspx
+# https://docs.microsoft.com/en-us/previous-versions/office/developer/server-technologies/aa286483(v=msdn.10)
--type-add=asp:ext:asp
# ASP.Net
-# http://www.asp.net/
+# https://dotnet.microsoft.com/apps/aspnet
--type-add=aspx:ext:master,ascx,asmx,aspx,svc
# Assembly
--type-add=asm:ext:asm,s
-# Batch
+# DOS/Windows batch
--type-add=batch:ext:bat,cmd
# ColdFusion
-# http://en.wikipedia.org/wiki/ColdFusion
+# https://en.wikipedia.org/wiki/ColdFusion
--type-add=cfmx:ext:cfc,cfm,cfml
# Clojure
-# http://clojure.org/
+# https://clojure.org/
--type-add=clojure:ext:clj,cljs,edn,cljc
# C
--type-add=hh:ext:h
# CoffeeScript
-# http://coffeescript.org/
+# https://coffeescript.org/
--type-add=coffeescript:ext:coffee
# C++
--type-add=csharp:ext:cs
# CSS
-# http://www.w3.org/Style/CSS/
+# https://www.w3.org/Style/CSS/
--type-add=css:ext:css
# Dart
-# http://www.dartlang.org/
+# https://dart.dev/
--type-add=dart:ext:dart
# Delphi
-# http://en.wikipedia.org/wiki/Embarcadero_Delphi
+# https://en.wikipedia.org/wiki/Embarcadero_Delphi
--type-add=delphi:ext:pas,int,dfm,nfm,dof,dpk,dproj,groupproj,bdsgroup,bdsproj
# Elixir
-# http://elixir-lang.org/
+# https://elixir-lang.org/
--type-add=elixir:ext:ex,exs
# Emacs Lisp
-# http://www.gnu.org/software/emacs
+# https://www.gnu.org/software/emacs
--type-add=elisp:ext:el
# Erlang
-# http://www.erlang.org/
+# https://www.erlang.org/
--type-add=erlang:ext:erl,hrl
# Fortran
-# http://en.wikipedia.org/wiki/Fortran
+# https://en.wikipedia.org/wiki/Fortran
--type-add=fortran:ext:f,f77,f90,f95,f03,for,ftn,fpp
# Go
-# http://golang.org/
+# https://golang.org/
--type-add=go:ext:go
# Groovy
-# http://groovy.codehaus.org/
+# https://www.groovy-lang.org/
--type-add=groovy:ext:groovy,gtmpl,gpp,grunit,gradle
# GSP
-# http://groovy.codehaus.org/GSP
+# https://gsp.grails.org/
--type-add=gsp:ext:gsp
# Haskell
-# http://www.haskell.org/
+# https://www.haskell.org/
--type-add=haskell:ext:hs,lhs
# HTML
--type-add=jade:ext:jade
# Java
-# http://www.oracle.com/technetwork/java/index.html
+# https://www.oracle.com/technetwork/java/index.html
--type-add=java:ext:java,properties
# JavaScript
--type-add=js:ext:js
# JSP
-# http://www.oracle.com/technetwork/java/javaee/jsp/index.html
+# https://www.oracle.com/technetwork/java/javaee/jsp/index.html
--type-add=jsp:ext:jsp,jspx,jspf,jhtm,jhtml
# JSON
-# http://www.json.org/
+# https://json.org/
--type-add=json:ext:json
# Kotlin
--type-add=less:ext:less
# Common Lisp
-# http://common-lisp.net/
+# https://common-lisp.net/
--type-add=lisp:ext:lisp,lsp
# Lua
-# http://www.lua.org/
+# 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
--type-add=objcpp:ext:mm,h
# OCaml
-# http://caml.inria.fr/
+# https://ocaml.org/
--type-add=ocaml:ext:ml,mli,mll,mly
-# Matlab
-# http://en.wikipedia.org/wiki/MATLAB
---type-add=matlab:ext:m
+# Perl
+# https://perl.org/
+--type-add=perl:ext:pl,pm,pod,t,psgi
+--type-add=perl:firstlinematch:/^#!.*\bperl/
-# Parrot
-# http://www.parrot.org/
---type-add=parrot:ext:pir,pasm,pmc,ops,pod,pg,tg
+# Perl tests
+--type-add=perltest:ext:t
+
+# Perl's Plain Old Documentation format, POD
+--type-add=pod:ext:pod
# PHP
-# http://www.php.net/
+# https://www.php.net/
--type-add=php:ext:php,phpt,php3,php4,php5,phtml
--type-add=php:firstlinematch:/^#!.*\bphp/
# Plone
-# http://plone.org/
+# https://plone.org/
--type-add=plone:ext:pt,cpt,metadata,cpy,py
# Python
-# http://www.python.org/
+# https://www.python.org/
--type-add=python:ext:py
--type-add=python:firstlinematch:/^#!.*\bpython/
# R
-# http://www.r-project.org/
+# https://www.r-project.org/
--type-add=rr:ext:R
# reStructured Text
-# http://docutils.sourceforge.net/rst.html
+# https://docutils.sourceforge.io/rst.html
--type-add=rst:ext:rst
# Ruby
-# http://www.ruby-lang.org/
+# 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
-# http://www.rust-lang.org/
+# https://www.rust-lang.org/
--type-add=rust:ext:rs
# Sass
-# http://sass-lang.com
+# https://sass-lang.com
--type-add=sass:ext:sass,scss
# Scala
-# http://www.scala-lang.org/
+# https://www.scala-lang.org/
--type-add=scala:ext:scala
# Scheme
-# http://groups.csail.mit.edu/mac/projects/scheme/
+# https://groups.csail.mit.edu/mac/projects/scheme/
--type-add=scheme:ext:scm,ss
# Shell
--type-add=smalltalk:ext:st
# Smarty
-# http://www.smarty.net/
+# https://www.smarty.net/
--type-add=smarty:ext:tpl
# SQL
-# http://www.iso.org/iso/catalogue_detail.htm?csnumber=45498
+# https://www.iso.org/standard/45498.html
--type-add=sql:ext:sql,ctl
# Stylus
-# http://learnboost.github.io/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
-# http://www.tcl.tk/
+# https://www.tcl.tk/
--type-add=tcl:ext:tcl,itcl,itk
-# LaTeX
-# http://www.latex-project.org/
+# TeX & LaTeX
+# https://www.latex-project.org/
--type-add=tex:ext:tex,cls,sty
# Template Toolkit (Perl)
-# http://template-toolkit.org/
---type-add=tt:ext:tt,tt2,ttml
+# 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
--type-add=vhdl:ext:vhd,vhdl
# Vim
-# http://www.vim.org/
+# https://www.vim.org/
--type-add=vim:ext:vim
# XML
-# http://www.w3.org/TR/REC-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
-# http://yaml.org/
+# https://yaml.org/
--type-add=yaml:ext:yaml,yml
HERE
$lines =~ s/==VERSION==/$App::Ack::VERSION/sm;
use warnings;
use Cwd 3.00 ();
-use File::Spec 3.00;
+use File::Spec 3.00 ();
use if ($^O eq 'MSWin32'), 'Win32';
my @configs = @_;
my %seen;
+ my @uniq;
foreach my $config (@configs) {
- my $key = $config->{path};
+ 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;
}
- undef $config if $seen{$key}++;
+ push( @uniq, $config ) unless $seen{$key}++;
}
- return grep { defined } @configs;
+ return @uniq;
}
map { File::Spec->catfile(@_, $_) }
qw(.ackrc _ackrc);
- die File::Spec->catdir(@_) . " contains both .ackrc and _ackrc.\n" .
- "Please remove one of those files.\n"
- if @files > 1;
+ 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
$cwd =~ /(.+)/;
$cwd = $1;
my @dirs = File::Spec->splitdir( $cwd );
- while(@dirs) {
+ while ( @dirs ) {
my $ackrc = _check_for_ackrc(@dirs);
- if(defined $ackrc) {
+ if ( defined $ackrc ) {
push @config_files, { project => 1, path => $ackrc };
last;
}
return _remove_redundancies( @config_files );
}
-
-
-sub read_rcfile {
- my $file = shift;
-
- return unless defined $file && -e $file;
-
- my @lines;
-
- open( my $fh, '<', $file ) or App::Ack::die( "Unable to read $file: $!" );
- while ( my $line = <$fh> ) {
- chomp $line;
- $line =~ s/^\s+//;
- $line =~ s/\s+$//;
-
- next if $line eq '';
- next if $line =~ /^\s*#/;
-
- push( @lines, $line );
- }
- close $fh or App::Ack::die( "Unable to close $file: $!" );
-
- return @lines;
-}
-
1;
package App::Ack::ConfigLoader;
use strict;
use warnings;
+use 5.010;
-use Carp 1.04 ();
-use Getopt::Long 2.38 ();
+use File::Spec 3.00 ();
+use Getopt::Long 2.39 ();
use Text::ParseWords 3.1 ();
+sub opt_parser {
+ my @opts = @_;
-my @INVALID_COMBINATIONS;
-
-BEGIN {
- my @context = qw( -A -B -C --after-context --before-context --context );
- my @pretty = qw( --heading --group --break );
- my @filename = qw( -h -H --with-filename --no-filename );
-
- @INVALID_COMBINATIONS = (
- # XXX normalize
- [qw(-l)] => [@context, @pretty, @filename, qw(-L -o --passthru --output --max-count --column -f -g --show-types)],
- [qw(-L)] => [@context, @pretty, @filename, qw(-l -o --passthru --output --max-count --column -f -g --show-types -c --count)],
- [qw(--line)] => [@context, @pretty, @filename, qw(-l --files-with-matches --files-without-matches -L -o --passthru --match -m --max-count -1 -c --count --column --print0 -f -g --show-types)],
- [qw(-o)] => [@context, qw(--output -c --count --column --column -f --show-types)],
- [qw(--passthru)] => [@context, qw(--output --column -m --max-count -1 -c --count -f -g)],
- [qw(--output)] => [@context, qw(-c --count -f -g)],
- [qw(--match)] => [qw(-f -g)],
- [qw(-m --max-count)] => [qw(-1 -f -g -c --count)],
- [qw(-h --no-filename)] => [qw(-H --with-filename -f -g --group --heading)],
- [qw(-H --with-filename)] => [qw(-h --no-filename -f -g)],
- [qw(-c --count)] => [@context, @pretty, qw(--column -f -g)],
- [qw(--column)] => [qw(-f -g)],
- [@context] => [qw(-f -g)],
- [qw(-f)] => [qw(-g), @pretty],
- [qw(-g)] => [qw(-f), @pretty],
+ my @standard = qw(
+ default
+ bundling
+ no_auto_help
+ no_auto_version
+ no_ignore_case
);
+ return Getopt::Long::Parser->new( config => [ @standard, @opts ] );
}
sub _generate_ignore_dir {
return sub {
my ( undef, $dir ) = @_;
- $dir = App::Ack::remove_dir_sep( $dir );
+ $dir = _remove_directory_separator( $dir );
if ( $dir !~ /:/ ) {
$dir = 'is:' . $dir;
}
my ( $filter_type, $args ) = split /:/, $dir, 2;
if ( $filter_type eq 'firstlinematch' ) {
- Carp::croak( qq{Invalid filter specification "$filter_type" for option '$option_name'} );
+ App::Ack::die( qq{Invalid filter specification "$filter_type" for option '$option_name'} );
}
my $filter = App::Ack::Filter->create_filter($filter_type, split(/,/, $args));
$collection = $opt->{idirs}[-1];
if ( $is_inverted ) {
- # XXX this relies on invert of an inverted filter
- # to return the original
- $collection = $collection->invert()
+ # This relies on invert of an inverted filter to return the original.
+ $collection = $collection->invert();
}
}
else {
$collection = App::Ack::Filter::Collection->new();
-
- if ( $is_inverted ) {
- push @{ $opt->{idirs} }, $collection->invert();
- }
- else {
- push @{ $opt->{idirs} }, $collection;
- }
+ push @{ $opt->{idirs} }, $is_inverted ? $collection->invert() : $collection;
}
$collection->add($filter);
};
}
-sub process_filter_spec {
+
+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+):(.*)/ ) {
return ( $type_name, App::Ack::Filter->create_filter('ext', @extensions) );
}
else {
- Carp::croak "invalid filter specification '$spec'";
+ App::Ack::die( "Invalid filter specification '$spec'" );
}
}
-sub uninvert_filter {
+sub _uninvert_filter {
my ( $opt, @filters ) = @_;
return unless defined $opt->{filters} && @filters;
}
-sub process_filetypes {
+sub _process_filetypes {
my ( $opt, $arg_sources ) = @_;
- Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); # start with default options, minus some annoying ones
- Getopt::Long::Configure(
- 'no_ignore_case',
- 'no_auto_abbrev',
- 'pass_through',
- );
my %additional_specs;
my $add_spec = sub {
my ( undef, $spec ) = @_;
- my ( $name, $filter ) = process_filter_spec($spec);
+ my ( $name, $filter ) = _process_filter_spec($spec);
push @{ $App::Ack::mappings{$name} }, $filter;
@filters = map { $_->invert() } @filters;
}
else {
- uninvert_filter( $opt, @filters );
+ _uninvert_filter( $opt, @filters );
}
push @{ $opt->{'filters'} }, @filters;
my $set_spec = sub {
my ( undef, $spec ) = @_;
- my ( $name, $filter ) = process_filter_spec($spec);
+ my ( $name, $filter ) = _process_filter_spec($spec);
$App::Ack::mappings{$name} = [ $filter ];
'type-del=s' => $delete_spec,
);
+ my $p = opt_parser( 'no_auto_abbrev', 'pass_through' );
foreach my $source (@{$arg_sources}) {
- my ( $source_name, $args ) = @{$source}{qw/name contents/};
+ my $args = $source->{contents};
if ( ref($args) ) {
# $args are modified in place, so no need to munge $arg_sources
- local @ARGV = @{$args};
- Getopt::Long::GetOptions(%type_arg_specs);
- @{$args} = @ARGV;
+ $p->getoptionsfromarray( $args, %type_arg_specs );
}
else {
( undef, $source->{contents} ) =
- Getopt::Long::GetOptionsFromString($args, %type_arg_specs);
+ $p->getoptionsfromstring( $args, %type_arg_specs );
}
}
$additional_specs{'k|known-types'} = sub {
- my ( undef, $value ) = @_;
-
my @filters = map { @{$_} } values(%App::Ack::mappings);
push @{ $opt->{'filters'} }, @filters;
}
-sub removed_option {
- my ( $option, $explanation ) = @_;
-
- $explanation ||= '';
- return sub {
- warn "Option '$option' is not valid in ack 2.\n$explanation";
- exit 1;
- };
-}
-
-
sub get_arg_spec {
my ( $opt, $extra_specs ) = @_;
- my $dash_a_explanation = <<'EOT';
-You don't need -a, ack 1.x users. This is because ack 2.x has
--k/--known-types which makes it only select files of known types, rather
-than any text file (which is the behavior of ack 1.x).
-If you're surprised to see this message because you didn't put -a on the
-command line, you may have options in an .ackrc, or in the ACKRC_OPTIONS
-environment variable. Try using the --dump flag to help find it.
-EOT
+ sub _type_handler {
+ my ( $getopt, $value ) = @_;
+
+ my $cb_value = 1;
+ if ( $value =~ s/^no// ) {
+ $cb_value = 0;
+ }
+ my $callback;
+ {
+ no warnings;
+ $callback = $extra_specs->{ $value . '!' };
+ }
- sub _context_value {
- my $val = shift;
+ if ( $callback ) {
+ $callback->( $getopt, $cb_value );
+ }
+ else {
+ App::Ack::die( "Unknown type '$value'" );
+ }
- # Contexts default to 2.
- return (!defined($val) || ($val < 0)) ? 2 : $val;
+ return;
}
return {
1 => sub { $opt->{1} = $opt->{m} = 1 },
- 'A|after-context:-1' => sub { shift; $opt->{after_context} = _context_value(shift) },
- 'B|before-context:-1' => sub { shift; $opt->{before_context} = _context_value(shift) },
- 'C|context:-1' => sub { shift; $opt->{before_context} = $opt->{after_context} = _context_value(shift) },
- 'a' => removed_option('-a', $dash_a_explanation),
- 'all' => removed_option('--all', $dash_a_explanation),
+ '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 => \$opt->{count},
+ '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},
- count => \$opt->{count},
- 'create-ackrc' => sub { print "$_\n" for ( '--ignore-ack-defaults', App::Ack::ConfigDefault::options() ); exit; },
+ 'create-ackrc' => sub { say for ( '--ignore-ack-defaults', App::Ack::ConfigDefault::options() ); exit; },
+ 'debug' => \$opt->{debug},
'env!' => sub {
my ( undef, $value ) = @_;
f => \$opt->{f},
'files-from=s' => \$opt->{files_from},
'filter!' => \$App::Ack::is_filter_mode,
- flush => \$opt->{flush},
+ flush => sub { $| = 1 },
'follow!' => \$opt->{follow},
g => \$opt->{g},
- G => removed_option('-G'),
'group!' => sub { shift; $opt->{heading} = $opt->{break} = shift },
'heading!' => \$opt->{heading},
'h|no-filename' => \$opt->{h},
'H|with-filename' => \$opt->{H},
- 'i|ignore-case' => \$opt->{i},
+ '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 ( undef, $file ) = @_;
- my ( $filter_type, $args ) = split /:/, $file, 2;
+ my ( $filter_type, $args ) = split /:/, $file, 2;
- my $filter = App::Ack::Filter->create_filter($filter_type, split(/,/, $args));
+ 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);
- },
- 'lines=s' => sub { shift; my $val = shift; push @{$opt->{lines}}, $val },
+ if ( !$opt->{ifiles} ) {
+ $opt->{ifiles} = App::Ack::Filter::Collection->new();
+ }
+ $opt->{ifiles}->add($filter);
+ },
'l|files-with-matches'
=> \$opt->{l},
'L|files-without-matches'
'nopager' => sub { $opt->{pager} = undef },
'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 },
- 's' => \$opt->{dont_report_bad_filenames},
+ '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},
- 'smart-case!' => \$opt->{smart_case},
+ 'S|smart-case!' => sub { my (undef,$value) = @_; $opt->{S} = $value; $opt->{i} = 0 if $value; },
'sort-files' => \$opt->{sort_files},
- 'type=s' => sub {
- my ( $getopt, $value ) = @_;
-
- my $cb_value = 1;
- if ( $value =~ s/^no// ) {
- $cb_value = 0;
- }
-
- my $callback = $extra_specs->{ $value . '!' };
-
- if ( $callback ) {
- $callback->( $getopt, $cb_value );
- }
- else {
- Carp::croak( "Unknown type '$value'" );
- }
- },
- 'u' => removed_option('-u'),
- 'unrestricted' => removed_option('--unrestricted'),
+ '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} = '-' },
- 'version' => sub { App::Ack::print_version_statement(); exit; },
- 'help|?:s' => sub { shift; App::Ack::show_help(@_); exit; },
+ 'help' => sub { App::Ack::show_help(); exit; },
'help-types' => sub { App::Ack::show_help_types(); exit; },
- 'man' => sub { App::Ack::show_man(); 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
}
-sub process_other {
- my ( $opt, $extra_specs, $arg_sources ) = @_;
+sub _context_value {
+ my $val = shift;
+
+ # Contexts default to 2.
+ return (!defined($val) || ($val < 0)) ? 2 : $val;
+}
- # Start with default options, minus some annoying ones.
- Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version');
- Getopt::Long::Configure(
- 'bundling',
- 'no_ignore_case',
- );
+
+sub _process_other {
+ my ( $opt, $extra_specs, $arg_sources ) = @_;
my $argv_source;
my $is_help_types_active;
foreach my $source (@{$arg_sources}) {
- my ( $source_name, $args ) = @{$source}{qw/name contents/};
-
- if ( $source_name eq 'ARGV' ) {
- $argv_source = $args;
+ if ( $source->{name} eq 'ARGV' ) {
+ $argv_source = $source->{contents};
last;
}
}
if ( $argv_source ) { # This *should* always be true, but you never know...
- my @copy = @{$argv_source};
- local @ARGV = @copy;
-
- Getopt::Long::Configure('pass_through');
-
- Getopt::Long::GetOptions(
+ my $p = opt_parser( 'pass_through' );
+ $p->getoptionsfromarray( [ @{$argv_source} ],
'help-types' => \$is_help_types_active,
);
-
- Getopt::Long::Configure('no_pass_through');
}
- my $arg_specs = get_arg_spec($opt, $extra_specs);
+ my $arg_specs = get_arg_spec( $opt, $extra_specs );
+ my $p = opt_parser();
foreach my $source (@{$arg_sources}) {
my ( $source_name, $args ) = @{$source}{qw/name contents/};
- my $args_for_source = $arg_specs;
+ my $args_for_source = { %{$arg_specs} };
- if ( $source->{project} ) {
+ if ( $source->{is_ackrc} ) {
my $illegal = sub {
- die "Options --output, --pager and --match are forbidden in project .ackrc files.\n";
+ my $name = shift;
+ App::Ack::die( "Option --$name is forbidden in .ackrc files." );
};
$args_for_source = {
%{$args_for_source},
'output=s' => $illegal,
- 'pager: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." );
+ };
+
+ $args_for_source = {
+ %{$args_for_source},
+ 'pager:s' => $illegal,
+ };
+ }
my $ret;
if ( ref($args) ) {
- local @ARGV = @{$args};
- $ret = Getopt::Long::GetOptions( %{$args_for_source} );
- @{$args} = @ARGV;
+ $ret = $p->getoptionsfromarray( $args, %{$args_for_source} );
}
else {
( $ret, $source->{contents} ) =
- Getopt::Long::GetOptionsFromString( $args, %{$args_for_source} );
+ $p->getoptionsfromstring( $args, %{$args_for_source} );
}
if ( !$ret ) {
if ( !$is_help_types_active ) {
}
-sub should_dump_options {
- my ( $sources ) = @_;
-
- foreach my $source (@{$sources}) {
- my ( $name, $options ) = @{$source}{qw/name contents/};
-
- if($name eq 'ARGV') {
- my $dump;
- local @ARGV = @{$options};
- Getopt::Long::Configure('default', 'pass_through', 'no_auto_help', 'no_auto_version');
- Getopt::Long::GetOptions(
- 'dump' => \$dump,
- );
- @{$options} = @ARGV;
- return $dump;
- }
- }
- return;
-}
-
-
-sub explode_sources {
+sub _explode_sources {
my ( $sources ) = @_;
my @new_sources;
- Getopt::Long::Configure('default', 'pass_through', 'no_auto_help', 'no_auto_version');
-
my %opt;
- my $arg_spec = get_arg_spec(\%opt);
+ my $arg_spec = get_arg_spec( \%opt, {} );
+ my $dummy_sub = sub {};
my $add_type = sub {
my ( undef, $arg ) = @_;
- # XXX refactor?
if ( $arg =~ /(\w+)=/) {
- $arg_spec->{$1} = sub {};
+ $arg_spec->{$1} = $dummy_sub;
}
else {
( $arg ) = split /:/, $arg;
- $arg_spec->{$arg} = sub {};
+ $arg_spec->{$arg} = $dummy_sub;
}
};
delete $arg_spec->{$arg};
};
+ my $p = opt_parser( 'pass_through' );
foreach my $source (@{$sources}) {
my ( $name, $options ) = @{$source}{qw/name contents/};
if ( ref($options) ne 'ARRAY' ) {
$j--;
my @copy = @chunk;
- local @ARGV = @chunk;
- Getopt::Long::GetOptions(
+ $p->getoptionsfromarray( [@chunk],
'type-add=s' => $add_type,
'type-set=s' => $add_type,
'type-del=s' => $del_type,
+ %{$arg_spec}
);
- Getopt::Long::GetOptions( %{$arg_spec} );
push @new_sources, {
name => $name,
}
-sub compare_opts {
+sub _compare_opts {
my ( $a, $b ) = @_;
my $first_a = $a->[0];
}
-sub dump_options {
+sub _dump_options {
my ( $sources ) = @_;
- $sources = explode_sources($sources);
+ $sources = _explode_sources($sources);
my %opts_by_source;
my @source_names;
foreach my $source (@{$sources}) {
- my ( $name, $contents ) = @{$source}{qw/name contents/};
+ my $name = $source->{name};
if ( not $opts_by_source{$name} ) {
$opts_by_source{$name} = [];
push @source_names, $name;
}
- push @{$opts_by_source{$name}}, $contents;
+ push @{$opts_by_source{$name}}, $source->{contents};
}
foreach my $name (@source_names) {
my $contents = $opts_by_source{$name};
- print $name, "\n";
- print '=' x length($name), "\n";
- print ' ', join(' ', @{$_}), "\n" foreach sort { compare_opts($a, $b) } @{$contents};
+ say $name;
+ say '=' x length($name);
+ say ' ', join(' ', @{$_}) for sort { _compare_opts($a, $b) } @{$contents};
}
return;
}
-sub remove_default_options_if_needed {
+sub _remove_default_options_if_needed {
my ( $sources ) = @_;
my $default_index;
my $should_remove = 0;
- # Start with default options, minus some annoying ones.
- Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version');
- Getopt::Long::Configure(
- 'no_ignore_case',
- 'no_auto_abbrev',
- 'pass_through',
- );
+ my $p = opt_parser( 'no_auto_abbrev', 'pass_through' );
foreach my $index ( $default_index + 1 .. $#{$sources} ) {
- my ( $name, $args ) = @{$sources->[$index]}{qw/name contents/};
+ my $args = $sources->[$index]->{contents};
if (ref($args)) {
- local @ARGV = @{$args};
- Getopt::Long::GetOptions(
+ $p->getoptionsfromarray( $args,
'ignore-ack-defaults' => \$should_remove,
);
- @{$args} = @ARGV;
}
else {
- ( undef, $sources->[$index]{contents} ) = Getopt::Long::GetOptionsFromString($args,
+ ( undef, $sources->[$index]{contents} ) = $p->getoptionsfromstring( $args,
'ignore-ack-defaults' => \$should_remove,
);
}
}
- Getopt::Long::Configure('default');
- Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version');
-
return $sources unless $should_remove;
my @copy = @{$sources};
}
-sub check_for_mutually_exclusive_options {
- my ( $arg_sources ) = @_;
-
- my %mutually_exclusive_with;
- my @copy = @{$arg_sources};
-
- for(my $i = 0; $i < @INVALID_COMBINATIONS; $i += 2) {
- my ( $lhs, $rhs ) = @INVALID_COMBINATIONS[ $i, $i + 1 ];
-
- foreach my $l_opt ( @{$lhs} ) {
- foreach my $r_opt ( @{$rhs} ) {
- push @{ $mutually_exclusive_with{ $l_opt } }, $r_opt;
- push @{ $mutually_exclusive_with{ $r_opt } }, $l_opt;
- }
- }
- }
-
- while( @copy ) {
- my %set_opts;
-
- my $source = shift @copy;
- my ( $source_name, $args ) = @{$source}{qw/name contents/};
- $args = ref($args) ? [ @{$args} ] : [ Text::ParseWords::shellwords($args) ];
-
- foreach my $opt ( @{$args} ) {
- next unless $opt =~ /^[-+]/;
- last if $opt eq '--';
-
- if( $opt =~ /^(.*)=/ ) {
- $opt = $1;
- }
- elsif ( $opt =~ /^(-[^-]).+/ ) {
- $opt = $1;
- }
-
- $set_opts{ $opt } = 1;
-
- my $mutex_opts = $mutually_exclusive_with{ $opt };
-
- next unless $mutex_opts;
-
- foreach my $mutex_opt ( @{$mutex_opts} ) {
- if($set_opts{ $mutex_opt }) {
- die "Options '$mutex_opt' and '$opt' are mutually exclusive\n";
- }
- }
- }
- }
-
- return;
-}
-
-
sub process_args {
my $arg_sources = \@_;
pager => $ENV{ACK_PAGER_COLOR} || $ENV{ACK_PAGER},
);
- check_for_mutually_exclusive_options($arg_sources);
-
- $arg_sources = remove_default_options_if_needed($arg_sources);
+ $arg_sources = _remove_default_options_if_needed($arg_sources);
- if ( should_dump_options($arg_sources) ) {
- dump_options($arg_sources);
- exit(0);
+ # Check for --dump early.
+ foreach my $source (@{$arg_sources}) {
+ if ( $source->{name} eq 'ARGV' ) {
+ my $dump;
+ my $p = opt_parser( 'pass_through' );
+ $p->getoptionsfromarray( $source->{contents},
+ 'dump' => \$dump,
+ );
+ if ( $dump ) {
+ _dump_options($arg_sources);
+ exit(0);
+ }
+ }
}
- my $type_specs = process_filetypes(\%opt, $arg_sources);
- process_other(\%opt, $type_specs, $arg_sources);
+ my $type_specs = _process_filetypes(\%opt, $arg_sources);
+
+ _check_for_mutex_options( $type_specs );
+
+ _process_other(\%opt, $type_specs, $arg_sources);
while ( @{$arg_sources} ) {
my $source = shift @{$arg_sources};
- my ( $source_name, $args ) = @{$source}{qw/name contents/};
+ my $args = $source->{contents};
# 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}) {
- Carp::croak "source '$source_name' has extra arguments!";
+ App::Ack::die( "Source '$source_name' has extra arguments!" );
}
}
else {
- Carp::croak 'The impossible has occurred!';
+ App::Ack::die( 'The impossible has occurred!' );
}
}
my $filters = ($opt{filters} ||= []);
my $noenv;
my $ackrc;
- Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version');
- Getopt::Long::Configure('pass_through');
- Getopt::Long::Configure('no_auto_abbrev');
-
- Getopt::Long::GetOptions(
+ my $p = opt_parser( 'no_auto_abbrev', 'pass_through' );
+ $p->getoptions(
'noenv' => \$noenv,
'ackrc=s' => \$ackrc,
);
- Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version');
-
my @files;
if ( !$noenv ) {
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) {
+ if ( open my $fh, '<', $ackrc ) {
close $fh;
}
else {
- die "Unable to load ackrc '$ackrc': $!"
+ App::Ack::die( "Unable to load ackrc '$ackrc': $!" );
}
push( @files, { path => $ackrc } );
}
};
foreach my $file ( @files) {
- my @lines = App::Ack::ConfigFinder::read_rcfile($file->{path});
-
- if(@lines) {
+ my @lines = read_rcfile($file->{path});
+ if ( @lines ) {
push @arg_sources, {
name => $file->{path},
contents => \@lines,
project => $file->{project},
+ is_ackrc => 1,
};
}
}
- if ( $ENV{ACK_OPTIONS} && !$noenv ) {
- push @arg_sources, {
- name => 'ACK_OPTIONS',
- contents => $ENV{ACK_OPTIONS},
- };
- }
-
push @arg_sources, {
name => 'ARGV',
contents => [ @ARGV ],
return @arg_sources;
}
-1; # End of App::Ack::ConfigLoader
-package App::Ack::Filter;
-
-use strict;
-use warnings;
+sub read_rcfile {
+ my $file = shift;
-use Carp 1.04 ();
+ return unless defined $file && -e $file;
-my %filter_types;
+ 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+$//;
-sub create_filter {
- my ( undef, $type, @args ) = @_;
+ next if $line eq '';
+ next if $line =~ /^\s*#/;
- if ( my $package = $filter_types{$type} ) {
- return $package->new(@args);
+ push( @lines, $line );
}
- Carp::croak "Unknown filter type '$type'";
+ close $fh or App::Ack::die( "Unable to close $file: $!" );
+
+ return @lines;
}
-sub register_filter {
- my ( undef, $type, $package ) = @_;
+# Verifies no mutex options were passed. Dies if they were.
+sub _check_for_mutex_options {
+ my $type_specs = shift;
- $filter_types{$type} = $package;
+ my $mutex = mutex_options();
+
+ my ($raw,$used) = _options_used( $type_specs );
+
+ my @used = sort { lc $a cmp lc $b } keys %{$used};
+
+ 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." );
+ }
+ }
+ }
return;
}
-sub invert {
- my ( $self ) = @_;
+# 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;
- return App::Ack::Filter::Inverse->new( $self );
-}
+ 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,
+ };
-sub is_inverted {
- return 0;
-}
+ my %parsed;
+ my @raw;
+ my %spec_capture_parsed;
+ my %spec_capture_raw;
-sub to_string {
- my ( $self ) = @_;
+ # 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}
+ );
+ }
- return '(unimplemented to_string)';
+ # 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;
+
+ $spec_capture_raw{$alias} = $sub_count;
+ }
+ }
+ }
+
+ # Parse @ARGV twice, once with each capture spec.
+ my $p = opt_parser( 'pass_through' ); # Ignore invalid options.
+ $p->getoptionsfromarray( [@ARGV], %spec_capture_raw );
+ $p->getoptionsfromarray( [@ARGV], %spec_capture_parsed );
+
+ return (\@raw,\%parsed);
}
-sub inspect {
- my ( $self ) = @_;
+sub mutex_options {
+ # This list is machine-generated by dev/crank-mutex. Do not modify it by hand.
+
+ 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,
+ },
+ 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,
+ },
+ 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,
+ L => 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,
+ o => 1,
+ output => 1,
+ p => 1,
+ passthru => 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,
+ 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,
+ o => 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,
+ },
+ 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,
+ },
+ 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,
+ },
+ 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,
+ },
+ 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,
+ },
+ };
+
+} # End of mutex_options()
+
+
+1; # End of App::Ack::ConfigLoader
+package App::Ack::File;
+
+use warnings;
+use strict;
+
+use File::Spec ();
+
+
+sub new {
+ my $class = shift;
+ my $filename = shift;
+
+ my $self = bless {
+ filename => $filename,
+ fh => undef,
+ }, $class;
+
+ if ( $self->{filename} eq '-' ) {
+ $self->{fh} = *STDIN;
+ }
+
+ return $self;
+}
+
+
+
+sub name {
+ return $_[0]->{filename};
+}
+
+
+
+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 {
+ $self->{fh} = undef;
+ }
+ }
+
+ return $self->{fh};
+}
+
+
+sub may_be_present {
+ my $self = shift;
+ my $regex = shift;
+
+ # 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.
+
+ # 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.
+
+ # The $regex may be undef if it had a "$" in it, and is therefore unsuitable for this heuristic.
+
+ 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 {
+ # 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;
+ }
+ }
+ }
+ }
+
+ return $may_be_present;
+}
+
+
+
+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}: $!" );
+ }
+ }
+
+ return;
+}
+
+
+
+sub close {
+ my $self = shift;
+
+ if ( $self->{fh} ) {
+ if ( !close($self->{fh}) && $App::Ack::report_bad_filenames ) {
+ App::Ack::warn( $self->name() . ": $!" );
+ }
+ $self->{fh} = undef;
+ }
+
+ return;
+}
+
+
+
+sub clone {
+ my ( $self ) = @_;
+
+ return __PACKAGE__->new($self->name);
+}
+
+
+
+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;
+ }
+ }
+
+ return $self->{firstliney};
+}
+
+1;
+package App::Ack::Files;
+
+
+
+use warnings;
+use strict;
+use 5.010;
+
+
+sub from_argv {
+ my $class = shift;
+ my $opt = shift;
+ my $start = shift;
+
+ my $self = bless {}, $class;
+
+ my $descend_filter = $opt->{descend_filter};
+
+ if ( $opt->{n} ) {
+ $descend_filter = sub {
+ return 0;
+ };
+ }
+
+ $self->{iter} =
+ File::Next::files( {
+ file_filter => $opt->{file_filter},
+ descend_filter => $descend_filter,
+ error_handler => _generate_error_handler(),
+ warning_handler => sub {},
+ sort_files => $opt->{sort_files},
+ follow_symlinks => $opt->{follow},
+ }, @{$start} );
+
+ return $self;
+}
+
+
+sub from_file {
+ my $class = shift;
+ my $opt = shift;
+ my $file = shift;
+
+ 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 bless {
+ iter => $iter,
+ }, $class;
+}
+
+
+
+
+sub from_stdin {
+ my $class = shift;
+
+ my $self = bless {}, $class;
+
+ $self->{iter} = sub {
+ state $has_been_called = 0;
+
+ if ( !$has_been_called ) {
+ $has_been_called = 1;
+ return '-';
+ }
+ return;
+ };
+
+ return $self;
+}
+
+
+sub next {
+ my $self = shift;
+
+ my $file = $self->{iter}->();
+
+ return unless defined($file);
+
+ return App::Ack::File->new( $file );
+}
+
+
+sub _generate_error_handler {
+ if ( $App::Ack::report_bad_filenames ) {
+ return sub {
+ my $msg = shift;
+ App::Ack::warn( $msg );
+ };
+ }
+ else {
+ return sub {};
+ }
+}
+
+1;
+package App::Ack::Filter;
+
+use strict;
+use warnings;
+
+
+my %filter_types;
+
+
+sub create_filter {
+ my ( undef, $type, @args ) = @_;
+
+ 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 register_filter {
+ my ( undef, $type, $package ) = @_;
+
+ $filter_types{$type} = $package;
+
+ return;
+}
+
+
+sub invert {
+ my ( $self ) = @_;
+
+ return App::Ack::Filter::Inverse->new( $self );
+}
+
+
+sub is_inverted {
+ return 0;
+}
+
+
+sub to_string {
+ return '(unimplemented to_string)';
+}
+
+
+sub inspect {
+ my ( $self ) = @_;
+
+ return ref($self);
+}
+
+1;
+package App::Ack::Filter::Collection;
+
+
+use strict;
+use warnings;
+BEGIN {
+ our @ISA = 'App::Ack::Filter';
+}
+
+sub new {
+ my ( $class ) = @_;
+
+ return bless {
+ groups => {},
+ ungrouped => [],
+ }, $class;
+}
+
+sub filter {
+ my ( $self, $file ) = @_;
+
+ for my $group (values %{$self->{groups}}) {
+ return 1 if $group->filter($file);
+ }
+
+ for my $filter (@{$self->{ungrouped}}) {
+ return 1 if $filter->filter($file);
+ }
+
+ return 0;
+}
+
+sub add {
+ my ( $self, $filter ) = @_;
+
+ if (exists $filter->{'groupname'}) {
+ my $group = ($self->{groups}->{$filter->{groupname}} ||= $filter->create_group());
+ $group->add($filter);
+ }
+ else {
+ push @{$self->{'ungrouped'}}, $filter;
+ }
+
+ return;
+}
+
+sub inspect {
+ my ( $self ) = @_;
+
+ return ref($self) . " - $self";
+}
+
+sub to_string {
+ my ( $self ) = @_;
+
+ return join(', ', map { "($_)" } @{$self->{ungrouped}});
+}
+
+1;
+package App::Ack::Filter::Default;
+
+
+use strict;
+use warnings;
+BEGIN {
+ our @ISA = 'App::Ack::Filter';
+}
+
+sub new {
+ my ( $class ) = @_;
+
+ return bless {}, $class;
+}
- return ref($self);
+sub filter {
+ my ( undef, $file ) = @_;
+
+ return -T $file->name;
}
1;
}
sub filter {
- my ( $self, $resource ) = @_;
-
- my $re = $self->{'regex'};
+ my ( $self, $file ) = @_;
- return $resource->name =~ /$re/;
+ return $file->name =~ /$self->{regex}/;
}
sub inspect {
my ( $self ) = @_;
- my $re = $self->{'regex'};
-
- return ref($self) . " - $re";
+ return ref($self) . ' - ' . $self->{regex};
}
sub to_string {
my ( $self ) = @_;
- my $exts = $self->{'extensions'};
-
- return join(' ', map { ".$_" } @{$exts});
+ return join( ' ', map { ".$_" } @{$self->{extensions}} );
}
BEGIN {
}
1;
-package App::Ack::Filter::FirstLineMatch;
-
+package App::Ack::Filter::ExtensionGroup;
use strict;
}
sub new {
- my ( $class, $re ) = @_;
-
- $re =~ s{^/|/$}{}g; # XXX validate?
- $re = qr{$re}i;
+ my ( $class ) = @_;
return bless {
- regex => $re,
+ data => {},
}, $class;
}
-# This test reads the first 250 characters of a file, then just uses the
-# first line found in that. This prevents reading something like an entire
-# .min.js file (which might be only one "line" long) into memory.
-
-sub filter {
- my ( $self, $resource ) = @_;
-
- my $re = $self->{'regex'};
-
- my $line = $resource->firstliney;
-
- return $line =~ /$re/;
-}
-
-sub inspect {
- my ( $self ) = @_;
-
- my $re = $self->{'regex'};
-
- return ref($self) . " - $re";
-}
-
-sub to_string {
- my ( $self ) = @_;
-
- (my $re = $self->{regex}) =~ s{\([^:]*:(.*)\)$}{$1};
-
- return "first line matches /$re/";
-}
-
-BEGIN {
- App::Ack::Filter->register_filter(firstlinematch => __PACKAGE__);
-}
-
-1;
-package App::Ack::Filter::Is;
-
-
-use strict;
-use warnings;
-BEGIN {
- our @ISA = 'App::Ack::Filter';
-}
-
-use File::Spec 3.00 ();
-
-sub new {
- my ( $class, $filename ) = @_;
+sub add {
+ my ( $self, $filter ) = @_;
- return bless {
- filename => $filename,
- groupname => 'IsGroup',
- }, $class;
-}
+ foreach my $ext (@{$filter->{extensions}}) {
+ $self->{data}->{lc $ext} = 1;
+ }
-sub create_group {
- return App::Ack::Filter::IsGroup->new();
+ return;
}
sub filter {
- my ( $self, $resource ) = @_;
+ my ( $self, $file ) = @_;
- my $filename = $self->{'filename'};
- my $base = (File::Spec->splitpath($resource->name))[2];
+ if ($file->name =~ /[.]([^.]*)$/) {
+ return exists $self->{'data'}->{lc $1};
+ }
- return $base eq $filename;
+ return 0;
}
sub inspect {
my ( $self ) = @_;
- my $filename = $self->{'filename'};
-
- return ref($self) . " - $filename";
+ return ref($self) . " - $self";
}
sub to_string {
my ( $self ) = @_;
- my $filename = $self->{'filename'};
-
- return $filename;
-}
-
-BEGIN {
- App::Ack::Filter->register_filter(is => __PACKAGE__);
+ return join(' ', map { ".$_" } sort keys %{$self->{data}});
}
1;
-package App::Ack::Filter::Match;
+package App::Ack::Filter::FirstLineMatch;
+
+
use strict;
use warnings;
our @ISA = 'App::Ack::Filter';
}
-use File::Spec 3.00;
-
-
sub new {
my ( $class, $re ) = @_;
$re =~ s{^/|/$}{}g; # XXX validate?
- $re = qr/$re/i;
+ $re = qr{$re}i;
return bless {
regex => $re,
- groupname => 'MatchGroup',
}, $class;
}
-sub create_group {
- return App::Ack::Filter::MatchGroup->new;
-}
+# This test reads the first 250 characters of a file, then just uses the
+# first line found in that. This prevents reading something like an entire
+# .min.js file (which might be only one "line" long) into memory.
sub filter {
- my ( $self, $resource ) = @_;
+ my ( $self, $file ) = @_;
- my $re = $self->{'regex'};
-
- return $resource->basename =~ /$re/;
+ return $file->firstliney =~ /$self->{regex}/;
}
sub inspect {
my ( $self ) = @_;
- my $re = $self->{'regex'};
-
- print ref($self) . " - $re";
- return;
+ return ref($self) . ' - ' . $self->{regex};
}
sub to_string {
my ( $self ) = @_;
- my $re = $self->{'regex'};
-
- return "filename matches $re";
-}
+ (my $re = $self->{regex}) =~ s{\([^:]*:(.*)\)$}{$1};
-BEGIN {
- App::Ack::Filter->register_filter(match => __PACKAGE__);
+ return "First line matches /$re/";
}
-1;
-package App::Ack::Filter::Default;
-
-
-use strict;
-use warnings;
BEGIN {
- our @ISA = 'App::Ack::Filter';
-}
-
-sub new {
- my ( $class ) = @_;
-
- return bless {}, $class;
-}
-
-sub filter {
- my ( $self, $resource ) = @_;
-
- return -T $resource->name;
+ App::Ack::Filter->register_filter(firstlinematch => __PACKAGE__);
}
1;
}
sub filter {
- my ( $self, $resource ) = @_;
+ my ( $self, $file ) = @_;
- my $filter = $self->{'filter'};
- return !$filter->filter( $resource );
+ return !$self->{filter}->filter( $file );
}
sub invert {
}
1;
-package App::Ack::Filter::Collection;
+package App::Ack::Filter::Is;
use strict;
our @ISA = 'App::Ack::Filter';
}
+use File::Spec 3.00 ();
+
sub new {
- my ( $class ) = @_;
+ my ( $class, $filename ) = @_;
return bless {
- groups => {},
- ungrouped => [],
+ filename => $filename,
+ groupname => 'IsGroup',
}, $class;
}
-sub filter {
- my ( $self, $resource ) = @_;
-
- for my $group (values %{$self->{'groups'}}) {
- if ($group->filter($resource)) {
- return 1;
- }
- }
-
- for my $filter (@{$self->{'ungrouped'}}) {
- if ($filter->filter($resource)) {
- return 1;
- }
- }
-
- return 0;
+sub create_group {
+ return App::Ack::Filter::IsGroup->new();
}
-sub add {
- my ( $self, $filter ) = @_;
-
- if (exists $filter->{'groupname'}) {
- my $group = ($self->{groups}->{$filter->{groupname}} ||= $filter->create_group());
- $group->add($filter);
- }
- else {
- push @{$self->{'ungrouped'}}, $filter;
- }
+sub filter {
+ my ( $self, $file ) = @_;
- return;
+ return (File::Spec->splitpath($file->name))[2] eq $self->{filename};
}
sub inspect {
my ( $self ) = @_;
- return ref($self) . " - $self";
+ return ref($self) . ' - ' . $self->{filename};
}
sub to_string {
my ( $self ) = @_;
- my $ungrouped = $self->{'ungrouped'};
+ return $self->{filename};
+}
- return join(', ', map { "($_)" } @{$ungrouped});
+BEGIN {
+ App::Ack::Filter->register_filter(is => __PACKAGE__);
}
1;
our @ISA = 'App::Ack::Filter';
}
-use File::Spec 3.00 ();
-
sub new {
my ( $class ) = @_;
}
sub filter {
- my ( $self, $resource ) = @_;
+ my ( $self, $file ) = @_;
- my $data = $self->{'data'};
- my $base = $resource->basename;
-
- return exists $data->{$base};
+ return exists $self->{data}->{ $file->basename };
}
sub inspect {
}
1;
-package App::Ack::Filter::ExtensionGroup;
+package App::Ack::Filter::IsPath;
use strict;
our @ISA = 'App::Ack::Filter';
}
+
sub new {
- my ( $class ) = @_;
+ my ( $class, $filename ) = @_;
return bless {
- data => {},
+ filename => $filename,
+ groupname => 'IsPathGroup',
}, $class;
}
-sub add {
- my ( $self, $filter ) = @_;
-
- foreach my $ext (@{$filter->{extensions}}) {
- $self->{data}->{lc $ext} = 1;
- }
-
- return;
+sub create_group {
+ return App::Ack::Filter::IsPathGroup->new();
}
sub filter {
- my ( $self, $resource ) = @_;
-
- if ($resource->name =~ /[.]([^.]*)$/) {
- return exists $self->{'data'}->{lc $1};
- }
+ my ( $self, $file ) = @_;
- return 0;
+ return $file->name eq $self->{filename};
}
sub inspect {
my ( $self ) = @_;
- return ref($self) . " - $self";
+ return ref($self) . ' - ' . $self->{filename};
}
sub to_string {
my ( $self ) = @_;
- return join(' ', map { ".$_" } sort keys %{$self->{data}});
+ return $self->{filename};
}
1;
-package App::Ack::Filter::MatchGroup;
+package App::Ack::Filter::IsPathGroup;
use strict;
my ( $class ) = @_;
return bless {
- matches => [],
- big_re => undef,
+ data => {},
}, $class;
}
sub add {
my ( $self, $filter ) = @_;
- push @{ $self->{matches} }, $filter->{regex};
-
- my $re = join('|', map { "(?:$_)" } @{ $self->{matches} });
- $self->{big_re} = qr/$re/;
+ $self->{data}->{ $filter->{filename} } = 1;
return;
}
sub filter {
- my ( $self, $resource ) = @_;
-
- my $re = $self->{big_re};
+ my ( $self, $file ) = @_;
- return $resource->basename =~ /$re/;
+ return exists $self->{data}->{$file->name};
}
sub inspect {
my ( $self ) = @_;
- # XXX Needs an explicit return.
+ return ref($self) . " - $self";
}
sub to_string {
my ( $self ) = @_;
- # XXX Needs an explicit return.
+ return join(' ', keys %{$self->{data}});
}
1;
-package App::Ack::Filter::IsPath;
-
+package App::Ack::Filter::Match;
use strict;
use warnings;
}
+
sub new {
- my ( $class, $filename ) = @_;
+ my ( $class, $re ) = @_;
+
+ $re =~ s{^/|/$}{}g; # XXX validate?
+ $re = qr/$re/i;
return bless {
- filename => $filename,
- groupname => 'IsPathGroup',
+ regex => $re,
+ groupname => 'MatchGroup',
}, $class;
}
sub create_group {
- return App::Ack::Filter::IsPathGroup->new();
+ return App::Ack::Filter::MatchGroup->new;
}
sub filter {
- my ( $self, $resource ) = @_;
+ my ( $self, $file ) = @_;
- return $resource->name eq $self->{'filename'};
+ return $file->basename =~ /$self->{regex}/;
}
sub inspect {
my ( $self ) = @_;
- my $filename = $self->{'filename'};
-
- return ref($self) . " - $filename";
+ return ref($self) . ' - ' . $self->{regex};
}
sub to_string {
my ( $self ) = @_;
- my $filename = $self->{'filename'};
+ return "Filename matches $self->{regex}";
+}
- return $filename;
+BEGIN {
+ App::Ack::Filter->register_filter(match => __PACKAGE__);
}
1;
-package App::Ack::Filter::IsPathGroup;
-
-
+package App::Ack::Filter::MatchGroup;
use strict;
my ( $class ) = @_;
return bless {
- data => {},
+ matches => [],
+ big_re => undef,
}, $class;
}
sub add {
my ( $self, $filter ) = @_;
- $self->{data}->{ $filter->{filename} } = 1;
+ push @{ $self->{matches} }, $filter->{regex};
+
+ my $re = join('|', map { "(?:$_)" } @{ $self->{matches} });
+ $self->{big_re} = qr/$re/;
return;
}
sub filter {
- my ( $self, $resource ) = @_;
-
- my $data = $self->{'data'};
+ my ( $self, $file ) = @_;
- return exists $data->{$resource->name};
+ return $file->basename =~ /$self->{big_re}/;
}
-sub inspect {
- my ( $self ) = @_;
-
- return ref($self) . " - $self";
-}
-
-sub to_string {
- my ( $self ) = @_;
-
- return join(' ', keys %{$self->{data}});
-}
+# This class has no inspect() or to_string() method.
+# It will just use the default one unless someone writes something useful.
1;
package File::Next;
use warnings;
-our $VERSION = '1.16';
+our $VERSION = '1.18';
my ($parms,@queue) = _setup( \%files_defaults, @_ );
+ my $filter = $parms->{file_filter};
return sub {
- my $filter = $parms->{file_filter};
- while (@queue) {
- my ($dirname,$file,$fullpath) = splice( @queue, 0, 3 );
- if ( -f $fullpath || -p _ || $fullpath =~ m{^/dev/fd} ) {
+ 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;
}
return wantarray ? ($dirname,$file,$fullpath) : $fullpath;
}
- if ( -d _ ) {
+ if ( $is_dir ) {
unshift( @queue, _candidate_files( $parms, $fullpath ) );
}
} # while
+
sub from_file {
die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__);
my $err = $parms->{error_handler};
my $warn = $parms->{warning_handler};
- my $filename = $queue[1];
+ my $filename = $queue[0]->[1];
if ( !defined($filename) ) {
$err->( 'Must pass a filename to from_file()' );
}
}
+ my $filter = $parms->{file_filter};
return sub {
- my $filter = $parms->{file_filter};
local $/ = $parms->{nul_separated} ? "\x00" : $/;
while ( my $fullpath = <$fh> ) {
chomp $fullpath;
}
# Any leftover keys are bogus
- for my $badkey ( keys %passed_parms ) {
+ for my $badkey ( sort keys %passed_parms ) {
my $sub = (caller(1))[3];
$parms->{error_handler}->( "Invalid option passed to $sub(): $badkey" );
}
for ( @_ ) {
my $start = reslash( $_ );
- if (-d $start) {
- push @queue, ($start,undef,$start);
- }
- else {
- push @queue, (undef,$start,$start);
- }
+ 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 ];
}
return ($parms,@queue);
my @newfiles;
my $descend_filter = $parms->{descend_filter};
my $follow_symlinks = $parms->{follow_symlinks};
- my $sort_sub = $parms->{sort_files};
for my $file ( grep { !exists $skip_dirs{$_} } readdir $dh ) {
- my $has_stat;
-
my $fullpath = File::Spec->catdir( $dirname, $file );
if ( !$follow_symlinks ) {
next if -l $fullpath;
- $has_stat = 1;
}
+ else {
+ stat($fullpath);
+ }
+ my $is_dir = -d _;
+ my $is_file = -f _;
+ my $is_fifo = (-p _) || ($fullpath =~ m{^/dev/fd});
# Only do directory checking if we have a descend_filter
if ( $descend_filter ) {
- if ( $has_stat ? (-d _) : (-d $fullpath) ) {
+ if ( $is_dir ) {
local $File::Next::dir = $fullpath;
local $_ = $file;
next if not $descend_filter->();
}
}
- if ( $sort_sub ) {
- push( @newfiles, [ $dirname, $file, $fullpath ] );
- }
- else {
- push( @newfiles, $dirname, $file, $fullpath );
- }
+ push @newfiles, [ $dirname, $file, $fullpath, $is_dir, $is_file, $is_fifo ];
}
closedir $dh;
+ my $sort_sub = $parms->{sort_files};
if ( $sort_sub ) {
- return map { @{$_} } sort $sort_sub @newfiles;
+ @newfiles = sort $sort_sub @newfiles;
}
return @newfiles;
}
+
1; # End of File::Next