use warnings;
use strict;
-our $VERSION = '1.92';
+our $VERSION = '1.96';
# Check http://betterthangrep.com/ for updates
# These are all our globals.
/^--th[pt]+t+$/ && App::Ack::_thpppt($_);
# See if we want to ignore the environment. (Don't tell Al Gore.)
- if ( $_ eq '--noenv' ) {
- my @keys = ( 'ACKRC', grep { /^ACK_/ } keys %ENV );
- delete @ENV{@keys};
- $env_is_usable = 0;
+ if ( /^--(no)?env$/ ) {
+ $env_is_usable = defined $1 ? 0 : 1;
}
}
- unshift( @ARGV, App::Ack::read_ackrc() ) if $env_is_usable;
+ if ( $env_is_usable ) {
+ unshift( @ARGV, App::Ack::read_ackrc() );
+ }
+ else {
+ my @keys = ( 'ACKRC', grep { /^ACK_/ } keys %ENV );
+ delete @ENV{@keys};
+ }
App::Ack::load_colors();
if ( exists $ENV{ACK_SWITCHES} ) {
my $s = $nargs == 1 ? '' : 's';
App::Ack::warn( "Ignoring $nargs argument$s on the command-line while acting as a filter." );
}
+
my $res = App::Ack::Resource::Basic->new( '-' );
- App::Ack::search_resource( $res, $opt );
+ my $nmatches;
+ if ( $opt->{count} ) {
+ $nmatches = App::Ack::search_and_list( $res, $opt );
+ }
+ else {
+ # normal searching
+ $nmatches = App::Ack::search_resource( $res, $opt );
+ }
$res->close();
- exit 0;
+ App::Ack::exit_from_ack( $nmatches );
}
my $file_matching = $opt->{f} || $opt->{lines};
- if ( !$file_matching ) {
- @ARGV or App::Ack::die( 'No regular expression found.' );
+ if ( $file_matching ) {
+ App::Ack::die( "Can't specify both a regex ($opt->{regex}) and use one of --line, -f or -g." ) if $opt->{regex};
+ }
+ else {
$opt->{regex} = App::Ack::build_regex( defined $opt->{regex} ? $opt->{regex} : shift @ARGV, $opt );
}
$nmatches = App::Ack::print_matches( $iter, $opt );
}
close $App::Ack::fh;
- exit ($nmatches ? 0 : 1);
+ App::Ack::exit_from_ack( $nmatches );
}
=head1 NAME
number of lines for each file that has lines matching. Without
B<-l>, some line counts may be zeroes.
+If combined with B<-h> (B<--no-filename>) ack outputs only one total count.
+
=item B<--color>, B<--nocolor>
B<--color> highlights the matching text. B<--nocolor> supresses
Sets the color to be used for matches.
+=item B<--color-lineno=I<color>>
+
+Sets the color to be used for line numbers.
+
=item B<--column>
Show the column number of the first match. This is helpful for editors
This applies only to the PATTERN, not to the regexes given for the B<-g>
and B<-G> options.
-=item B<--[no]ignore-dir=DIRNAME>
+=item B<--[no]ignore-dir=I<DIRNAME>>
Ignore directory (as CVS, .svn, etc are ignored). May be used multiple times
to ignore multiple directories. For example, mason users may wish to include
directories which would normally be ignored (perhaps to research the contents
of F<.svn/props> directories).
+The I<DIRNAME> must always be a simple directory name. Nested directories like
+F<foo/bar> are NOT supported. You would need to specify B<--ignore-dir=foo> and
+then no files from any foo directory are taken into account by ack unless given
+explicitly on the command line.
+
=item B<--line=I<NUM>>
Only print line I<NUM> of each file. Multiple lines can be given with multiple
Print this manual page.
-=item B<-n>
+=item B<-n>, B<--no-recurse>
No descending into subdirectories.
This applies only to the PATTERN, not to the regexes given for the B<-g>
and B<-G> options.
+=item B<-r>, B<-R>, B<--recurse>
+
+Recurse into sub-directories. This is the default and just here for
+compatibility with grep. You can also use it for turning B<--no-recurse> off.
+
=item B<--smart-case>, B<--no-smart-case>
Ignore case in the search strings if PATTERN contains no uppercase
Sorts the found files lexically. Use this if you want your file
listings to be deterministic between runs of I<ack>.
+=item B<--show-types>
+
+Outputs the filetypes that ack associates with each file.
+
+Works with B<-f> and B<-g> options.
+
=item B<--thpppt>
Display the all-important Bill The Cat logo. Note that the exact
See B<ACK_COLOR_FILENAME> for the color specifications.
+=item ACK_COLOR_LINENO
+
+Specifies the color of the line number when printed in B<--color>
+mode. By default, it's "bold yellow".
+
+This option can also be set with B<--color-lineno>.
+
+See B<ACK_COLOR_FILENAME> for the color specifications.
+
=item ACK_PAGER
Specifies a pager program, such as C<more>, C<less> or C<most>, to which
=head2 Why isn't ack finding a match in (some file)?
-Probably because it's of a type that ack doesn't recognize.
+Probably because it's of a type that ack doesn't recognize. ack's
+searching behavior is driven by filetype. B<If ack doesn't know
+what kind of file it is, ack ignores the file.>
-ack's searching behavior is driven by filetype. If ack doesn't
-know what kind of file it is, ack ignores it.
+Use the C<-f> switch to see a list of files that ack will search
+for you.
If you want ack to search files that it doesn't recognize, use the
C<-a> switch.
You can certainly use ack to select your files to update. For
example, to change all "foo" to "bar" in all PHP files, you can do
-this form the Unix shell:
+this from the Unix shell:
$ perl -i -p -e's/foo/bar/g' $(ack -f --php)
"ack-grep" when creating packages because there's already a package
out there called "ack" that has nothing to do with this ack.
-I suggest you rename your ack-grep install to "ack" because one of
-the crucial benefits of ack is having a name that's so short and
-simple to type.
+I suggest you make a symlink named F<ack> that points to F<ack-grep>
+because one of the crucial benefits of ack is having a name that's
+so short and simple to type.
+
+To do that, run this with F<sudo> or as root:
+
+ ln -s /usr/bin/ack-grep /usr/bin/ack
+
+=head2 What does F<ack> mean?
+
+Nothing. I wanted a name that was easy to type and that you could
+pronounce as a single syllable.
+
+=head2 Can I do multi-line regexes?
+
+No, ack does not support regexes that match multiple lines. Doing
+so would require reading in the entire file at a time.
+
+If you want to see lines near your match, use the C<--A>, C<--B>
+and C<--C> switches for displaying context.
=head1 AUTHOR
How appropriate to have I<ack>nowledgements!
Thanks to everyone who has contributed to ack in any way, including
+Matthew Wild,
+Scott Kyle,
+Nick Hooey,
+Bo Borgerson,
+Mark Szymanski,
+Marq Schneider,
Packy Anderson,
JR Boyens,
Dan Sully,
=head1 COPYRIGHT & LICENSE
-Copyright 2005-2009 Andy Lester.
+Copyright 2005-2011 Andy Lester.
This program is free software; you can redistribute it and/or modify
-it under the terms of either:
-
-=over 4
-
-=item * the GNU General Public License as published by the Free
-Software Foundation; either version 1, or (at your option) any later
-version, or
-
-=item * the Artistic License version 2.0.
-
-=back
+it under the terms of the Artistic License v2.0.
=cut
package File::Next;
our $VERSION;
our $COPYRIGHT;
BEGIN {
- $VERSION = '1.92';
- $COPYRIGHT = 'Copyright 2005-2009 Andy Lester.';
+ $VERSION = '1.96';
+ $COPYRIGHT = 'Copyright 2005-2011 Andy Lester.';
}
our $fh;
'.hg' => 'Mercurial',
'.pc' => 'quilt',
'.svn' => 'Subversion',
+ _MTN => 'Monotone',
blib => 'Perl module building',
CVS => 'CVS',
RCS => 'RCS',
binary => q{Binary files, as defined by Perl's -B op (default: off)},
cc => [qw( c h xs )],
cfmx => [qw( cfc cfm cfml )],
+ clojure => [qw( clj )],
cpp => [qw( cpp cc cxx m hpp hh h hxx )],
csharp => [qw( cs )],
css => [qw( css )],
+ delphi => [qw( pas int dfm nfm dof dpk dproj groupproj bdsgroup bdsproj )],
elisp => [qw( el )],
erlang => [qw( erl hrl )],
fortran => [qw( f f77 f90 f95 f03 for ftn fpp )],
+ go => [qw( go )],
+ groovy => [qw( groovy gtmpl gpp grunit )],
haskell => [qw( hs lhs )],
hh => [qw( h )],
html => [qw( htm html shtml xhtml )],
jsp => [qw( jsp jspx jhtm jhtml )],
lisp => [qw( lisp lsp )],
lua => [qw( lua )],
- make => q{Makefiles},
+ make => q{Makefiles (including *.mk and *.mak)},
mason => [qw( mas mhtml mpl mtxt )],
objc => [qw( m h )],
objcpp => [qw( mm h )],
ocaml => [qw( ml mli )],
parrot => [qw( pir pasm pmc ops pod pg tg )],
- perl => [qw( pl pm pod t )],
+ perl => [qw( pl pm pm6 pod t )],
php => [qw( php phpt php3 php4 php5 phtml)],
plone => [qw( pt cpt metadata cpy py )],
python => [qw( py )],
rake => q{Rakefiles},
- ruby => [qw( rb rhtml rjs rxml erb rake )],
+ ruby => [qw( rb rhtml rjs rxml erb rake spec )],
scala => [qw( scala )],
scheme => [qw( scm ss )],
shell => [qw( sh bash csh tcsh ksh zsh )],
text => q{Text files, as defined by Perl's -T op (default: off)},
tt => [qw( tt tt2 ttml )],
vb => [qw( bas cls frm ctl vb resx )],
+ verilog => [qw( v vh sv )],
+ vhdl => [qw( vhd vhdl )],
vim => [qw( vim )],
yaml => [qw( yaml yml )],
- xml => [qw( xml dtd xslt ent )],
+ xml => [qw( xml dtd xsl xslt ent )],
);
while ( my ($type,$exts) = each %mappings ) {
}
}
}
+ # add manually Makefile extensions
+ push @{$types{$_}}, 'make' for qw{ mk mak };
# These have to be checked before any filehandle diddling.
$output_to_pipe = not -t *STDOUT;
chomp @lines;
close $fh or App::Ack::die( "$filename: $!\n" );
+ # get rid of leading and trailing whitespaces
+ for ( @lines ) {
+ s/^\s+//;
+ s/\s+$//;
+ }
+
return @lines;
}
}
'color|colour!' => \$opt{color},
'color-match=s' => \$ENV{ACK_COLOR_MATCH},
'color-filename=s' => \$ENV{ACK_COLOR_FILENAME},
+ 'color-lineno=s' => \$ENV{ACK_COLOR_LINENO},
'column!' => \$opt{column},
count => \$opt{count},
'env!' => sub { }, # ignore this option, it is handled beforehand
'h|no-filename' => \$opt{h},
'H|with-filename' => \$opt{H},
'i|ignore-case' => \$opt{i},
+ 'invert-file-match' => \$opt{invert_file_match},
'lines=s' => sub { shift; my $val = shift; push @{$opt{lines}}, $val },
'l|files-with-matches' => \$opt{l},
'L|files-without-matches' => sub { $opt{l} = $opt{v} = 1 },
'passthru' => \$opt{passthru},
'print0' => \$opt{print0},
'Q|literal' => \$opt{Q},
- 'r|R|recurse' => sub {},
+ 'r|R|recurse' => sub { $opt{n} = 0 },
+ 'show-types' => \$opt{show_types},
'smart-case!' => \$opt{smart_case},
'sort-files' => \$opt{sort_files},
'u|unrestricted' => \$opt{u},
'ignore-dirs=s' => sub { shift; my $dir = remove_dir_sep( shift ); $ignore_dirs{$dir} = '--ignore-dirs' },
'noignore-dirs=s' => sub { shift; my $dir = remove_dir_sep( shift ); delete $ignore_dirs{$dir} },
- 'version' => sub { print_version_statement(); exit 1; },
+ 'version' => sub { print_version_statement(); exit; },
'help|?:s' => sub { shift; show_help(@_); exit; },
'help-types'=> sub { show_help_types(); exit; },
- 'man' => sub { require Pod::Usage; Pod::Usage::pod2usage({-verbose => 2}); exit; },
+ 'man' => sub {
+ require Pod::Usage;
+ Pod::Usage::pod2usage({
+ -verbose => 2,
+ -exitval => 0,
+ });
+ },
'type=s' => sub {
# Whatever --type=xxx they specify, set it manually in the hash
sub ignoredir_filter {
- return !exists $ignore_dirs{$_};
+ return !exists $ignore_dirs{$_} && !exists $ignore_dirs{$File::Next::dir};
}
return 'skipped' unless is_searchable( $basename );
my $lc_basename = lc $basename;
- return ('make',TEXT) if $lc_basename eq 'makefile';
+ return ('make',TEXT) if $lc_basename eq 'makefile' || $lc_basename eq 'gnumakefile';
return ('rake','ruby',TEXT) if $lc_basename eq 'rakefile';
# If there's an extension, look it up
close $fh;
if ( $header =~ /^#!/ ) {
- return ($1,TEXT) if $header =~ /\b(ruby|p(?:erl|hp|ython))\b/;
+ return ($1,TEXT) if $header =~ /\b(ruby|lua|p(?:erl|hp|ython))-?(\d[\d.]*)?\b/;
return ('shell',TEXT) if $header =~ /\b(?:ba|t?c|k|z)?sh\b/;
}
else {
return if $filename =~ m{^#.*#$}o;
return if $filename =~ m{^core\.\d+$}o;
return if $filename =~ m{[._].*\.swp$}o;
+ return if $filename =~ /[.-]min\.js$/;
return 1;
}
my $str = shift;
my $opt = shift;
+ defined $str or App::Ack::die( 'No regular expression found.' );
+
$str = quotemeta( $str ) if $opt->{Q};
if ( $opt->{w} ) {
$str = "\\b$str" if $str =~ /^\w/;
output is redirected, or on Windows)
--[no]colour Same as --[no]color
--color-filename=COLOR
- --color-match=COLOR Set the color for matches and filenames.
+ --color-match=COLOR
+ --color-lineno=COLOR Set the color for filenames, matches, and line numbers.
--flush Flush output immediately, even when ack is used
non-interactively (when output goes to a pipe or
file).
The PATTERN must not be specified.
-g REGEX Same as -f, but only print files matching REGEX.
--sort-files Sort the found files lexically.
+ --invert-file-match Print/search handle files that do not match -g/-G.
+ --show-types Show which types each file has.
File inclusion/exclusion:
-a, --all-types All file types searched;
$ignore_dirs
Files not checked for type:
- /~\$/ - Unix backup files
- /#.+#\$/ - Emacs swap files
+ /~\$/ - Unix backup files
+ /#.+#\$/ - Emacs swap files
/[._].*\\.swp\$/ - Vi(m) swap files
- /core\\.\\d+\$/ - core dumps
+ /core\\.\\d+\$/ - core dumps
+ /[.-]min\\.js\$/ - Minified javascript files
Miscellaneous:
--noenv Ignore environment variables and ~/.ackrc
$copyright
-This program is free software; you can redistribute it and/or modify
-it under the terms of either: the GNU General Public License as
-published by the Free Software Foundation; or the Artistic License.
+This program is free software. You may modify or distribute it
+under the terms of the Artistic License v2.0.
END_OF_VERSION
}
$ENV{ACK_COLOR_MATCH} ||= 'black on_yellow';
$ENV{ACK_COLOR_FILENAME} ||= 'bold green';
+ $ENV{ACK_COLOR_LINENO} ||= 'bold yellow';
return;
}
my $nmatches = shift;
my $ors = shift;
my $count = shift;
+ my $show_filename = shift;
- App::Ack::print( $filename );
- App::Ack::print( ':', $nmatches ) if $count;
+ if ($show_filename) {
+ App::Ack::print( $filename );
+ App::Ack::print( ':', $nmatches ) if $count;
+ }
+ else {
+ App::Ack::print( $nmatches ) if $count;
+ }
App::Ack::print( $ors );
}
sub print_count0 {
my $filename = shift;
my $ors = shift;
+ my $show_filename = shift;
- App::Ack::print( $filename, ':0', $ors );
+ if ($show_filename) {
+ App::Ack::print( $filename, ':0', $ors );
+ }
+ else {
+ App::Ack::print( '0', $ors );
+ }
}
if ( $show_filename ) {
App::Ack::print_filename($display_filename, $sep) if not $heading;
- App::Ack::print_line_no($line_no, $sep);
+ my $display_line_no =
+ $color
+ ? Term::ANSIColor::colored( $line_no, $ENV{ACK_COLOR_LINENO} )
+ : $line_no;
+ App::Ack::print_line_no($display_line_no, $sep);
}
if ( $output_func ) {
} # scope around search_resource() and print_match_or_context()
+TOTAL_COUNT_SCOPE: {
+my $total_count;
+
+sub get_total_count {
+ return $total_count;
+}
+
+sub reset_total_count {
+ $total_count = 0;
+}
+
sub search_and_list {
my $res = shift;
my $nmatches = 0;
my $count = $opt->{count};
my $ors = $opt->{print0} ? "\0" : "\n"; # output record separator
+ my $show_filename = $opt->{show_filename};
my $regex = qr/$opt->{regex}/;
}
}
- if ( $nmatches ) {
- App::Ack::print_count( $res->name, $nmatches, $ors, $count );
+ if ( $opt->{show_total} ) {
+ $total_count += $nmatches;
}
- elsif ( $count && !$opt->{l} ) {
- App::Ack::print_count0( $res->name, $ors );
+ else {
+ if ( $nmatches ) {
+ App::Ack::print_count( $res->name, $nmatches, $ors, $count, $show_filename );
+ }
+ elsif ( $count && !$opt->{l} ) {
+ App::Ack::print_count0( $res->name, $ors, $show_filename );
+ }
}
return $nmatches ? 1 : 0;
} # search_and_list()
+} # scope around $total_count
+
sub filetypes_supported_set {
my $nmatches = 0;
while ( defined ( my $file = $iter->() ) ) {
- App::Ack::print $file, $ors;
+ App::Ack::print $file, $opt->{show_types} ? " => " . join( ',', filetypes( $file ) ) : (), $ors;
$nmatches++;
last if $opt->{1};
}
my $iter = shift;
my $opt = shift;
+ # if we have -l and only 1 file given on command line (this means
+ # show_filename is set to 0), we want to see the filename nevertheless
+ $opt->{show_filename} = 1 if $opt->{l};
+
+ $opt->{show_filename} = 0 if $opt->{h};
+ $opt->{show_filename} = 1 if $opt->{H};
+
+ # abuse options to hand in the show_total parameter to search_and_list
+ $opt->{show_total} = $opt->{count} && !$opt->{show_filename};
+ reset_total_count();
+
my $nmatches = 0;
while ( defined ( my $filename = $iter->() ) ) {
my $repo = App::Ack::Repository::Basic->new( $filename );
$repo->close();
}
+ if ( $nmatches && $opt->{show_total} ) {
+ App::Ack::print_count('', get_total_count(), "\n", 1, 0 )
+ }
+
return $nmatches;
}
return \@what;
}
+sub _match {
+ my ( $target, $expression, $invert_flag ) = @_;
+
+ if ( $invert_flag ) {
+ return $target !~ $expression;
+ }
+ else {
+ return $target =~ $expression;
+ }
+}
sub get_iterator {
if ( $g_regex ) {
$file_filter
- = $opt->{u} ? sub { $File::Next::name =~ /$g_regex/ } # XXX Maybe this should be a 1, no?
- : $opt->{all} ? sub { $starting_point{ $File::Next::name } || ( $File::Next::name =~ /$g_regex/ && is_searchable( $_ ) ) }
- : sub { $starting_point{ $File::Next::name } || ( $File::Next::name =~ /$g_regex/ && is_interesting( @_ ) ) }
+ = $opt->{u} ? sub { _match( $File::Next::name, qr/$g_regex/, $opt->{invert_file_match} ) } # XXX Maybe this should be a 1, no?
+ : $opt->{all} ? sub { $starting_point{ $File::Next::name } || ( _match( $File::Next::name, qr/$g_regex/, $opt->{invert_file_match} ) && is_searchable( $_ ) ) }
+ : sub { $starting_point{ $File::Next::name } || ( _match( $File::Next::name, qr/$g_regex/, $opt->{invert_file_match} ) && is_interesting( @ _) ) }
;
}
else {
}
+sub exit_from_ack {
+ my $nmatches = shift;
+
+ my $rc = $nmatches ? 0 : 1;
+ exit $rc;
+}
+
+
1; # End of App::Ack
package App::Ack::Repository;