From b699001601519f32f04229b0b11897b5bf895622 Mon Sep 17 00:00:00 2001 From: Tony Duckles Date: Fri, 10 Jan 2025 05:58:44 -0600 Subject: [PATCH] bin/ack: ack v3.8.1 --- bin/ack | 977 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 670 insertions(+), 307 deletions(-) diff --git a/bin/ack b/bin/ack index 0af126e..a0e11df 100755 --- a/bin/ack +++ b/bin/ack @@ -10,16 +10,18 @@ # $App::Ack::STANDALONE = 1; +{ package main; use strict; use warnings; -our $VERSION = 'v3.4.0'; # Check https://beyondgrep.com/ for updates +our $VERSION = 'v3.8.1'; # Check https://beyondgrep.com/ for updates use 5.010001; use File::Spec (); +use Getopt::Long (); @@ -43,7 +45,6 @@ our $opt_passthru; 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; @@ -53,11 +54,17 @@ our $opt_v; # Flag if we need any context tracking. our $is_tracking_context; -# The regex that we search for in each file. -our $search_re; +# The regex that we use to match each line in the file. +our $re_match; + +# Regex for matching for highlighting in matched lines. +our $re_hilite; -# Special /m version of our $search_re. -our $scan_re; +# The regex that matches for things we want to exclude via the --not option. +our $re_not; + +# Version of the match regex for checking to see if the file should be scanned line-by-line. +our $re_scan; our @special_vars_used_by_opt_output; @@ -111,8 +118,8 @@ MAIN: { $ENV{ACK_COLOR_COLNO} ||= 'bold yellow'; } - my $p = App::Ack::ConfigLoader::opt_parser( 'no_auto_abbrev', 'pass_through' ); - $p->getoptions( + App::Ack::ConfigLoader::configure_parser( 'no_auto_abbrev', 'pass_through' ); + Getopt::Long::GetOptions( help => sub { App::Ack::show_help(); exit; }, version => sub { App::Ack::print( App::Ack::get_version_statement() ); exit; }, man => sub { App::Ack::show_man(); }, @@ -146,7 +153,6 @@ MAIN: { $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}; @@ -158,10 +164,10 @@ MAIN: { } if ( $opt_range_start ) { - ($opt_range_start, undef) = build_regex( $opt_range_start, {} ); + ($opt_range_start, undef) = App::Ack::build_regex( $opt_range_start, {} ); } if ( $opt_range_end ) { - ($opt_range_end, undef) = build_regex( $opt_range_end, {} ); + ($opt_range_end, undef) = App::Ack::build_regex( $opt_range_end, {} ); } $using_ranges = $opt_range_start || $opt_range_end; @@ -202,14 +208,18 @@ MAIN: { } } + # 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; + defined $opt_regex or App::Ack::die( 'No regular expression found.' ); + ($re_match, $re_not, $re_hilite, $re_scan) = App::Ack::build_all_regexes( $opt_regex, $opt ); + $stats{re_match} = $re_match; + $stats{re_not} = $re_not; + $stats{re_hilite} = $re_hilite; + $stats{re_scan} = $re_scan; } else { if ( $opt_f ) { @@ -217,13 +227,12 @@ MAIN: { } 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 ); + defined $opt_regex or App::Ack::die( 'No regular expression found.' ); + ($re_match, $re_not, $re_hilite, $re_scan) = App::Ack::build_all_regexes( $opt_regex, $opt ); + $stats{re_match} = $re_match; + $stats{re_not} = $re_not; + $stats{re_hilite} = $re_hilite; + $stats{re_scan} = $re_scan; } my @start; if ( not defined $opt->{files_from} ) { @@ -271,7 +280,7 @@ MAIN: { if ( $opt_debug ) { require List::Util; - my @stats = qw( search_re scan_re prescans linescans filematches linematches ); + my @stats = qw( re_match re_scan re_not prescans linescans filematches linematches ); my $width = List::Util::max( map { length } @stats ); for my $stat ( @stats ) { @@ -282,9 +291,11 @@ MAIN: { close $App::Ack::fh; App::Ack::exit_from_ack( $nmatches ); -} +} # End of MAIN + + +exit 0; -# End of MAIN sub file_loop_fg { my $files = shift; @@ -391,6 +402,7 @@ sub _compile_descend_filter { }; } + sub _compile_file_filter { my ( $opt, $start ) = @_; @@ -422,7 +434,7 @@ sub _compile_file_filter { return sub { if ( $opt_g ) { - if ( $File::Next::name =~ /$search_re/o ) { + if ( $File::Next::name =~ /$re_match/o ) { return 0 if $opt_v; } else { @@ -443,7 +455,7 @@ sub _compile_file_filter { else { my @dirs = File::Spec->splitdir($File::Next::dir); - my $is_ignoring = 0; + my $is_ignoring; for ( my $i = 0; $i < @dirs; $i++) { my $dir_rsrc = App::Ack::File->new(File::Spec->catfile(@dirs[0 .. $i])); @@ -497,7 +509,7 @@ sub _compile_file_filter { $match_found = 0; } return $match_found; - }; + }; # End of compiled sub } @@ -522,97 +534,6 @@ sub get_file_id { } } -# Returns a regex object based on a string and command-line options. -# Dies when the regex $str is undefined (i.e. not given on command line). - -sub build_regex { - my $str = shift; - my $opt = shift; - - defined $str or App::Ack::die( 'No regular expression found.' ); - - 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 $ok = 1; - - if ( $str =~ /^\\[wd]/ ) { - # Explicit \w is good. - } - else { - # Can start with \w, (, [ or dot. - if ( $str !~ /^[\w\(\[\.]/ ) { - $ok = 0; - } - } - - # Can end with \w, }, ), ], +, *, or dot. - if ( $str !~ /[\w\}\)\]\+\*\?\.]$/ ) { - $ok = 0; - } - # ... unless it's escaped. - elsif ( $str =~ /\\[\}\)\]\+\*\?\.]$/ ) { - $ok = 0; - } - - if ( !$ok ) { - App::Ack::die( '-w will not do the right thing if your regex does not begin and end with a word character.' ); - } - - if ( $str =~ /^\w+$/ ) { - # No need for fancy regex if it's a simple word. - $str = sprintf( '\b(?:%s)\b', $str ); - } - else { - $str = sprintf( '(?:^|\b|\s)\K(?:%s)(?=\s|\b|$)', $str ); - } - } - - if ( $opt->{i} || ($opt->{S} && $regex_is_lc) ) { - $_ = "(?i)$_" for ( $str, $scan_str ); - } - - my $scan_regex = undef; - my $regex = eval { qr/$str/ }; - if ( $regex ) { - if ( $scan_str !~ /\$/ ) { - # No line_scan is possible if there's a $ in the regex. - $scan_regex = eval { qr/$scan_str/m }; - } - } - else { - my $err = $@; - chomp $err; - App::Ack::die( "Invalid regex '$str':\n $err" ); - } - - return ($regex, $scan_regex); -} my $match_colno; @@ -634,7 +555,7 @@ my $after_context_pending; my $printed_lineno; my $is_first_match; -state $has_printed_from_any_file = 0; +state $has_printed_from_any_file; sub file_loop_normal { @@ -662,7 +583,7 @@ sub file_loop_normal { my $needs_line_scan = 1; if ( !$opt_passthru && !$opt_v ) { $stats{prescans}++; - if ( $file->may_be_present( $scan_re ) ) { + if ( $file->may_be_present( $re_scan ) ) { $file->reset(); } else { @@ -683,12 +604,8 @@ sub file_loop_normal { sub print_matches_in_file { my $file = shift; - 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; - my $has_printed_from_this_file = 0; - my $fh = $file->open; if ( !$fh ) { if ( $App::Ack::report_bad_filenames ) { @@ -703,83 +620,181 @@ sub print_matches_in_file { } # Check for context before the main loop, so we don't pay for it if we don't need it. + my $nmatches; + my $max_count = $opt_m || -1; # Go negative for no limit so it can never reduce to 0. if ( $is_tracking_context ) { - local $_ = undef; + $nmatches = pmif_context( $fh, $filename, $display_filename, $max_count ); + } + elsif ( $opt_passthru ) { + $nmatches = pmif_passthru( $fh, $filename, $display_filename, $max_count ); + } + elsif ( $opt_v ) { + $nmatches = pmif_opt_v( $fh, $filename, $display_filename, $max_count ); + } + else { + $nmatches = pmif_normal( $fh, $filename, $display_filename, $max_count ); + } + + return $nmatches; +} - $after_context_pending = 0; - my $in_range = range_setup(); +sub pmif_context { + my $fh = shift; + my $filename = shift; + my $display_filename = shift; + my $max_count = shift; - while ( <$fh> ) { - chomp; - $match_colno = undef; + my $in_range = range_setup(); + my $has_printed_from_this_file; + my $nmatches = 0; - $in_range = 1 if ( $using_ranges && !$in_range && $opt_range_start && /$opt_range_start/o ); + $after_context_pending = 0; + local $_ = undef; - 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; - } + while ( <$fh> ) { + chomp; + $match_colno = undef; + + $in_range = 1 if ( $using_ranges && !$in_range && defined($opt_range_start) && /$opt_range_start/o ); + + my $does_match; + if ( $in_range ) { + $does_match = /$re_match/o; + if ( $does_match && defined($re_not) ) { + local @-; + $does_match = !/$re_not/o; + } + if ( $opt_v ) { + $does_match = !$does_match; + } + else { + if ( $does_match ) { + # @- = @LAST_MATCH_START + $match_colno = $-[0] + 1; } } + } - 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 ); - } + if ( $does_match && $max_count ) { + if ( !$has_printed_from_this_file ) { + $stats{filematches}++; + if ( $opt_break && $has_printed_from_any_file ) { + App::Ack::print_blank_line(); + } + if ( $opt_show_filename && $opt_heading ) { + App::Ack::say( $display_filename ); } - print_line_with_context( $filename, $_, $. ); - $has_printed_from_this_file = 1; - $stats{linematches}++; - $nmatches++; - $max_count--; } - else { - if ( $after_context_pending ) { - # Disable $opt_column since there are no matches in the context lines. - local $opt_column = 0; - print_line_with_options( $filename, $_, $., '-' ); - --$after_context_pending; + print_line_with_context( $filename, $_, $. ); + $has_printed_from_this_file = 1; + $stats{linematches}++; + $nmatches++; + $max_count--; + } + else { + if ( $after_context_pending ) { + # Disable $opt_column since there are no matches in the context lines. + local $opt_column = 0; + print_line_with_options( $filename, $_, $., '-' ); + --$after_context_pending; + } + elsif ( $n_before_ctx_lines ) { + # Save line for "before" context. + $before_context_buf[$before_context_pos] = $_; + $before_context_pos = ($before_context_pos+1) % $n_before_ctx_lines; + } + } + + $in_range = 0 if ( $using_ranges && $in_range && defined($opt_range_end) && /$opt_range_end/o ); + + last if ($max_count == 0) && ($after_context_pending == 0); + } + + return $nmatches; +} + + +sub pmif_passthru { + my $fh = shift; + my $filename = shift; + my $display_filename = shift; + my $max_count = shift; + + my $in_range = range_setup(); + my $has_printed_from_this_file; + my $nmatches = 0; + + local $_ = undef; + + while ( <$fh> ) { + chomp; + + $in_range = 1 if ( $using_ranges && !$in_range && defined($opt_range_start) && /$opt_range_start/o ); + + $match_colno = undef; + my $does_match = /$re_match/o; + if ( $does_match && defined($re_not) ) { + local @-; + $does_match = !/$re_not/o; + } + if ( $in_range && $does_match ) { + $match_colno = $-[0] + 1; + if ( !$has_printed_from_this_file ) { + if ( $opt_break && $has_printed_from_any_file ) { + App::Ack::print_blank_line(); } - 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; + if ( $opt_show_filename && $opt_heading ) { + App::Ack::say( $display_filename ); } } + print_line_with_options( $filename, $_, $., ':' ); + $has_printed_from_this_file = 1; + $nmatches++; + $max_count--; + } + else { + if ( $opt_break && !$has_printed_from_this_file && $has_printed_from_any_file ) { + App::Ack::print_blank_line(); + } + print_line_with_options( $filename, $_, $., '-', 1 ); + $has_printed_from_this_file = 1; + } - $in_range = 0 if ( $using_ranges && $in_range && $opt_range_end && /$opt_range_end/o ); + $in_range = 0 if ( $using_ranges && $in_range && defined($opt_range_end) && /$opt_range_end/o ); - last if ($max_count == 0) && ($after_context_pending == 0); - } + last if $max_count == 0; } - elsif ( $opt_passthru ) { - local $_ = undef; - my $in_range = range_setup(); + return $nmatches; +} - while ( <$fh> ) { - chomp; - $in_range = 1 if ( $using_ranges && !$in_range && $opt_range_start && /$opt_range_start/o ); +sub pmif_opt_v { + my $fh = shift; + my $filename = shift; + my $display_filename = shift; + my $max_count = shift; - $match_colno = undef; - if ( $in_range && ($opt_v xor /$search_re/o) ) { - if ( !$opt_v ) { - $match_colno = $-[0] + 1; - } + my $in_range = range_setup(); + my $has_printed_from_this_file; + my $nmatches = 0; + + $match_colno = undef; + local $_ = undef; + + while ( <$fh> ) { + chomp; + + $in_range = 1 if ( $using_ranges && !$in_range && defined($opt_range_start) && /$opt_range_start/o ); + + if ( $in_range ) { + my $does_match = /$re_match/o; + if ( $does_match && defined($re_not) ) { + # local @-; No need to localize this because we don't use @-. + $does_match = !/$re_not/o; + } + if ( !$does_match ) { if ( !$has_printed_from_this_file ) { if ( $opt_break && $has_printed_from_any_file ) { App::Ack::print_blank_line(); @@ -788,105 +803,81 @@ sub print_matches_in_file { App::Ack::say( $display_filename ); } } - print_line_with_options( $filename, $_, $., ':' ); + print_line_with_context( $filename, $_, $. ); $has_printed_from_this_file = 1; $nmatches++; $max_count--; } - else { - if ( $opt_break && !$has_printed_from_this_file && $has_printed_from_any_file ) { - App::Ack::print_blank_line(); - } - print_line_with_options( $filename, $_, $., '-', 1 ); - $has_printed_from_this_file = 1; - } - - $in_range = 0 if ( $using_ranges && $in_range && $opt_range_end && /$opt_range_end/o ); - - last if $max_count == 0; } - } - elsif ( $opt_v ) { - local $_ = undef; - $match_colno = undef; - my $in_range = range_setup(); + $in_range = 0 if ( $using_ranges && $in_range && defined($opt_range_end) && /$opt_range_end/o ); - while ( <$fh> ) { - chomp; + last if $max_count == 0; + } - $in_range = 1 if ( $using_ranges && !$in_range && $opt_range_start && /$opt_range_start/o ); + return $nmatches; +} - 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::say( $display_filename ); - } - } - print_line_with_context( $filename, $_, $. ); - $has_printed_from_this_file = 1; - $nmatches++; - $max_count--; - } - } - $in_range = 0 if ( $using_ranges && $in_range && $opt_range_end && /$opt_range_end/o ); +sub pmif_normal { + my $fh = shift; + my $filename = shift; + my $display_filename = shift; + my $max_count = shift; - last if $max_count == 0; - } - } - else { # Normal search: No context, no -v, no --passthru - local $_ = undef; + my $in_range = range_setup(); + my $has_printed_from_this_file; + my $nmatches = 0; - my $last_match_lineno; - my $in_range = range_setup(); + my $last_match_lineno; + local $_ = undef; - while ( <$fh> ) { - chomp; + while ( <$fh> ) { + chomp; - $in_range = 1 if ( $using_ranges && !$in_range && $opt_range_start && /$opt_range_start/o ); + $in_range = 1 if ( $using_ranges && !$in_range && defined($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::say( $display_filename ); - } + if ( $in_range ) { + $match_colno = undef; + my $is_match = /$re_match/o; + if ( $is_match && defined($re_not) ) { + local @-; + $is_match = !/$re_not/o; + } + if ( $is_match ) { + $match_colno = $-[0] + 1; + if ( !$has_printed_from_this_file ) { + $stats{filematches}++; + if ( $opt_break && $has_printed_from_any_file ) { + App::Ack::print_blank_line(); } - if ( $opt_p ) { - if ( $last_match_lineno ) { - if ( $. > $last_match_lineno + $opt_p ) { - App::Ack::print_blank_line(); - } - } - elsif ( !$opt_break && $has_printed_from_any_file ) { + if ( $opt_show_filename && $opt_heading ) { + App::Ack::say( $display_filename ); + } + } + if ( $opt_p ) { + if ( $last_match_lineno ) { + if ( $. > $last_match_lineno + $opt_p ) { App::Ack::print_blank_line(); } } - s/[\r\n]+$//; - print_line_with_options( $filename, $_, $., ':' ); - $has_printed_from_this_file = 1; - $nmatches++; - $stats{linematches}++; - $max_count--; - $last_match_lineno = $.; + elsif ( !$opt_break && $has_printed_from_any_file ) { + App::Ack::print_blank_line(); + } } + s/[\r\n]+$//; + print_line_with_options( $filename, $_, $., ':' ); + $has_printed_from_this_file = 1; + $nmatches++; + $stats{linematches}++; + $max_count--; + $last_match_lineno = $.; } + } - $in_range = 0 if ( $using_ranges && $in_range && $opt_range_end && /$opt_range_end/o ); + $in_range = 0 if ( $using_ranges && $in_range && defined($opt_range_end) && /$opt_range_end/o ); - last if $max_count == 0; - } + last if $max_count == 0; } return $nmatches; @@ -919,7 +910,7 @@ sub print_line_with_options { } if ( $opt_output ) { - while ( $line =~ /$search_re/og ) { + while ( $line =~ /$re_match/og ) { my $output = $opt_output; if ( @special_vars_used_by_opt_output ) { no strict; @@ -941,7 +932,7 @@ sub print_line_with_options { # We have to do underlining before any highlighting because highlighting modifies string length. if ( $opt_underline && !$skip_coloring ) { - while ( $line =~ /$search_re/og ) { + while ( $line =~ /$re_hilite/og ) { my $match_start = $-[0] // next; my $match_end = $+[0]; my $match_length = $match_end - $match_start; @@ -956,7 +947,7 @@ sub print_line_with_options { if ( $opt_color && !$skip_coloring ) { my $highlighted = 0; # If highlighted, need to escape afterwards. - while ( $line =~ /$search_re/og ) { + while ( $line =~ /$re_hilite/og ) { my $match_start = $-[0] // next; my $match_end = $+[0]; my $match_length = $match_end - $match_start; @@ -1068,7 +1059,7 @@ sub count_matches_in_file { } else { if ( !$opt_v ) { - if ( !$file->may_be_present( $scan_re ) ) { + if ( !$file->may_be_present( $re_scan ) ) { $do_scan = 0; } } @@ -1083,20 +1074,28 @@ sub count_matches_in_file { if ( $using_ranges ) { while ( <$fh> ) { chomp; - $in_range = 1 if ( !$in_range && $opt_range_start && /$opt_range_start/o ); + $in_range = 1 if ( !$in_range && defined($opt_range_start) && /$opt_range_start/o ); if ( $in_range ) { - if ( /$search_re/o xor $opt_v ) { + my $is_match = /$re_match/o; + if ( $is_match && defined($re_not) ) { + $is_match = !/$re_not/o; + } + if ( $is_match xor $opt_v ) { ++$nmatches; last if $bail; } } - $in_range = 0 if ( $in_range && $opt_range_end && /$opt_range_end/o ); + $in_range = 0 if ( $in_range && defined($opt_range_end) && /$opt_range_end/o ); } } else { while ( <$fh> ) { chomp; - if ( /$search_re/o xor $opt_v ) { + my $is_match = /$re_match/o; + if ( $is_match && defined($re_not) ) { + $is_match = !/$re_not/o; + } + if ( $is_match xor $opt_v ) { ++$nmatches; last if $bail; } @@ -1245,18 +1244,18 @@ lines were in a range and which were out of the range. 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 +file until the first line that matches C<--range-end>. Similarly, if C<--range-end> is omitted, the range runs from the first line matching C<--range-start> to the end of the file. For example, if you wanted to search all HTML files up until the first instance of the C<< >>, you could do - ack foo --range-end='' + ack foo --html --range-end='' Or to search after Perl's `__DATA__` or `__END__` markers, you would do - ack pattern --range-end='^__(END|DATA)__' + ack pattern --perl --range-start='^__(END|DATA)__' It's possible for a range to start and stop on the same line. For example @@ -1299,6 +1298,25 @@ matches for lines within the range. Specifies an ackrc file to load after all others; see L. +=item B<--and=PATTERN> + +Specifies a I that MUST ALSO be found on a given line for a match to +occur. This option can be repeated. + +If you want to find all the lines with both "dogs" or "cats", use: + + ack dogs --and cats + +Note that the options that affect "dogs" also affect "cats", so if you have + + ack -i -w dogs --and cats + +then the search for both "dogs" and "cats" will be case-insensitive and be +word-limited. + +See also the other two boolean options C<--or> and C<--not>, neither of +which can be used with C<--and>. + =item B<-A I>, B<--after-context=I> Print I lines of trailing context after matching lines. @@ -1538,11 +1556,51 @@ Print this manual page. No descending into subdirectories. +=item B<--not=PATTERN> + +Specifies a I that must NOT be true on a given line for a match to +occur. This option can be repeated. + +If you want to find all the lines with "dogs" but not if "cats" or "fish" +appear on the line, use: + + ack dogs --not cats --not fish + +Note that the options that affect "dogs" also affect "cats" and "fish", so +if you have + + ack -i -w dogs --not cats + +then the search for both "dogs" and "cats" will be case-insensitive and be +word-limited. + +See also the other two boolean options C<--and> and C<--or>, neither of +which can be used with C<--not>. + =item B<-o> Show only the part of each line matching PATTERN (turns off text highlighting). This is exactly the same as C<--output=$&>. +=item B<--or=PATTERN> + +Specifies a I that MAY be found on a given line for a match to +occur. This option can be repeated. + +If you want to find all the lines with "dogs" or "cats", use: + + ack dogs --or cats + +Note that the options that affect "dogs" also affect "cats", so if you have + + ack -i -w dogs --or cats + +then the search for both "dogs" and "cats" will be case-insensitive and be +word-limited. + +See also the other two boolean options C<--and> and C<--not>, neither of +which can be used with C<--or>. + =item B<--output=I> Output the evaluation of I for each line (turns off text @@ -1573,7 +1631,7 @@ 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 +C<$&> is the string matched by the pattern, C<$`> is what precedes the match, and C<$'> is what follows it. If the pattern is C and the string is "lexicographic", then C<$&> is "graph", C<$`> is "lexico" and C<$'> is "ic". @@ -1832,7 +1890,7 @@ an F<.ackrc> file - then you do not have to define your types over and over again. In the following examples the options will always be shown on one command line so that they can be easily copy & pasted. -File types can be specified both with the the I<--type=xxx> option, +File types can be specified both with the I<--type=xxx> option, or the file type as an option itself. For example, if you create a filetype of "cobol", you can specify I<--type=cobol> or simply I<--cobol>. File types must be at least two characters long. This @@ -1931,8 +1989,8 @@ There are four different colors ack uses: -------- ----------------- ------------------ --------------- 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 + 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. @@ -2161,7 +2219,7 @@ Options are then loaded from the command line. ack is based at GitHub at L Please report any bugs or feature requests to the issues list at -Github: L. +GitHub: L. Please include the operating system that you're using; the output of the command C; and any customizations in your F<.ackrc> @@ -2188,7 +2246,7 @@ L L -=item * The ack issues list at Github +=item * The ack issues list at GitHub L @@ -2322,6 +2380,18 @@ mailing list. How appropriate to have Inowledgements! Thanks to everyone who has contributed to ack in any way, including +Geraint Edwards, +Loren Howard, +Yaroslav Halchenko, +Thiago Perrotta, +Thomas Gossler, +Kieran Mace, +Volker Glave, +Axel Beckert, +Eric Pement, +Gabor Szabo, +Frieder Bluemle, +Grzegorz Kaczmarczyk, Dan Book, Tomasz Konojacki, Salomon Smeke, @@ -2446,7 +2516,7 @@ Andy Lester, C<< >> =head1 COPYRIGHT & LICENSE -Copyright 2005-2020 Andy Lester. +Copyright 2005-2024 Andy Lester. This program is free software; you can redistribute it and/or modify it under the terms of the Artistic License v2.0. @@ -2457,6 +2527,8 @@ file that comes with the ack distribution. =cut 1; +} +{ package App::Ack; use warnings; @@ -2466,8 +2538,8 @@ use strict; our $VERSION; our $COPYRIGHT; BEGIN { - $VERSION = 'v3.4.0'; # Check https://beyondgrep.com/ for updates - $COPYRIGHT = 'Copyright 2005-2020 Andy Lester.'; + $VERSION = 'v3.8.1'; # Check https://beyondgrep.com/ for updates + $COPYRIGHT = 'Copyright 2005-2024 Andy Lester.'; } our $STANDALONE = 0; our $ORIGINAL_PROGRAM_NAME; @@ -2673,6 +2745,12 @@ Searching: --range-start PATTERN Specify PATTERN as the start of a match range. --range-end PATTERN Specify PATTERN as the end of a match range. --match PATTERN Specify PATTERN explicitly. Typically omitted. + --and PATTERN Specifies PATTERN that MUST also be found on + the line for a match to occur. Repeatable. + --or PATTERN Specifies PATTERN that MAY also be found on + the line for a match to occur. Repeatable. + --not PATTERN Specifies PATTERN that must NOT be found on + the line for a match to occur. Repeatable. Search output: --output=expr Output the evaluation of expr for each line @@ -2862,7 +2940,7 @@ sub show_help_rgb { ack allows customization of the colors it uses when presenting matches onscreen. See the "ACK COLORS" section of the ack manual (ack --man). -Colors may be specified as "rggNNN" where "NNN" is a triplet of digits +Colors may be specified as "rgbNNN" where "NNN" is a triplet of digits from 0 to 5 specifying the intensity of red, green and blue, respectively. Here is a grid of the 216 possible values for NNN. @@ -3083,7 +3161,7 @@ sub is_lowercase { # Get rid of any literal backslashes first to avoid confusion. $pat =~ s/\\\\//g; - my $metacharacter = qr/ + my $metacharacter = qr{ |\\A # Beginning of string |\\B # Not word boundary |\\c[a-zA-Z] # Control characters @@ -3101,7 +3179,7 @@ sub is_lowercase { |\\X # ??? |\\x[0-9A-Fa-f]{2} # Hex sequence |\\Z # End of string - /x; + }x; $pat =~ s/$metacharacter//g; my $name = qr/[_A-Za-z][_A-Za-z0-9]*?/; @@ -3121,7 +3199,168 @@ sub is_lowercase { } +# Returns a regex object based on a string and command-line options. +# Dies when the regex $str is undefined (i.e. not given on command line). + +sub build_regex { + my $str = shift; + my $opt = shift; + + # Check for lowercaseness before we do any modifications. + my $regex_is_lc = App::Ack::is_lowercase( $str ); + + if ( $opt->{Q} ) { + $str = quotemeta( $str ); + } + else { + # Compile the regex to see if it dies or throws warnings. + local $SIG{__WARN__} = sub { CORE::die @_ }; # Anything that warns becomes a die. + my $scratch_regex = eval { qr/$str/ }; + if ( not $scratch_regex ) { + my $err = $@; + chomp $err; + + if ( $err =~ m{^(.+?); marked by <-- HERE in m/(.+?) <-- HERE} ) { + my ($why, $where) = ($1,$2); + my $pointy = ' ' x (6+length($where)) . '^---HERE'; + App::Ack::die( "Invalid regex '$str'\nRegex: $str\n$pointy $why" ); + } + else { + App::Ack::die( "Invalid regex '$str'\n$err" ); + } + } + } + + my $scan_str = $str; + + # Whole words only. + if ( $opt->{w} ) { + my $ok = 1; + + if ( $str =~ /^\\[wd]/ ) { + # Explicit \w is good. + } + else { + # Can start with \w, (, [ or dot. + if ( $str !~ /^[\w\(\[\.]/ ) { + $ok = 0; + } + } + + # Can end with \w, }, ), ], +, *, or dot. + if ( $str !~ /[\w\}\)\]\+\*\?\.]$/ ) { + $ok = 0; + } + # ... unless it's escaped. + elsif ( $str =~ /\\[\}\)\]\+\*\?\.]$/ ) { + $ok = 0; + } + + if ( !$ok ) { + App::Ack::die( '-w will not do the right thing if your regex does not begin and end with a word character.' ); + } + + if ( $str =~ /^\w+$/ ) { + # No need for fancy regex if it's a simple word. + $str = sprintf( '\b(?:%s)\b', $str ); + } + else { + $str = sprintf( '(?:^|\b|\s)\K(?:%s)(?=\s|\b|$)', $str ); + } + } + + if ( $opt->{i} || ($opt->{S} && $regex_is_lc) ) { + $_ = "(?i)$_" for ( $str, $scan_str ); + } + + my $scan_regex = undef; + my $regex = eval { qr/$str/ }; + if ( $regex ) { + if ( $scan_str !~ /\$/ ) { + # No line_scan is possible if there's a $ in the regex. + $scan_regex = eval { qr/$scan_str/m }; + } + } + else { + my $err = $@; + chomp $err; + App::Ack::die( "Invalid regex '$str':\n $err" ); + } + + return ($regex, $scan_regex); +} + + +sub build_all_regexes { + my $opt_regex = shift; + my $opt = shift; + + my $re_match; + my $re_not; + my $re_hilite; + my $re_scan; + + my @parts; + + # AND: alpha and beta + if ( @parts = @{$opt->{and}} ) { + my @match_parts; + my @hilite_parts; + + for my $part ( @parts ) { + my ($match, undef) = build_regex( $part, $opt ); + push @match_parts, "(?=.*$match)"; + push @hilite_parts, $match; + } + + my ($match, $scan) = build_regex( $opt_regex, $opt ); + push @match_parts, ".*$match"; + push @hilite_parts, $match; + + $re_match = join( '', @match_parts ); + $re_hilite = join( '|', @hilite_parts ); + $re_scan = $scan; + } + # OR: alpha OR beta + elsif ( @parts = @{$opt->{or}} ) { + my @match_parts; + my @scan_parts; + + for my $part ( $opt_regex, @parts ) { + my ($match, $scan) = build_regex( $part, $opt ); + push @match_parts, $match; + push @scan_parts, $scan; + } + + $re_match = join( '|', @match_parts ); + $re_hilite = $re_match; + $re_scan = join( '|', @scan_parts ); + } + # NOT: alpha NOT beta + elsif ( @parts = @{$opt->{not}} ) { + ($re_match, $re_scan) = build_regex( $opt_regex, $opt ); + $re_hilite = $re_match; + + my @not_parts; + for my $part ( @parts ) { + (my $re, undef) = build_regex( $part, $opt ); + push @not_parts, $re; + } + $re_not = join( '|', @not_parts ); + } + # No booleans. + else { + ($re_match, $re_scan) = build_regex( $opt_regex, $opt ); + $re_hilite = $re_match; + } + + return ($re_match, $re_not, $re_hilite, $re_scan); +} + + 1; # End of App::Ack +} +{ package App::Ack::ConfigDefault; use warnings; @@ -3266,7 +3505,7 @@ sub _options_block { # core dumps --ignore-file=match:/core[.]\d+$/ -# minified Javascript +# minified JavaScript --ignore-file=match:/[.-]min[.]js$/ --ignore-file=match:/[.]js[.]min$/ @@ -3287,15 +3526,24 @@ sub _options_block { # Common archives, as an optimization --ignore-file=ext:gz,tar,tgz,zip -# Python compiles modules +# Python compiled modules --ignore-file=ext:pyc,pyd,pyo +# Python's pickle serialization format +# https://docs.python.org/2/library/pickle.html#example +# https://docs.python.org/3.7/library/pickle.html#examples +--ignore-file=ext:pkl,pickle + # C extensions --ignore-file=ext:so # Compiled gettext files --ignore-file=ext:mo +# Visual Studio user and workspace settings +# https://code.visualstudio.com/docs/getstarted/settings +--ignore-dir=is:.vscode + ### Filetypes defined # Makefiles @@ -3317,6 +3565,17 @@ sub _options_block { --type-add=cmake:is:CMakeLists.txt --type-add=cmake:ext:cmake +# Bazel build tool +# https://docs.bazel.build/versions/master/skylark/bzl-style.html +--type-add=bazel:ext:bzl +# https://docs.bazel.build/versions/master/guide.html#bazelrc-the-bazel-configuration-file +--type-add=bazel:ext:bazelrc +# https://docs.bazel.build/versions/master/build-ref.html#BUILD_files +--type-add=bazel:is:BUILD +# https://docs.bazel.build/versions/master/build-ref.html#workspace +--type-add=bazel:is:WORKSPACE + + # Actionscript --type-add=actionscript:ext:as,mxml @@ -3366,6 +3625,10 @@ sub _options_block { # C# --type-add=csharp:ext:cs +# Crystal-lang +# https://crystal-lang.org/ +--type-add=crystal:ext:cr,ecr + # CSS # https://www.w3.org/Style/CSS/ --type-add=css:ext:css @@ -3382,6 +3645,10 @@ sub _options_block { # https://elixir-lang.org/ --type-add=elixir:ext:ex,exs +# Elm +# https://elm-lang.org +--type-add=elm:ext:elm + # Emacs Lisp # https://www.gnu.org/software/emacs --type-add=elisp:ext:el @@ -3490,14 +3757,31 @@ sub _options_block { # https://plone.org/ --type-add=plone:ext:pt,cpt,metadata,cpy,py +# PowerShell +# https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_scripts +# https://learn.microsoft.com/en-us/powershell/scripting/developer/module/understanding-a-windows-powershell-module +--type-add=powershell:ext:ps1,psm1 + +# PureScript +# https://www.purescript.org +--type-add=purescript:ext:purs + # Python # https://www.python.org/ --type-add=python:ext:py --type-add=python:firstlinematch:/^#!.*\bpython/ +# Pytest +# https://pytest.org/ +# Pytest files are *.py files that start with test_ or end with _test.py +# https://docs.pytest.org/en/stable/explanation/goodpractices.html#test-discovery +--type-add=pytest:match:_test\.py$ +--type-add=pytest:match:^test_.*\.py$ + # R # https://www.r-project.org/ ---type-add=rr:ext:R +# https://r4ds.had.co.nz/r-markdown.html +--type-add=rr:ext:R,Rmd # reStructured Text # https://docutils.sourceforge.io/rst.html @@ -3519,7 +3803,7 @@ sub _options_block { # Scala # https://www.scala-lang.org/ ---type-add=scala:ext:scala +--type-add=scala:ext:scala,sbt # Scheme # https://groups.csail.mit.edu/mac/projects/scheme/ @@ -3558,6 +3842,10 @@ sub _options_block { # https://www.tcl.tk/ --type-add=tcl:ext:tcl,itcl,itk +# Terraform +# https://github.com/hashicorp/terraform +--type-add=terraform=.tf,.tfvars + # TeX & LaTeX # https://www.latex-project.org/ --type-add=tex:ext:tex,cls,sty @@ -3570,7 +3858,7 @@ sub _options_block { # https://toml.io/ --type-add=toml:ext:toml -# Typescript +# TypeScript # https://www.typescriptlang.org/ --type-add=ts:ext:ts,tsx @@ -3603,6 +3891,8 @@ HERE } 1; +} +{ package App::Ack::ConfigFinder; @@ -3698,6 +3988,8 @@ sub find_config_files { } 1; +} +{ package App::Ack::ConfigLoader; use strict; @@ -3705,10 +3997,10 @@ use warnings; use 5.010; use File::Spec 3.00 (); -use Getopt::Long 2.39 (); +use Getopt::Long 2.38 (); use Text::ParseWords 3.1 (); -sub opt_parser { +sub configure_parser { my @opts = @_; my @standard = qw( @@ -3718,9 +4010,12 @@ sub opt_parser { no_auto_version no_ignore_case ); - return Getopt::Long::Parser->new( config => [ @standard, @opts ] ); + Getopt::Long::Configure( @standard, @opts ); + + return; } + sub _generate_ignore_dir { my ( $option_name, $opt ) = @_; @@ -3884,17 +4179,17 @@ sub _process_filetypes { 'type-del=s' => $delete_spec, ); - my $p = opt_parser( 'no_auto_abbrev', 'pass_through' ); + configure_parser( 'no_auto_abbrev', 'pass_through' ); foreach my $source (@{$arg_sources}) { my $args = $source->{contents}; if ( ref($args) ) { # $args are modified in place, so no need to munge $arg_sources - $p->getoptionsfromarray( $args, %type_arg_specs ); + Getopt::Long::GetOptionsFromArray( $args, %type_arg_specs ); } else { ( undef, $source->{contents} ) = - $p->getoptionsfromstring( $args, %type_arg_specs ); + Getopt::Long::GetOptionsFromString( $args, %type_arg_specs ); } } @@ -3936,8 +4231,13 @@ sub get_arg_spec { return; } + $opt->{and} = []; + $opt->{or} = []; + $opt->{not} = []; + return { 1 => sub { $opt->{1} = $opt->{m} = 1 }, + 'and=s' => $opt->{and}, 'A|after-context:-1' => sub { shift; $opt->{A} = _context_value(shift) }, 'B|before-context:-1' => sub { shift; $opt->{B} = _context_value(shift) }, 'C|context:-1' => sub { shift; $opt->{B} = $opt->{A} = _context_value(shift) }, @@ -3999,6 +4299,8 @@ sub get_arg_spec { }, 'noignore-directory|noignore-dir=s' => _generate_ignore_dir('--noignore-dir', $opt), 'nopager' => sub { $opt->{pager} = undef }, + 'not=s' => $opt->{not}, + 'or=s' => $opt->{or}, 'passthru' => \$opt->{passthru}, 'print0' => \$opt->{print0}, 'p|proximate:1' => \$opt->{p}, @@ -4050,15 +4352,15 @@ sub _process_other { } if ( $argv_source ) { # This *should* always be true, but you never know... - my $p = opt_parser( 'pass_through' ); - $p->getoptionsfromarray( [ @{$argv_source} ], + configure_parser( 'pass_through' ); + Getopt::Long::GetOptionsFromArray( [ @{$argv_source} ], 'help-types' => \$is_help_types_active, ); } my $arg_specs = get_arg_spec( $opt, $extra_specs ); - my $p = opt_parser(); + configure_parser(); foreach my $source (@{$arg_sources}) { my ( $source_name, $args ) = @{$source}{qw/name contents/}; @@ -4090,11 +4392,11 @@ sub _process_other { my $ret; if ( ref($args) ) { - $ret = $p->getoptionsfromarray( $args, %{$args_for_source} ); + $ret = Getopt::Long::GetOptionsFromArray( $args, %{$args_for_source} ); } else { ( $ret, $source->{contents} ) = - $p->getoptionsfromstring( $args, %{$args_for_source} ); + Getopt::Long::GetOptionsFromString( $args, %{$args_for_source} ); } if ( !$ret ) { if ( !$is_help_types_active ) { @@ -4140,7 +4442,7 @@ sub _explode_sources { delete $arg_spec->{$arg}; }; - my $p = opt_parser( 'pass_through' ); + configure_parser( 'pass_through' ); foreach my $source (@{$sources}) { my ( $name, $options ) = @{$source}{qw/name contents/}; if ( ref($options) ne 'ARRAY' ) { @@ -4155,7 +4457,7 @@ sub _explode_sources { $j--; my @copy = @chunk; - $p->getoptionsfromarray( [@chunk], + Getopt::Long::GetOptionsFromArray( [@chunk], 'type-add=s' => $add_type, 'type-set=s' => $add_type, 'type-del=s' => $del_type, @@ -4231,18 +4533,18 @@ sub _remove_default_options_if_needed { my $should_remove = 0; - my $p = opt_parser( 'no_auto_abbrev', 'pass_through' ); + configure_parser( 'no_auto_abbrev', 'pass_through' ); foreach my $index ( $default_index + 1 .. $#{$sources} ) { my $args = $sources->[$index]->{contents}; if (ref($args)) { - $p->getoptionsfromarray( $args, + Getopt::Long::GetOptionsFromArray( $args, 'ignore-ack-defaults' => \$should_remove, ); } else { - ( undef, $sources->[$index]{contents} ) = $p->getoptionsfromstring( $args, + ( undef, $sources->[$index]{contents} ) = Getopt::Long::GetOptionsFromString( $args, 'ignore-ack-defaults' => \$should_remove, ); } @@ -4269,8 +4571,8 @@ sub process_args { foreach my $source (@{$arg_sources}) { if ( $source->{name} eq 'ARGV' ) { my $dump; - my $p = opt_parser( 'pass_through' ); - $p->getoptionsfromarray( $source->{contents}, + configure_parser( 'pass_through' ); + Getopt::Long::GetOptionsFromArray( $source->{contents}, 'dump' => \$dump, ); if ( $dump ) { @@ -4319,8 +4621,8 @@ sub retrieve_arg_sources { my $noenv; my $ackrc; - my $p = opt_parser( 'no_auto_abbrev', 'pass_through' ); - $p->getoptions( + configure_parser( 'no_auto_abbrev', 'pass_through' ); + Getopt::Long::GetOptions( 'noenv' => \$noenv, 'ackrc=s' => \$ackrc, ); @@ -4498,9 +4800,9 @@ sub _options_used { } # 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 ); + configure_parser( 'pass_through' ); # Ignore invalid options. + Getopt::Long::GetOptionsFromArray( [@ARGV], %spec_capture_raw ); + Getopt::Long::GetOptionsFromArray( [@ARGV], %spec_capture_parsed ); return (\@raw,\%parsed); } @@ -4553,6 +4855,9 @@ sub mutex_options { g => 1, l => 1, }, + I => { + f => 1, + }, L => { A => 1, B => 1, @@ -4577,6 +4882,11 @@ sub mutex_options { v => 1, 'with-filename' => 1, }, + and => { + g => 1, + not => 1, + or => 1, + }, break => { L => 1, c => 1, @@ -4617,6 +4927,7 @@ sub mutex_options { B => 1, C => 1, H => 1, + I => 1, L => 1, break => 1, c => 1, @@ -4627,6 +4938,7 @@ sub mutex_options { group => 1, h => 1, heading => 1, + i => 1, l => 1, m => 1, match => 1, @@ -4634,6 +4946,7 @@ sub mutex_options { output => 1, p => 1, passthru => 1, + 'smart-case' => 1, u => 1, v => 1, x => 1, @@ -4649,6 +4962,7 @@ sub mutex_options { C => 1, H => 1, L => 1, + and => 1, break => 1, c => 1, column => 1, @@ -4661,7 +4975,9 @@ sub mutex_options { l => 1, m => 1, match => 1, + not => 1, o => 1, + or => 1, output => 1, p => 1, passthru => 1, @@ -4688,6 +5004,9 @@ sub mutex_options { g => 1, l => 1, }, + i => { + f => 1, + }, l => { A => 1, B => 1, @@ -4725,6 +5044,10 @@ sub mutex_options { L => 1, l => 1, }, + not => { + and => 1, + g => 1, + }, o => { A => 1, B => 1, @@ -4742,6 +5065,10 @@ sub mutex_options { 'show-types' => 1, v => 1, }, + or => { + and => 1, + g => 1, + }, output => { A => 1, B => 1, @@ -4797,6 +5124,9 @@ sub mutex_options { o => 1, output => 1, }, + 'smart-case' => { + f => 1, + }, u => { f => 1, g => 1, @@ -4825,6 +5155,8 @@ sub mutex_options { 1; # End of App::Ack::ConfigLoader +} +{ package App::Ack::File; use warnings; @@ -4996,6 +5328,8 @@ sub firstliney { } 1; +} +{ package App::Ack::Files; @@ -5098,6 +5432,8 @@ sub _generate_error_handler { } 1; +} +{ package App::Ack::Filter; use strict; @@ -5151,6 +5487,8 @@ sub inspect { } 1; +} +{ package App::Ack::Filter::Collection; @@ -5210,6 +5548,8 @@ sub to_string { } 1; +} +{ package App::Ack::Filter::Default; @@ -5232,6 +5572,8 @@ sub filter { } 1; +} +{ package App::Ack::Filter::Extension; @@ -5282,6 +5624,8 @@ BEGIN { } 1; +} +{ package App::Ack::Filter::ExtensionGroup; @@ -5332,6 +5676,8 @@ sub to_string { } 1; +} +{ package App::Ack::Filter::FirstLineMatch; @@ -5383,6 +5729,8 @@ BEGIN { } 1; +} +{ package App::Ack::Filter::Inverse; @@ -5426,6 +5774,8 @@ sub inspect { } 1; +} +{ package App::Ack::Filter::Is; @@ -5473,6 +5823,8 @@ BEGIN { } 1; +} +{ package App::Ack::Filter::IsGroup; @@ -5517,6 +5869,8 @@ sub to_string { } 1; +} +{ package App::Ack::Filter::IsPath; @@ -5559,6 +5913,8 @@ sub to_string { } 1; +} +{ package App::Ack::Filter::IsPathGroup; @@ -5603,6 +5959,8 @@ sub to_string { } 1; +} +{ package App::Ack::Filter::Match; use strict; @@ -5652,6 +6010,8 @@ BEGIN { } 1; +} +{ package App::Ack::Filter::MatchGroup; @@ -5691,6 +6051,8 @@ sub filter { # It will just use the default one unless someone writes something useful. 1; +} +{ package File::Next; use strict; @@ -5919,3 +6281,4 @@ sub _candidate_files { 1; # End of File::Next +} -- 2.47.1