From f9c10e2e58708cce236c02dcca405084cfe71ebf Mon Sep 17 00:00:00 2001 From: Tony Duckles Date: Mon, 1 Jan 2018 10:56:22 -0600 Subject: [PATCH] bin/ack: ack v2.22 --- bin/ack | 898 +++++++++++++++++++++++++++----------------------------- 1 file changed, 425 insertions(+), 473 deletions(-) diff --git a/bin/ack b/bin/ack index 50d4290..80329a4 100755 --- a/bin/ack +++ b/bin/ack @@ -4,7 +4,7 @@ # Please DO NOT EDIT or send patches for it. # # Please take a look at the source from -# https://github.com/petdance/ack2 +# https://github.com/beyondgrep/ack2 # and submit patches against the individual files # that build ack. # @@ -13,8 +13,7 @@ package main; use strict; use warnings; - -our $VERSION = '2.15_01'; # Check http://beyondgrep.com/ for updates +our $VERSION = '2.22'; # Check https://beyondgrep.com/ for updates use 5.008008; use Getopt::Long 2.38 (); @@ -24,7 +23,7 @@ use File::Spec (); # XXX Don't make this so brute force -# See also: https://github.com/petdance/ack2/issues/89 +# See also: https://github.com/beyondgrep/ack2/issues/89 our $opt_after_context; our $opt_before_context; @@ -45,7 +44,8 @@ our $opt_L; our $opt_l; our $opt_passthru; our $opt_column; -# flag if we need any context tracking + +# Flag if we need any context tracking. our $is_tracking_context; # These are all our globals. @@ -63,9 +63,9 @@ MAIN: { last if ( $arg eq '--' ); # Get the --thpppt, --bar, --cathy checking out of the way. - $arg =~ /^--th[pt]+t+$/ and App::Ack::_thpppt($arg); - $arg eq '--bar' and App::Ack::_bar(); - $arg eq '--cathy' and App::Ack::_cathy(); + $arg =~ /^--th[pt]+t+$/ and App::Ack::thpppt($arg); + $arg eq '--bar' and App::Ack::ackbar(); + $arg eq '--cathy' and App::Ack::cathy(); # See if we want to ignore the environment. (Don't tell Al Gore.) $arg eq '--env' and $env_is_usable = 1; @@ -110,16 +110,16 @@ sub _compile_descend_filter { } } - # if we have one or more --noignore-dir directives, we can't ignore + # If we have one or more --noignore-dir directives, we can't ignore # entire subdirectory hierarchies, so we return an "accept all" - # filter and scrutinize the files more in _compile_file_filter + # filter and scrutinize the files more in _compile_file_filter. return if $dont_ignore_dirs; return unless $idirs; $idirs = $opt->{idirs}; return sub { - my $resource = App::Ack::Resource::Basic->new($File::Next::dir); + my $resource = App::Ack::Resource->new($File::Next::dir); return !grep { $_->filter($resource) } @{$idirs}; }; } @@ -147,8 +147,7 @@ sub _compile_file_filter { my @ignore_dir_filter = @{$opt->{idirs} || []}; my @is_inverted = map { $_->is_inverted() } @ignore_dir_filter; - # this depends on InverseFilter->invert returning the original - # filter (for optimization) + # This depends on InverseFilter->invert returning the original filter (for optimization). @ignore_dir_filter = map { $_->is_inverted() ? $_->invert() : $_ } @ignore_dir_filter; my $dont_ignore_dir_filter = grep { $_ } @is_inverted; my $previous_dir = ''; @@ -157,10 +156,10 @@ sub _compile_file_filter { return sub { if ( $opt_g ) { if ( $File::Next::name =~ /$opt_regex/ && $opt_v ) { - return; + return 0; } if ( $File::Next::name !~ /$opt_regex/ && !$opt_v ) { - return; + return 0; } } # ack always selects files that are specified on the command @@ -180,7 +179,7 @@ sub _compile_file_filter { my $is_ignoring = 0; for ( my $i = 0; $i < @dirs; $i++) { - my $dir_rsrc = App::Ack::Resource::Basic->new(File::Spec->catfile(@dirs[0 .. $i])); + my $dir_rsrc = App::Ack::Resource->new(File::Spec->catfile(@dirs[0 .. $i])); my $j = 0; for my $filter (@ignore_dir_filter) { @@ -218,7 +217,7 @@ sub _compile_file_filter { } } - my $resource = App::Ack::Resource::Basic->new($File::Next::name); + my $resource = App::Ack::Resource->new($File::Next::name); if ( $ifiles_filters && $ifiles_filters->filter($resource) ) { return 0; @@ -267,7 +266,7 @@ sub filetypes { my $filters = $App::Ack::mappings{$k}; foreach my $filter (@{$filters}) { - # clone the resource + # Clone the resource. my $clone = $resource->clone; if ( $filter->filter($clone) ) { push @matches, $k; @@ -291,12 +290,12 @@ sub get_file_id { return File::Next::reslash( $filename ); } else { - # XXX is this the best method? it always hits the FS + # XXX Is this the best method? It always hits the FS. if( my ( $dev, $inode ) = (stat($filename))[0, 1] ) { return join(':', $dev, $inode); } else { - # XXX this could be better + # XXX This could be better. return $filename; } } @@ -338,19 +337,19 @@ my $match_column_number; { -# number of context lines +# Number of context lines my $n_before_ctx_lines; my $n_after_ctx_lines; -# array to keep track of lines that might be required for a "before" context +# Array to keep track of lines that might be required for a "before" context my @before_context_buf; -# position to insert next line in @before_context_buf +# Position to insert next line in @before_context_buf my $before_context_pos; -# number of "after" context lines still pending +# Number of "after" context lines still pending my $after_context_pending; -# number of latest line that got printed +# Number of latest line that got printed my $printed_line_no; my $is_iterating; @@ -362,9 +361,8 @@ BEGIN { $has_printed_something = 0; } -# setup context tracking variables -sub setup_line_context { - +# 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); @@ -378,9 +376,8 @@ sub setup_line_context { return; } -# adjust context tracking variables when entering a new file -sub setup_line_context_for_file { - +# 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 ) { @@ -390,7 +387,7 @@ sub setup_line_context_for_file { return; } -=for Developers +=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, @@ -402,10 +399,12 @@ 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, $opt ) = @_; + my ( $resource ) = @_; my $max_count = $opt_m || -1; my $nmatches = 0; @@ -429,12 +428,11 @@ sub print_matches_in_resource { $display_filename = Term::ANSIColor::colored($display_filename, $ENV{ACK_COLOR_FILENAME}); } - # check for context before the main loop, so we don't - # pay for it if we don't need it + # 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; while ( <$fh> ) { - if ( does_match($opt, $_) && $max_count ) { + if ( does_match( $_ ) && $max_count ) { if ( !$has_printed_for_this_resource ) { if ( $opt_break && $has_printed_something ) { App::Ack::print_blank_line(); @@ -443,32 +441,32 @@ sub print_matches_in_resource { App::Ack::print_filename( $display_filename, $ors ); } } - print_line_with_context($opt, $filename, $_, $.); + print_line_with_context( $filename, $_, $. ); $has_printed_for_this_resource = 1; $nmatches++; $max_count--; } elsif ( $opt_passthru ) { - chomp; # XXX proper newline handling? - # XXX inline this call? + 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($opt, $filename, $_, $., ':'); + print_line_with_options( $filename, $_, $., ':' ); $has_printed_for_this_resource = 1; } else { - chomp; # XXX proper newline handling? - print_line_if_context($opt, $filename, $_, $., '-'); + chomp; # XXX Proper newline handling? + print_line_if_context( $filename, $_, $., '-' ); } last if ($max_count == 0) && ($after_context_pending == 0); } } else { - local $_; - if ( $opt_passthru ) { + local $_; + while ( <$fh> ) { $match_column_number = undef; if ( $opt_v ? !/$opt_regex/o : /$opt_regex/o ) { @@ -483,7 +481,7 @@ sub print_matches_in_resource { App::Ack::print_filename( $display_filename, $ors ); } } - print_line_with_context($opt, $filename, $_, $.); + print_line_with_context( $filename, $_, $. ); $has_printed_for_this_resource = 1; $nmatches++; $max_count--; @@ -493,13 +491,15 @@ sub print_matches_in_resource { if ( $opt_break && !$has_printed_for_this_resource && $has_printed_something ) { App::Ack::print_blank_line(); } - print_line_with_options($opt, $filename, $_, $., ':'); + print_line_with_options( $filename, $_, $., ':' ); $has_printed_for_this_resource = 1; } last unless $max_count != 0; } } elsif ( $opt_v ) { + local $_; + $match_column_number = undef; while ( <$fh> ) { if ( !/$opt_regex/o ) { @@ -511,7 +511,7 @@ sub print_matches_in_resource { App::Ack::print_filename( $display_filename, $ors ); } } - print_line_with_context($opt, $filename, $_, $.); + print_line_with_context( $filename, $_, $. ); $has_printed_for_this_resource = 1; $nmatches++; $max_count--; @@ -520,78 +520,39 @@ sub print_matches_in_resource { } } else { - # XXX unroll first match check ($has_printed_for_this_resource) - # XXX what if this is a *huge* file? (see also -l) - my $contents = do { - local $/; - <$fh>; - }; - - my $prev_match_end = 0; - my $line_no = 1; - - $match_column_number = undef; - while ( $contents =~ /$opt_regex/og ) { - my $match_start = $-[0]; - my $match_end = $+[0]; - - pos($contents) = $prev_match_end; - $prev_match_end = $match_end; - - while ( $contents =~ /\n/og && $-[0] < $match_start ) { - $line_no++; - } - - my $start_line = rindex($contents, "\n", $match_start); - my $end_line = index($contents, "\n", $match_end); - - if ( $start_line == -1 ) { - $start_line = 0; - } - else { - $start_line++; - } + local $_; - if ( $end_line == -1 ) { - $end_line = length($contents); - } - else { - $end_line--; - } - $match_column_number = $match_start - $start_line + 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 ); + 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 ) { + App::Ack::print_blank_line(); + } + if ( $opt_show_filename && $opt_heading ) { + App::Ack::print_filename( $display_filename, $ors ); + } } + s/[\r\n]+$//g; + print_line_with_options( $filename, $_, $., ':' ); + $has_printed_for_this_resource = 1; + $nmatches++; + $max_count--; } - my $line = substr($contents, $start_line, $end_line - $start_line + 1); - $line =~ s/[\r\n]+$//g; - print_line_with_options($opt, $filename, $line, $line_no, ':'); - - pos($contents) = $end_line + 1; - - $has_printed_for_this_resource = 1; - $nmatches++; - $max_count--; - last unless $max_count != 0; } } } - $is_iterating = 0; # XXX this won't happen on an exception - # then again, do we care? ack doesn't really - # handle exceptions anyway. + $is_iterating = 0; return $nmatches; } sub print_line_with_options { - my ( $opt, $filename, $line, $line_no, $separator ) = @_; + my ( $filename, $line, $line_no, $separator ) = @_; $has_printed_something = 1; $printed_line_no = $line_no; @@ -621,22 +582,23 @@ sub print_line_with_options { } if( $opt_output ) { while ( $line =~ /$opt_regex/og ) { - # XXX We need to stop using eval() for --output. See https://github.com/petdance/ack2/issues/421 + # 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 { if ( $opt_color ) { - $line =~ /$opt_regex/o; # this match is redundant, but we need - # to perfom it in order to get if - # capture groups are set + # 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 + if ( @+ > 1 ) { # If we have captures... while ( $line =~ /$opt_regex/og ) { - my $offset = 0; # additional offset for when we add stuff + my $offset = 0; # Additional offset for when we add stuff. my $previous_match_end = 0; + last if $-[0] == $+[0]; + for ( my $i = 1; $i < @+; $i++ ) { my ( $match_start, $match_end ) = ( $-[$i], $+[$i] ); @@ -651,7 +613,7 @@ sub print_line_with_options { substr( $line, $offset + $match_start, $match_end - $match_start, $substitution ); - $previous_match_end = $match_end; # offsets do not need to be applied + $previous_match_end = $match_end; # Offsets do not need to be applied. $offset += length( $substitution ) - length( $substring ); } @@ -659,13 +621,14 @@ sub print_line_with_options { } } else { - my $matched = 0; # flag; if matched, need to escape afterwards + 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; my $substring = substr( $line, $match_start, $match_end - $match_start ); @@ -678,7 +641,7 @@ sub print_line_with_options { pos($line) = $match_end + (length( $substitution ) - length( $substring )); } - # XXX why do we do this? + # XXX Why do we do this? $line .= "\033[0m\033[K" if $matched; } } @@ -691,7 +654,7 @@ sub print_line_with_options { } sub iterate { - my ( $resource, $opt, $cb ) = @_; + my ( $resource, $cb ) = @_; $is_iterating = 1; @@ -719,29 +682,27 @@ sub iterate { } } - $is_iterating = 0; # XXX this won't happen on an exception - # then again, do we care? ack doesn't really - # handle exceptions anyway. + $is_iterating = 0; return; } sub print_line_with_context { - my ( $opt, $filename, $matching_line, $line_no ) = @_; + my ( $filename, $matching_line, $line_no ) = @_; my $ors = $opt_print0 ? "\0" : "\n"; my $is_tracking_context = $opt_after_context || $opt_before_context; $matching_line =~ s/[\r\n]+$//g; - # check if we need to print context lines first - if( $is_tracking_context ) { + # 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); } - # We want at most $n_before_ctx_lines of context + # We want at most $n_before_ctx_lines of context. if ( $before_unprinted > $n_before_ctx_lines ) { $before_unprinted = $n_before_ctx_lines; } @@ -751,17 +712,17 @@ sub print_line_with_context { chomp $line; - # Disable $opt->{column} since there are no matches in the context lines + # Disable $opt->{column} since there are no matches in the context lines. local $opt_column = 0; - print_line_with_options($opt, $filename, $line, $line_no-$before_unprinted, '-'); + print_line_with_options( $filename, $line, $line_no-$before_unprinted, '-' ); $before_unprinted--; } } - print_line_with_options($opt, $filename, $matching_line, $line_no, ':'); + print_line_with_options( $filename, $matching_line, $line_no, ':' ); - # We want to get the next $n_after_ctx_lines printed + # We want to get the next $n_after_ctx_lines printed. $after_context_pending = $n_after_ctx_lines; $is_first_match = 0; @@ -769,18 +730,18 @@ sub print_line_with_context { return; } -# print the line only if it's part of a context we need to display +# Print the line only if it's part of a context we need to display. sub print_line_if_context { - my ( $opt, $filename, $line, $line_no, $separator ) = @_; + my ( $filename, $line, $line_no, $separator ) = @_; if ( $after_context_pending ) { - # Disable $opt->{column} since there are no matches in the context lines + # Disable $opt_column since there are no matches in the context lines. local $opt_column = 0; - print_line_with_options( $opt, $filename, $line, $line_no, $separator ); + print_line_with_options( $filename, $line, $line_no, $separator ); --$after_context_pending; } elsif ( $n_before_ctx_lines ) { - # save line for "before" context + # Save line for "before" context. $before_context_buf[$before_context_pos] = $_; $before_context_pos = ($before_context_pos+1) % $n_before_ctx_lines; } @@ -792,16 +753,18 @@ sub print_line_if_context { # does_match() MUST have an $opt_regex set. -=for Developers +=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 ( $opt, $line ) = @_; + my ( $line ) = @_; $match_column_number = undef; @@ -826,7 +789,7 @@ sub get_match_column { } sub resource_has_match { - my ( $resource, $opt ) = @_; + my ( $resource ) = @_; my $has_match = 0; my $fh = $resource->open(); @@ -836,23 +799,12 @@ sub resource_has_match { } } else { - if ( $opt_v ) { - while ( <$fh> ) { - if (!/$opt_regex/o) { - $has_match = 1; - last; - } + while ( <$fh> ) { + if (/$opt_regex/o xor $opt_v) { + $has_match = 1; + last; } } - else { - # XXX read in chunks - # XXX only do this for certain file sizes? - my $content = do { - local $/; - <$fh>; - }; - $has_match = $content =~ /$opt_regex/o; - } close $fh; } @@ -860,7 +812,7 @@ sub resource_has_match { } sub count_matches_in_resource { - my ( $resource, $opt ) = @_; + my ( $resource ) = @_; my $nmatches = 0; my $fh = $resource->open(); @@ -870,17 +822,8 @@ sub count_matches_in_resource { } } else { - if ( $opt_v ) { - while ( <$fh> ) { - ++$nmatches if (!/$opt_regex/o); - } - } - else { - my $content = do { - local $/; - <$fh>; - }; - $nmatches =()= ($content =~ /$opt_regex/og); + while ( <$fh> ) { + ++$nmatches if (/$opt_regex/o xor $opt_v); } close $fh; } @@ -996,17 +939,15 @@ sub main { my $nmatches = 0; my $total_count = 0; - setup_line_context( $opt ); + set_up_line_context(); RESOURCES: while ( my $resource = $resources->next ) { if ($is_tracking_context) { - setup_line_context_for_file($opt); + set_up_line_context_for_file(); } - # XXX Combine the -f and -g functions if ( $opt_f ) { - # XXX printing should probably happen inside of App::Ack if ( $opt->{show_types} ) { show_types( $resource, $ors ); } @@ -1023,7 +964,7 @@ RESOURCES: else { local $opt_show_filename = 0; # XXX Why is this local? - print_line_with_options($opt, '', $resource->name, 0, $ors); + print_line_with_options( '', $resource->name, 0, $ors ); } ++$nmatches; last RESOURCES if defined($opt_m) && $nmatches >= $opt_m; @@ -1044,23 +985,23 @@ RESOURCES: local $opt_color = 0; - iterate($resource, $opt, sub { + iterate( $resource, sub { chomp; if ( $line_numbers{$.} ) { - print_line_with_context($opt, $filename, $_, $.); + print_line_with_context( $filename, $_, $. ); } elsif ( $opt_passthru ) { - print_line_with_options($opt, $filename, $_, $., ':'); + print_line_with_options( $filename, $_, $., ':' ); } elsif ( $is_tracking_context ) { - print_line_if_context($opt, $filename, $_, $., '-'); + print_line_if_context( $filename, $_, $., '-' ); } return 1; }); } elsif ( $opt_count ) { - my $matches_for_this_file = count_matches_in_resource( $resource, $opt ); + my $matches_for_this_file = count_matches_in_resource( $resource ); if ( not $opt_show_filename ) { $total_count += $matches_for_this_file; @@ -1077,7 +1018,7 @@ RESOURCES: } } elsif ( $opt_l || $opt_L ) { - my $is_match = resource_has_match( $resource, $opt ); + my $is_match = resource_has_match( $resource ); if ( $opt_L ? !$is_match : $is_match ) { App::Ack::print( $resource->name, $ors ); @@ -1103,7 +1044,9 @@ RESOURCES: App::Ack::exit_from_ack( $nmatches ); } +=pod +=encoding UTF-8 =head1 NAME @@ -1116,11 +1059,11 @@ ack - grep-like text finder =head1 DESCRIPTION -Ack is designed as an alternative to F for programmers. +ack is designed as an alternative to F for programmers. -Ack searches the named input FILEs (or standard input if no files -are named, or the file name - is given) for lines containing a match -to the given PATTERN. By default, ack prints the matching lines. +ack 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 @@ -1217,6 +1160,8 @@ when used interactively. =item B<-C [I]>, B<--context[=I]> Print I lines (default 2) of context around matching lines. +You can specify zero lines of context to override another context +specified in an ackrc. =item B<-c>, B<--count> @@ -1301,9 +1246,26 @@ This is off by default. =item B<-g I> -Print files where the relative path + filename matches I. -This option can be combined with B<--color> to make it easier to spot -the match. +Print searchable files where the relative path + filename matches +I. + +Note that + + ack -g foo + +is exactly the same as + + ack -f | ack foo + +This means that just as ack will not search, for example, F<.jpg> +files, C<-g> will not list F<.jpg> files either. ack is not intended +to be a general-purpose file finder. + +Note also that if you have C<-i> in your .ackrc that the filenames +to be matched will be case-insensitive as well. + +This option can be combined with B<--color> to make it easier to +spot the match. =item B<--[no]group> @@ -1524,6 +1486,21 @@ Display version and copyright information. =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 will not +match the strings C or C. 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. @@ -1565,7 +1542,7 @@ might look like this: # Always sort the files --sort-files - # Always color, even if piping to a another program + # Always color, even if piping to another program --color # Use "less -r" as my pager @@ -1587,6 +1564,12 @@ 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, +or the file type as an option itself. For example, if you create +a filetype of "cobol", you can specify I<--type=cobol> or simply +I<--cobol>. File types must be at least two characters long. This +is why the C language is I<--cc> and the R language is I<--rr>. + I searches for foo in all perl files. I tells you, that perl files are files ending in .pl, .pm, .pod or .t. So what if you would like to include .xs @@ -1618,7 +1601,6 @@ The following does B work in the F<.ackrc> file: --type-set eiffel:ext:e,eiffel - In order to see all currently defined types, use I<--help-types>, e.g. I @@ -1765,7 +1747,7 @@ these values. =head1 ACK & OTHER TOOLS -=head2 Vim integration +=head2 Simple vim integration F integrates easily with the Vim text editor. Set this in your F<.vimrc> to use F instead of F: @@ -1778,27 +1760,10 @@ with F and easily step through the results in Vim: :grep Dumper perllib -Miles Sterrett has written a Vim plugin for F which allows you to use -C<:Ack> instead of C<:grep>, as well as several other advanced features. - -L - -=head2 Emacs integration +=head2 Editor integration -Phil Jackson put together an F extension that "provides a -simple compilation mode ... has the ability to guess what files you -want to search for based on the major-mode." - -L - -=head2 TextMate integration - -Pedro Melo is a TextMate user who writes "I spend my day mostly -inside TextMate, and the built-in find-in-project sucks with large -projects. So I hacked a TextMate command that was using find + -grep to use ack. The result is the Search in Project with ack, and -you can find it here: -L" +Many users have integrated ack into their preferred text editors. +For details and links, see L. =head2 Shell and Return Code @@ -1920,22 +1885,28 @@ This shows how to pick out particular parts of a match using ( ) within regular input file contains "=head1 NAME" output "1 : NAME" -=head2 Share your knowledge +=head1 COMMUNITY -Join the ack-users mailing list. Send me your tips and I may add -them here. +There are ack mailing lists and a Slack channel for ack. See +L for details. =head1 FAQ =head2 Why isn't ack finding a match in (some file)? -Probably because it's of a type that ack doesn't recognize. ack's -searching behavior is driven by filetype. B +First, take a look and see if ack is even looking at the file. ack is +intelligent in what files it will search and which ones it won't, but +sometimes that can be surprising. -Use the C<-f> switch to see a list of files that ack will search -for you. You can use the C<--show-types> switch to show which type -ack thinks each file is. +Use the C<-f> switch, with no regex, to see a list of files that ack +will search for you. If your file doesn't show up in the list of files +that C 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 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 did search & replace? @@ -2003,9 +1974,15 @@ expression metacharacter!) =head2 Why does C<"ack '.{40000,}'"> fail? Isn't that a valid regex? -The Perl language limits the repetition quanitifier to 32K. You +The Perl language limits the repetition quantifier to 32K. You can search for C<.{32767}> but not C<.{32768}>. +=head2 Ack does "X" and shouldn't, should it? + +We try to remain as close to grep's behavior as possible, so when in doubt, +see what grep does! If there's a mismatch in functionality there, please +bring it up on the ack-users mailing list. + =head1 ACKRC LOCATION SEMANTICS Ack can load its configuration from many sources. The following list @@ -2220,7 +2197,7 @@ Andy Lester, C<< >> =head1 BUGS Please report any bugs or feature requests to the issues list at -Github: L +Github: L =head1 ENHANCEMENTS @@ -2230,7 +2207,7 @@ 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 in the ack -issues list at Github: L +issues list at Github: L Patches are always welcome, but patches with tests get the most attention. @@ -2243,7 +2220,7 @@ Support for and information about F can be found at: =item * The ack homepage -L +L =item * The ack-users mailing list @@ -2251,7 +2228,7 @@ L =item * The ack issues list at Github -L +L =item * AnnoCPAN: Annotated CPAN documentation @@ -2265,9 +2242,13 @@ L L +=item * MetaCPAN + +L + =item * Git source repository -L +L =back @@ -2276,6 +2257,21 @@ L How appropriate to have Inowledgements! Thanks to everyone who has contributed to ack in any way, including +Michele Campeotto, +H.Merijn Brand, +Duke Leto, +Gerhard Poul, +Ethan Mallove, +Marek Kubica, +Ray Donnelly, +Nikolaj Schumacher, +Ed Avis, +Nick Morrott, +Austin Chamberlin, +Varadinsky, +SEbastien FeugEre, +Jakub Wilk, +Pete Houston, Stephen Thirlwall, Jonah Bishop, Chris Rebert, @@ -2377,7 +2373,7 @@ Rob Hoelz. =head1 COPYRIGHT & LICENSE -Copyright 2005-2015 Andy Lester. +Copyright 2005-2017 Andy Lester. This program is free software; you can redistribute it and/or modify it under the terms of the Artistic License v2.0. @@ -2395,8 +2391,8 @@ use strict; our $VERSION; our $COPYRIGHT; BEGIN { - $VERSION = '2.15_01'; - $COPYRIGHT = 'Copyright 2005-2015 Andy Lester.'; + $VERSION = '2.22'; + $COPYRIGHT = 'Copyright 2005-2017 Andy Lester.'; } our $fh; @@ -2425,7 +2421,7 @@ BEGIN { $output_to_pipe = not -t *STDOUT; $is_filter_mode = -p STDIN; - $is_cygwin = ($^O eq 'cygwin'); + $is_cygwin = ($^O eq 'cygwin' || $^O eq 'msys'); $is_windows = ($^O eq 'MSWin32'); $dir_sep_chars = $is_windows ? quotemeta( '\\/' ) : quotemeta( File::Spec->catfile( '', '' ) ); } @@ -2461,19 +2457,15 @@ sub filetypes_supported { return keys %mappings; } -sub _get_thpppt { +sub thpppt { my $y = q{_ /|,\\'!.x',=(www)=, U }; $y =~ tr/,x!w/\nOo_/; - return $y; -} -sub _thpppt { - my $y = _get_thpppt(); App::Ack::print( "$y ack $_[0]!\n" ); exit 0; } -sub _bar { +sub ackbar { my $x; $x = <<'_BAR'; 6?!I'7!I"?%+! @@ -2516,10 +2508,10 @@ sub _bar { 77I!+!7!?!7!I"71+!7, _BAR - return App::Ack::__pic($x); + return _pic_decode($x); } -sub _cathy { +sub cathy { my $x = <<'CATHY'; 0+!--+! 0|! "C!H!O!C!O!L!A!T!E!!! !|! @@ -2572,10 +2564,10 @@ sub _cathy { 0?!$! &N! )." .,! %."M! ":!M!.! 0 0N!:! %?!O! #.! ..! &,! &.!D!,! "N!I! 0 CATHY - return App::Ack::__pic($x); + return _pic_decode($x); } -sub __pic { +sub _pic_decode { my($compressed) = @_; $compressed =~ s/(.)(.)/$1x(ord($2)-32)/eg; App::Ack::print( $compressed ); @@ -2725,7 +2717,7 @@ Miscellaneous: Exit status is 0 if match, 1 if no match. -ack's home page is at http://beyondgrep.com/ +ack's home page is at https://beyondgrep.com/ The full ack manual is available by running "ack --man". @@ -2816,49 +2808,9 @@ sub get_copyright { } -# print*() subs added in order to make it easy for a third party -# module (such as App::Wack) to redefine the display methods -# and show the results in a different way. sub print { print {$fh} @_; return; } -sub print_first_filename { App::Ack::print( $_[0], "\n" ); return; } sub print_blank_line { App::Ack::print( "\n" ); return; } -sub print_separator { App::Ack::print( "--\n" ); return; } sub print_filename { App::Ack::print( $_[0], $_[1] ); return; } -sub print_line_no { App::Ack::print( $_[0], $_[1] ); return; } -sub print_column_no { App::Ack::print( $_[0], $_[1] ); return; } -sub print_count { - my $filename = shift; - my $nmatches = shift; - my $ors = shift; - my $count = shift; - my $show_filename = shift; - - if ($show_filename) { - App::Ack::print( $filename ); - App::Ack::print( ':', $nmatches ) if $count; - } - else { - App::Ack::print( $nmatches ) if $count; - } - App::Ack::print( $ors ); - - return; -} - -sub print_count0 { - my $filename = shift; - my $ors = shift; - my $show_filename = shift; - - if ($show_filename) { - App::Ack::print( $filename, ':0', $ors ); - } - else { - App::Ack::print( '0', $ors ); - } - - return; -} sub set_up_pager { my $command = shift; @@ -2898,59 +2850,159 @@ use strict; use overload '""' => 'name'; -sub FAIL { - require Carp; - Carp::confess( 'Must be overloaded' ); -} - sub new { - return FAIL(); + my $class = shift; + my $filename = shift; + + my $self = bless { + filename => $filename, + fh => undef, + opened => 0, + }, $class; + + if ( $self->{filename} eq '-' ) { + $self->{fh} = *STDIN; + $self->{opened} = 1; + } + + return $self; } + sub name { - return FAIL(); + return $_[0]->{filename}; } + sub basename { - return FAIL(); -} + my ( $self ) = @_; + # XXX Definedness? Pre-populate the slot with an undef? + unless ( exists $self->{basename} ) { + $self->{basename} = (File::Spec->splitpath($self->name))[2]; + } -sub is_binary { - return FAIL(); + return $self->{basename}; } + sub open { - return FAIL(); + my ( $self ) = @_; + + if ( !$self->{opened} ) { + if ( open $self->{fh}, '<', $self->{filename} ) { + $self->{opened} = 1; + } + else { + $self->{fh} = undef; + } + } + + return $self->{fh}; } + sub needs_line_scan { - return FAIL(); + my $self = shift; + my $opt = shift; + + return 1 if $opt->{v}; + + my $size = -s $self->{fh}; + if ( $size == 0 ) { + return 0; + } + elsif ( $size > 100_000 ) { + return 1; + } + + 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 ); + + return $buffer =~ /$opt->{regex}/m; } + sub reset { - return FAIL(); + my $self = shift; + + # 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}: $!" ); + } + + return; } + sub close { - return FAIL(); + my $self = shift; + + # Return if we haven't opened the file yet. + if ( !defined($self->{fh}) ) { + return; + } + + if ( !close($self->{fh}) && $App::Ack::report_bad_filenames ) { + App::Ack::warn( $self->name() . ": $!" ); + } + + $self->{opened} = 0; + + return; } + sub clone { - return FAIL(); + my ( $self ) = @_; + + return __PACKAGE__->new($self->name); } + sub firstliney { - return FAIL(); + my ( $self ) = @_; + + if ( !exists $self->{firstliney} ) { + my $fh = $self->open(); + if ( !$fh ) { + if ( $App::Ack::report_bad_filenames ) { + App::Ack::warn( $self->name . ': ' . $! ); + } + return ''; + } + + 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; + + $self->close; + } + + return $self->{firstliney}; } 1; @@ -2958,6 +3010,8 @@ package App::Ack::Resources; +use Errno qw(EACCES); + use warnings; use strict; @@ -2967,9 +3021,7 @@ sub _generate_error_handler { if ( $opt->{dont_report_bad_filenames} ) { return sub { my $msg = shift; - # XXX restricting to specific error messages for now; I would - # prefer a different way of doing this - if ( $msg =~ /Permission denied/ ) { + if ( $! == EACCES ) { return; } App::Ack::warn( $msg ); @@ -3056,160 +3108,7 @@ sub next { my $file = $self->{iter}->() or return; - return App::Ack::Resource::Basic->new( $file ); -} - -1; -package App::Ack::Resource::Basic; - - -use warnings; -use strict; - -use Fcntl (); -use File::Spec (); - -BEGIN { - our @ISA = 'App::Ack::Resource'; -} - - - -sub new { - my $class = shift; - my $filename = shift; - - my $self = bless { - filename => $filename, - fh => undef, - opened => 0, - }, $class; - - if ( $self->{filename} eq '-' ) { - $self->{fh} = *STDIN; - $self->{opened} = 1; - } - - return $self; -} - - -sub name { - return $_[0]->{filename}; -} - -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]; - } - - return $self->{basename}; -} - - -sub needs_line_scan { - my $self = shift; - my $opt = shift; - - return 1 if $opt->{v}; - - my $size = -s $self->{fh}; - if ( $size == 0 ) { - return 0; - } - elsif ( $size > 100_000 ) { - return 1; - } - - 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 ); - - my $regex = $opt->{regex}; - return $buffer =~ /$regex/m; -} - - -sub reset { - my $self = shift; - - # 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}: $!" ); - } - - return; -} - - -sub close { - my $self = shift; - - # return if we haven't opened the file yet - if ( !defined($self->{fh}) ) { - return; - } - - if ( !close($self->{fh}) && $App::Ack::report_bad_filenames ) { - App::Ack::warn( $self->name() . ": $!" ); - } - - $self->{opened} = 0; - - return; -} - - -sub clone { - my ( $self ) = @_; - - return __PACKAGE__->new($self->name); -} - -sub firstliney { - my ( $self ) = @_; - - my $fh = $self->open(); - - if ( !exists $self->{firstliney} ) { - my $buffer = ''; - my $rc = sysread( $fh, $buffer, 250 ); - unless($rc) { # XXX handle this better? - $buffer = ''; - } - $buffer =~ s/[\r\n].*//s; - $self->{firstliney} = $buffer; - $self->reset; - } - - $self->close; - - return $self->{firstliney}; -} - -sub open { - my ( $self ) = @_; - - return $self->{fh} if $self->{opened}; - - if ( ! open $self->{fh}, '<', $self->{filename} ) { - return; - } - - $self->{opened} = 1; - - return $self->{fh}; + return App::Ack::Resource->new( $file ); } 1; @@ -3220,6 +3119,7 @@ use strict; + sub options { return split( /\n/, _options_block() ); } @@ -3267,6 +3167,8 @@ sub _options_block { # Git # http://git-scm.com/ --ignore-directory=is:.git +# When using submodules, .git is a file. +--ignore-file=is:.git # Mercurial # http://mercurial.selenic.com/ @@ -3419,7 +3321,7 @@ sub _options_block { # Clojure # http://clojure.org/ ---type-add=clojure:ext:clj +--type-add=clojure:ext:clj,cljs,edn,cljc # C # .xs are Perl C files @@ -3477,12 +3379,16 @@ sub _options_block { # http://groovy.codehaus.org/ --type-add=groovy:ext:groovy,gtmpl,gpp,grunit,gradle +# GSP +# http://groovy.codehaus.org/GSP +--type-add=gsp:ext:gsp + # Haskell # http://www.haskell.org/ --type-add=haskell:ext:hs,lhs # HTML ---type-add=html:ext:htm,html +--type-add=html:ext:htm,html,xhtml # Jade # http://jade-lang.com/ @@ -3497,12 +3403,16 @@ sub _options_block { # JSP # http://www.oracle.com/technetwork/java/javaee/jsp/index.html ---type-add=jsp:ext:jsp,jspx,jhtm,jhtml +--type-add=jsp:ext:jsp,jspx,jspf,jhtm,jhtml # JSON # http://www.json.org/ --type-add=json:ext:json +# Kotlin +# https://kotlinlang.org/ +--type-add=kotlin:ext:kt,kts + # Less # http://www.lesscss.org/ --type-add=less:ext:less @@ -3524,7 +3434,7 @@ sub _options_block { # OCaml # http://caml.inria.fr/ ---type-add=ocaml:ext:ml,mli +--type-add=ocaml:ext:ml,mli,mll,mly # Matlab # http://en.wikipedia.org/wiki/MATLAB @@ -3598,6 +3508,11 @@ sub _options_block { # http://learnboost.github.io/stylus/ --type-add=stylus:ext:styl +# Swift +# https://developer.apple.com/swift/ +--type-add=swift:ext:swift +--type-add=swift:firstlinematch:/^#!.*\bswift/ + # Tcl # http://www.tcl.tk/ --type-add=tcl:ext:tcl,itcl,itk @@ -3626,7 +3541,7 @@ sub _options_block { # XML # http://www.w3.org/TR/REC-xml/ ---type-add=xml:ext:xml,dtd,xsl,xslt,ent +--type-add=xml:ext:xml,dtd,xsd,xsl,xslt,ent,wsdl --type-add=xml:firstlinematch:/<[?]xml/ # YAML @@ -3712,8 +3627,10 @@ sub find_config_files { push @config_files, map { +{ path => $_ } } _check_for_ackrc($ENV{'HOME'}); } - # XXX This should go through some untainted cwd-fetching function, and not get untainted inline like this. my $cwd = Cwd::getcwd(); + return () unless defined $cwd; + + # XXX This should go through some untainted cwd-fetching function, and not get untainted brute-force like this. $cwd =~ /(.+)/; $cwd = $1; my @dirs = File::Spec->splitdir( $cwd ); @@ -3762,7 +3679,7 @@ use strict; use warnings; use Carp 1.04 (); -use Getopt::Long 2.35 (); +use Getopt::Long 2.38 (); use Text::ParseWords 3.1 (); @@ -3887,6 +3804,8 @@ sub uninvert_filter { $i--; } } + + return; } @@ -3987,7 +3906,7 @@ sub removed_option { $explanation ||= ''; return sub { - warn "Option '$option' is not valid in ack 2\n$explanation"; + warn "Option '$option' is not valid in ack 2.\n$explanation"; exit 1; }; } @@ -3997,19 +3916,28 @@ sub get_arg_spec { my ( $opt, $extra_specs ) = @_; my $dash_a_explanation = <<'EOT'; -This is because we now have -k/--known-types which makes it only select files -of known types, rather than any text file (which is the behavior of ack 1.x). -You may have options in a .ackrc, or in the ACKRC_OPTIONS environment variable. -Try using the --dump flag. +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 _context_value { + my $val = shift; + + # Contexts default to 2. + return (!defined($val) || ($val < 0)) ? 2 : $val; + } + return { 1 => sub { $opt->{1} = $opt->{m} = 1 }, - 'A|after-context=i' => \$opt->{after_context}, - 'B|before-context=i' - => \$opt->{before_context}, - 'C|context:i' => sub { shift; my $val = shift; $opt->{before_context} = $opt->{after_context} = ($val || 2) }, + 'A|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), 'break!' => \$opt->{break}, @@ -4157,7 +4085,8 @@ sub process_other { die "Options --output, --pager and --match are forbidden in project .ackrc files.\n"; }; - $args_for_source = { %$args_for_source, + $args_for_source = { + %{$args_for_source}, 'output=s' => $illegal, 'pager:s' => $illegal, 'match=s' => $illegal, @@ -4416,6 +4345,8 @@ sub check_for_mutually_exclusive_options { } } } + + return; } @@ -4534,6 +4465,7 @@ sub retrieve_arg_sources { 1; # End of App::Ack::ConfigLoader package App::Ack::Filter; + use strict; use warnings; @@ -4589,6 +4521,7 @@ sub inspect { 1; package App::Ack::Filter::Extension; + use strict; use warnings; BEGIN { @@ -4644,6 +4577,8 @@ BEGIN { 1; package App::Ack::Filter::FirstLineMatch; + + use strict; use warnings; BEGIN { @@ -4698,6 +4633,7 @@ BEGIN { 1; package App::Ack::Filter::Is; + use strict; use warnings; BEGIN { @@ -4759,6 +4695,7 @@ BEGIN { use File::Spec 3.00; + sub new { my ( $class, $re ) = @_; @@ -4808,6 +4745,7 @@ BEGIN { 1; package App::Ack::Filter::Default; + use strict; use warnings; BEGIN { @@ -4829,6 +4767,8 @@ sub filter { 1; package App::Ack::Filter::Inverse; + + use strict; use warnings; BEGIN { @@ -4871,6 +4811,7 @@ sub inspect { 1; package App::Ack::Filter::Collection; + use strict; use warnings; BEGIN { @@ -4935,6 +4876,7 @@ sub to_string { 1; package App::Ack::Filter::IsGroup; + use strict; use warnings; BEGIN { @@ -4983,6 +4925,7 @@ sub to_string { 1; package App::Ack::Filter::ExtensionGroup; + use strict; use warnings; BEGIN { @@ -5032,6 +4975,7 @@ sub to_string { 1; package App::Ack::Filter::MatchGroup; + use strict; use warnings; BEGIN { @@ -5068,15 +5012,20 @@ sub filter { sub inspect { my ( $self ) = @_; + + # XXX Needs an explicit return. } sub to_string { my ( $self ) = @_; + + # XXX Needs an explicit return. } 1; package App::Ack::Filter::IsPath; + use strict; use warnings; BEGIN { @@ -5122,6 +5071,9 @@ sub to_string { 1; package App::Ack::Filter::IsPathGroup; + + + use strict; use warnings; BEGIN { @@ -5171,7 +5123,7 @@ use strict; use warnings; -our $VERSION = '1.12'; +our $VERSION = '1.16'; @@ -5187,7 +5139,7 @@ BEGIN { %files_defaults = ( file_filter => undef, descend_filter => undef, - error_handler => sub { CORE::die @_ }, + error_handler => sub { CORE::die $_[0] }, warning_handler => sub { CORE::warn @_ }, sort_files => undef, follow_symlinks => 1, @@ -5201,12 +5153,12 @@ sub files { die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__); 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 || $fullpath =~ m{^/dev/fd} ) { + if ( -f $fullpath || -p _ || $fullpath =~ m{^/dev/fd} ) { if ( $filter ) { local $_ = $file; local $File::Next::dir = $dirname; @@ -5215,7 +5167,7 @@ sub files { } return wantarray ? ($dirname,$file,$fullpath) : $fullpath; } - elsif ( -d _ ) { + if ( -d _ ) { unshift( @queue, _candidate_files( $parms, $fullpath ) ); } } # while @@ -5234,7 +5186,7 @@ sub from_file { my ($parms,@queue) = _setup( \%files_defaults, @_ ); my $err = $parms->{error_handler}; - my $warn = $parms->{error_handler}; + my $warn = $parms->{warning_handler}; my $filename = $queue[1]; @@ -5249,13 +5201,13 @@ sub from_file { } else { if ( !open( $fh, '<', $filename ) ) { - $err->( "Unable to open $filename: $!" ); + $err->( "Unable to open $filename: $!", $! + 0 ); return undef; } } - my $filter = $parms->{file_filter}; return sub { + my $filter = $parms->{file_filter}; local $/ = $parms->{nul_separated} ? "\x00" : $/; while ( my $fullpath = <$fh> ) { chomp $fullpath; @@ -5348,7 +5300,7 @@ sub _candidate_files { my $dh; if ( !opendir $dh, $dirname ) { - $parms->{error_handler}->( "$dirname: $!" ); + $parms->{error_handler}->( "$dirname: $!", $! + 0 ); return; } @@ -5360,13 +5312,13 @@ sub _candidate_files { for my $file ( grep { !exists $skip_dirs{$_} } readdir $dh ) { my $has_stat; - # Only do directory checking if we have a descend_filter my $fullpath = File::Spec->catdir( $dirname, $file ); if ( !$follow_symlinks ) { next if -l $fullpath; $has_stat = 1; } + # Only do directory checking if we have a descend_filter if ( $descend_filter ) { if ( $has_stat ? (-d _) : (-d $fullpath) ) { local $File::Next::dir = $fullpath; -- 2.47.1