mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-11-14 15:20:28 +03:00
2785 lines
76 KiB
Perl
Executable File
2785 lines
76 KiB
Perl
Executable File
#!/usr/bin/env perl
|
|
#
|
|
# This file, ack, is generated code.
|
|
# Please DO NOT EDIT or send patches for it.
|
|
#
|
|
# Please take a look at the source from
|
|
# http://github.com/petdance/ack
|
|
# and submit patches against the individual files
|
|
# that build ack.
|
|
#
|
|
|
|
use warnings;
|
|
use strict;
|
|
|
|
our $VERSION = '1.96';
|
|
# Check http://betterthangrep.com/ for updates
|
|
|
|
# These are all our globals.
|
|
|
|
|
|
MAIN: {
|
|
if ( $App::Ack::VERSION ne $main::VERSION ) {
|
|
App::Ack::die( "Program/library version mismatch\n\t$0 is $main::VERSION\n\t$INC{'App/Ack.pm'} is $App::Ack::VERSION" );
|
|
}
|
|
|
|
# Do preliminary arg checking;
|
|
my $env_is_usable = 1;
|
|
for ( @ARGV ) {
|
|
last if ( $_ eq '--' );
|
|
|
|
# Priorities! Get the --thpppt checking out of the way.
|
|
/^--th[pt]+t+$/ && App::Ack::_thpppt($_);
|
|
|
|
# See if we want to ignore the environment. (Don't tell Al Gore.)
|
|
if ( /^--(no)?env$/ ) {
|
|
$env_is_usable = defined $1 ? 0 : 1;
|
|
}
|
|
}
|
|
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} ) {
|
|
App::Ack::warn( 'ACK_SWITCHES is no longer supported. Use ACK_OPTIONS.' );
|
|
}
|
|
|
|
if ( !@ARGV ) {
|
|
App::Ack::show_help();
|
|
exit 1;
|
|
}
|
|
|
|
main();
|
|
}
|
|
|
|
sub main {
|
|
my $opt = App::Ack::get_command_line_options();
|
|
|
|
$| = 1 if $opt->{flush}; # Unbuffer the output if flush mode
|
|
|
|
if ( App::Ack::input_from_pipe() ) {
|
|
# We're going into filter mode
|
|
for ( qw( f g l ) ) {
|
|
$opt->{$_} and App::Ack::die( "Can't use -$_ when acting as a filter." );
|
|
}
|
|
$opt->{show_filename} = 0;
|
|
$opt->{regex} = App::Ack::build_regex( defined $opt->{regex} ? $opt->{regex} : shift @ARGV, $opt );
|
|
if ( my $nargs = @ARGV ) {
|
|
my $s = $nargs == 1 ? '' : 's';
|
|
App::Ack::warn( "Ignoring $nargs argument$s on the command-line while acting as a filter." );
|
|
}
|
|
|
|
my $res = App::Ack::Resource::Basic->new( '-' );
|
|
my $nmatches;
|
|
if ( $opt->{count} ) {
|
|
$nmatches = App::Ack::search_and_list( $res, $opt );
|
|
}
|
|
else {
|
|
# normal searching
|
|
$nmatches = App::Ack::search_resource( $res, $opt );
|
|
}
|
|
$res->close();
|
|
App::Ack::exit_from_ack( $nmatches );
|
|
}
|
|
|
|
my $file_matching = $opt->{f} || $opt->{lines};
|
|
if ( $file_matching ) {
|
|
App::Ack::die( "Can't specify both a regex ($opt->{regex}) and use one of --line, -f or -g." ) if $opt->{regex};
|
|
}
|
|
else {
|
|
$opt->{regex} = App::Ack::build_regex( defined $opt->{regex} ? $opt->{regex} : shift @ARGV, $opt );
|
|
}
|
|
|
|
# check that all regexes do compile fine
|
|
App::Ack::check_regex( $_ ) for ( $opt->{regex}, $opt->{G} );
|
|
|
|
my $what = App::Ack::get_starting_points( \@ARGV, $opt );
|
|
my $iter = App::Ack::get_iterator( $what, $opt );
|
|
App::Ack::filetype_setup();
|
|
|
|
my $nmatches = 0;
|
|
|
|
App::Ack::set_up_pager( $opt->{pager} ) if defined $opt->{pager};
|
|
if ( $opt->{f} ) {
|
|
$nmatches = App::Ack::print_files( $iter, $opt );
|
|
}
|
|
elsif ( $opt->{l} || $opt->{count} ) {
|
|
$nmatches = App::Ack::print_files_with_matches( $iter, $opt );
|
|
}
|
|
else {
|
|
$nmatches = App::Ack::print_matches( $iter, $opt );
|
|
}
|
|
close $App::Ack::fh;
|
|
App::Ack::exit_from_ack( $nmatches );
|
|
}
|
|
|
|
=head1 NAME
|
|
|
|
ack - grep-like text finder
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
ack [options] PATTERN [FILE...]
|
|
ack -f [options] [DIRECTORY...]
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
Ack is designed as a replacement for 99% of the uses of F<grep>.
|
|
|
|
Ack searches the named input FILEs (or standard input if no files are
|
|
named, or the file name - is given) for lines containing a match to the
|
|
given PATTERN. By default, ack prints the matching lines.
|
|
|
|
Ack can also list files that would be searched, without actually searching
|
|
them, to let you take advantage of ack's file-type filtering capabilities.
|
|
|
|
=head1 FILE SELECTION
|
|
|
|
I<ack> is intelligent about the files it searches. It knows about
|
|
certain file types, based on both the extension on the file and,
|
|
in some cases, the contents of the file. These selections can be
|
|
made with the B<--type> option.
|
|
|
|
With no file selections, I<ack> only searches files of types that
|
|
it recognizes. If you have a file called F<foo.wango>, and I<ack>
|
|
doesn't know what a .wango file is, I<ack> won't search it.
|
|
|
|
The B<-a> option tells I<ack> to select all files, regardless of
|
|
type.
|
|
|
|
Some files will never be selected by I<ack>, even with B<-a>,
|
|
including:
|
|
|
|
=over 4
|
|
|
|
=item * Backup files: Files matching F<#*#> or ending with F<~>.
|
|
|
|
=item * Coredumps: Files matching F<core.\d+>
|
|
|
|
=back
|
|
|
|
However, I<ack> always searches the files given on the command line,
|
|
no matter what type. Furthermore, by specifying the B<-u> option all
|
|
files will be searched.
|
|
|
|
=head1 DIRECTORY SELECTION
|
|
|
|
I<ack> descends through the directory tree of the starting directories
|
|
specified. However, it will ignore the shadow directories used by
|
|
many version control systems, and the build directories used by the
|
|
Perl MakeMaker system. You may add or remove a directory from this
|
|
list with the B<--[no]ignore-dir> option. The option may be repeated
|
|
to add/remove multiple directories from the ignore list.
|
|
|
|
For a complete list of directories that do not get searched, run
|
|
F<ack --help>.
|
|
|
|
=head1 WHEN TO USE GREP
|
|
|
|
I<ack> trumps I<grep> as an everyday tool 99% of the time, but don't
|
|
throw I<grep> away, because there are times you'll still need it.
|
|
|
|
E.g., searching through huge files looking for regexes that can be
|
|
expressed with I<grep> syntax should be quicker with I<grep>.
|
|
|
|
If your script or parent program uses I<grep> C<--quiet> or
|
|
C<--silent> or needs exit 2 on IO error, use I<grep>.
|
|
|
|
=head1 OPTIONS
|
|
|
|
=over 4
|
|
|
|
=item B<-a>, B<--all>
|
|
|
|
Operate on all files, regardless of type (but still skip directories
|
|
like F<blib>, F<CVS>, etc.)
|
|
|
|
=item B<-A I<NUM>>, B<--after-context=I<NUM>>
|
|
|
|
Print I<NUM> lines of trailing context after matching lines.
|
|
|
|
=item B<-B I<NUM>>, B<--before-context=I<NUM>>
|
|
|
|
Print I<NUM> lines of leading context before matching lines.
|
|
|
|
=item B<-C [I<NUM>]>, B<--context[=I<NUM>]>
|
|
|
|
Print I<NUM> lines (default 2) of context around matching lines.
|
|
|
|
=item B<-c>, B<--count>
|
|
|
|
Suppress normal output; instead print a count of matching lines for
|
|
each input file. If B<-l> is in effect, it will only show the
|
|
number of lines for each file that has lines matching. Without
|
|
B<-l>, some line counts may be zeroes.
|
|
|
|
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
|
|
the color. This is on by default unless the output is redirected.
|
|
|
|
On Windows, this option is off by default unless the
|
|
L<Win32::Console::ANSI> module is installed or the C<ACK_PAGER_COLOR>
|
|
environment variable is used.
|
|
|
|
=item B<--color-filename=I<color>>
|
|
|
|
Sets the color to be used for filenames.
|
|
|
|
=item B<--color-match=I<color>>
|
|
|
|
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
|
|
that can place your cursor at a given position.
|
|
|
|
=item B<--env>, B<--noenv>
|
|
|
|
B<--noenv> disables all environment processing. No F<.ackrc> is read
|
|
and all environment variables are ignored. By default, F<ack> considers
|
|
F<.ackrc> and settings in the environment.
|
|
|
|
=item B<--flush>
|
|
|
|
B<--flush> flushes output immediately. This is off by default
|
|
unless ack is running interactively (when output goes to a pipe
|
|
or file).
|
|
|
|
=item B<-f>
|
|
|
|
Only print the files that would be searched, without actually doing
|
|
any searching. PATTERN must not be specified, or it will be taken as
|
|
a path to search.
|
|
|
|
=item B<--follow>, B<--nofollow>
|
|
|
|
Follow or don't follow symlinks, other than whatever starting files
|
|
or directories were specified on the command line.
|
|
|
|
This is off by default.
|
|
|
|
=item B<-G I<REGEX>>
|
|
|
|
Only paths matching I<REGEX> are included in the search. The entire
|
|
path and filename are matched against I<REGEX>, and I<REGEX> is a
|
|
Perl regular expression, not a shell glob.
|
|
|
|
The options B<-i>, B<-w>, B<-v>, and B<-Q> do not apply to this I<REGEX>.
|
|
|
|
=item B<-g I<REGEX>>
|
|
|
|
Print files where the relative path + filename matches I<REGEX>. This option is
|
|
a convenience shortcut for B<-f> B<-G I<REGEX>>.
|
|
|
|
The options B<-i>, B<-w>, B<-v>, and B<-Q> do not apply to this I<REGEX>.
|
|
|
|
=item B<--group>, B<--nogroup>
|
|
|
|
B<--group> groups matches by file name with. This is the default when
|
|
used interactively.
|
|
|
|
B<--nogroup> prints one result per line, like grep. This is the default
|
|
when output is redirected.
|
|
|
|
=item B<-H>, B<--with-filename>
|
|
|
|
Print the filename for each match.
|
|
|
|
=item B<-h>, B<--no-filename>
|
|
|
|
Suppress the prefixing of filenames on output when multiple files are
|
|
searched.
|
|
|
|
=item B<--help>
|
|
|
|
Print a short help statement.
|
|
|
|
=item B<-i>, B<--ignore-case>
|
|
|
|
Ignore case in the search strings.
|
|
|
|
This applies only to the PATTERN, not to the regexes given for the B<-g>
|
|
and B<-G> options.
|
|
|
|
=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
|
|
B<--ignore-dir=data>. The B<--noignore-dir> option allows users to search
|
|
directories which would normally be ignored (perhaps to research the contents
|
|
of F<.svn/props> directories).
|
|
|
|
The I<DIRNAME> must always be a simple directory name. Nested directories like
|
|
F<foo/bar> are NOT supported. You would need to specify B<--ignore-dir=foo> and
|
|
then no files from any foo directory are taken into account by ack unless given
|
|
explicitly on the command line.
|
|
|
|
=item B<--line=I<NUM>>
|
|
|
|
Only print line I<NUM> of each file. Multiple lines can be given with multiple
|
|
B<--line> options or as a comma separated list (B<--line=3,5,7>). B<--line=4-7>
|
|
also works. The lines are always output in ascending order, no matter the
|
|
order given on the command line.
|
|
|
|
=item B<-l>, B<--files-with-matches>
|
|
|
|
Only print the filenames of matching files, instead of the matching text.
|
|
|
|
=item B<-L>, B<--files-without-matches>
|
|
|
|
Only print the filenames of files that do I<NOT> match. This is equivalent
|
|
to specifying B<-l> and B<-v>.
|
|
|
|
=item B<--match I<REGEX>>
|
|
|
|
Specify the I<REGEX> explicitly. This is helpful if you don't want to put the
|
|
regex as your first argument, e.g. when executing multiple searches over the
|
|
same set of files.
|
|
|
|
# search for foo and bar in given files
|
|
ack file1 t/file* --match foo
|
|
ack file1 t/file* --match bar
|
|
|
|
=item B<-m=I<NUM>>, B<--max-count=I<NUM>>
|
|
|
|
Stop reading a file after I<NUM> matches.
|
|
|
|
=item B<--man>
|
|
|
|
Print this manual page.
|
|
|
|
=item B<-n>, B<--no-recurse>
|
|
|
|
No descending into subdirectories.
|
|
|
|
=item B<-o>
|
|
|
|
Show only the part of each line matching PATTERN (turns off text
|
|
highlighting)
|
|
|
|
=item B<--output=I<expr>>
|
|
|
|
Output the evaluation of I<expr> for each line (turns off text
|
|
highlighting)
|
|
|
|
=item B<--pager=I<program>>
|
|
|
|
Direct ack's output through I<program>. This can also be specified
|
|
via the C<ACK_PAGER> and C<ACK_PAGER_COLOR> environment variables.
|
|
|
|
Using --pager does not suppress grouping and coloring like piping
|
|
output on the command-line does.
|
|
|
|
=item B<--passthru>
|
|
|
|
Prints all lines, whether or not they match the expression. Highlighting
|
|
will still work, though, so it can be used to highlight matches while
|
|
still seeing the entire file, as in:
|
|
|
|
# Watch a log file, and highlight a certain IP address
|
|
$ tail -f ~/access.log | ack --passthru 123.45.67.89
|
|
|
|
=item B<--print0>
|
|
|
|
Only works in conjunction with -f, -g, -l or -c (filename output). The filenames
|
|
are output separated with a null byte instead of the usual newline. This is
|
|
helpful when dealing with filenames that contain whitespace, e.g.
|
|
|
|
# remove all files of type html
|
|
ack -f --html --print0 | xargs -0 rm -f
|
|
|
|
=item B<-Q>, B<--literal>
|
|
|
|
Quote all metacharacters in PATTERN, it is treated as a literal.
|
|
|
|
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
|
|
characters. This is similar to C<smartcase> in vim. This option is
|
|
off by default.
|
|
|
|
B<-i> always overrides this option.
|
|
|
|
This applies only to the PATTERN, not to the regexes given for the
|
|
B<-g> and B<-G> options.
|
|
|
|
=item B<--sort-files>
|
|
|
|
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
|
|
spelling of B<--thpppppt> is not important. It's checked against
|
|
a regular expression.
|
|
|
|
=item B<--type=TYPE>, B<--type=noTYPE>
|
|
|
|
Specify the types of files to include or exclude from a search.
|
|
TYPE is a filetype, like I<perl> or I<xml>. B<--type=perl> can
|
|
also be specified as B<--perl>, and B<--type=noperl> can be done
|
|
as B<--noperl>.
|
|
|
|
If a file is of both type "foo" and "bar", specifying --foo and
|
|
--nobar will exclude the file, because an exclusion takes precedence
|
|
over an inclusion.
|
|
|
|
Type specifications can be repeated and are ORed together.
|
|
|
|
See I<ack --help=types> for a list of valid types.
|
|
|
|
=item B<--type-add I<TYPE>=I<.EXTENSION>[,I<.EXT2>[,...]]>
|
|
|
|
Files with the given EXTENSION(s) are recognized as being of (the
|
|
existing) type TYPE. See also L</"Defining your own types">.
|
|
|
|
|
|
=item B<--type-set I<TYPE>=I<.EXTENSION>[,I<.EXT2>[,...]]>
|
|
|
|
Files with the given EXTENSION(s) are recognized as being of type
|
|
TYPE. This replaces an existing definition for type TYPE. See also
|
|
L</"Defining your own types">.
|
|
|
|
=item B<-u>, B<--unrestricted>
|
|
|
|
All files and directories (including blib/, core.*, ...) are searched,
|
|
nothing is skipped. When both B<-u> and B<--ignore-dir> are used, the
|
|
B<--ignore-dir> option has no effect.
|
|
|
|
=item B<-v>, B<--invert-match>
|
|
|
|
Invert match: select non-matching lines
|
|
|
|
This applies only to the PATTERN, not to the regexes given for the B<-g>
|
|
and B<-G> options.
|
|
|
|
=item B<--version>
|
|
|
|
Display version and copyright information.
|
|
|
|
=item B<-w>, B<--word-regexp>
|
|
|
|
Force PATTERN to match only whole words. The PATTERN is wrapped with
|
|
C<\b> metacharacters.
|
|
|
|
This applies only to the PATTERN, not to the regexes given for the B<-g>
|
|
and B<-G> options.
|
|
|
|
=item B<-1>
|
|
|
|
Stops after reporting first match of any kind. This is different
|
|
from B<--max-count=1> or B<-m1>, where only one match per file is
|
|
shown. Also, B<-1> works with B<-f> and B<-g>, where B<-m> does
|
|
not.
|
|
|
|
=back
|
|
|
|
=head1 THE .ackrc FILE
|
|
|
|
The F<.ackrc> file contains command-line options that are prepended
|
|
to the command line before processing. Multiple options may live
|
|
on multiple lines. Lines beginning with a # are ignored. A F<.ackrc>
|
|
might look like this:
|
|
|
|
# Always sort the files
|
|
--sort-files
|
|
|
|
# Always color, even if piping to a another program
|
|
--color
|
|
|
|
# Use "less -r" as my pager
|
|
--pager=less -r
|
|
|
|
Note that arguments with spaces in them do not need to be quoted,
|
|
as they are not interpreted by the shell. Basically, each I<line>
|
|
in the F<.ackrc> file is interpreted as one element of C<@ARGV>.
|
|
|
|
F<ack> looks in your home directory for the F<.ackrc>. You can
|
|
specify another location with the F<ACKRC> variable, below.
|
|
|
|
If B<--noenv> is specified on the command line, the F<.ackrc> file
|
|
is ignored.
|
|
|
|
=head1 Defining your own types
|
|
|
|
ack allows you to define your own types in addition to the predefined
|
|
types. This is done with command line options that are best put into
|
|
an F<.ackrc> file - then you do not have to define your types over and
|
|
over again. In the following examples the options will always be shown
|
|
on one command line so that they can be easily copy & pasted.
|
|
|
|
I<ack --perl foo> searches for foo in all perl files. I<ack --help=types>
|
|
tells you, that perl files are files ending
|
|
in .pl, .pm, .pod or .t. So what if you would like to include .xs
|
|
files as well when searching for --perl files? I<ack --type-add perl=.xs --perl foo>
|
|
does this for you. B<--type-add> appends
|
|
additional extensions to an existing type.
|
|
|
|
If you want to define a new type, or completely redefine an existing
|
|
type, then use B<--type-set>. I<ack --type-set
|
|
eiffel=.e,.eiffel> defines the type I<eiffel> to include files with
|
|
the extensions .e or .eiffel. So to search for all eiffel files
|
|
containing the word Bertrand use I<ack --type-set eiffel=.e,.eiffel --eiffel Bertrand>.
|
|
As usual, you can also write B<--type=eiffel>
|
|
instead of B<--eiffel>. Negation also works, so B<--noeiffel> excludes
|
|
all eiffel files from a search. Redefining also works: I<ack --type-set cc=.c,.h>
|
|
and I<.xs> files no longer belong to the type I<cc>.
|
|
|
|
When defining your own types in the F<.ackrc> file you have to use
|
|
the following:
|
|
|
|
--type-set=eiffel=.e,.eiffel
|
|
|
|
or writing on separate lines
|
|
|
|
--type-set
|
|
eiffel=.e,.eiffel
|
|
|
|
The following does B<NOT> work in the F<.ackrc> file:
|
|
|
|
--type-set eiffel=.e,.eiffel
|
|
|
|
|
|
In order to see all currently defined types, use I<--help types>, e.g.
|
|
I<ack --type-set backup=.bak --type-add perl=.perl --help types>
|
|
|
|
Restrictions:
|
|
|
|
=over 4
|
|
|
|
=item
|
|
|
|
The types 'skipped', 'make', 'binary' and 'text' are considered "builtin" and
|
|
cannot be altered.
|
|
|
|
=item
|
|
|
|
The shebang line recognition of the types 'perl', 'ruby', 'php', 'python',
|
|
'shell' and 'xml' cannot be redefined by I<--type-set>, it is always
|
|
active. However, the shebang line is only examined for files where the
|
|
extension is not recognised. Therefore it is possible to say
|
|
I<ack --type-set perl=.perl --type-set foo=.pl,.pm,.pod,.t --perl --nofoo> and
|
|
only find your shiny new I<.perl> files (and all files with unrecognized extension
|
|
and perl on the shebang line).
|
|
|
|
=back
|
|
|
|
=head1 ENVIRONMENT VARIABLES
|
|
|
|
For commonly-used ack options, environment variables can make life much easier.
|
|
These variables are ignored if B<--noenv> is specified on the command line.
|
|
|
|
=over 4
|
|
|
|
=item ACKRC
|
|
|
|
Specifies the location of the F<.ackrc> file. If this file doesn't
|
|
exist, F<ack> looks in the default location.
|
|
|
|
=item ACK_OPTIONS
|
|
|
|
This variable specifies default options to be placed in front of
|
|
any explicit options on the command line.
|
|
|
|
=item ACK_COLOR_FILENAME
|
|
|
|
Specifies the color of the filename when it's printed in B<--group>
|
|
mode. By default, it's "bold green".
|
|
|
|
The recognized attributes are clear, reset, dark, bold, underline,
|
|
underscore, blink, reverse, concealed black, red, green, yellow,
|
|
blue, magenta, on_black, on_red, on_green, on_yellow, on_blue,
|
|
on_magenta, on_cyan, and on_white. Case is not significant.
|
|
Underline and underscore are equivalent, as are clear and reset.
|
|
The color alone sets the foreground color, and on_color sets the
|
|
background color.
|
|
|
|
This option can also be set with B<--color-filename>.
|
|
|
|
=item ACK_COLOR_MATCH
|
|
|
|
Specifies the color of the matching text when printed in B<--color>
|
|
mode. By default, it's "black on_yellow".
|
|
|
|
This option can also be set with B<--color-match>.
|
|
|
|
See B<ACK_COLOR_FILENAME> for the color specifications.
|
|
|
|
=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
|
|
ack will send its output.
|
|
|
|
Using C<ACK_PAGER> does not suppress grouping and coloring like
|
|
piping output on the command-line does, except that on Windows
|
|
ack will assume that C<ACK_PAGER> does not support color.
|
|
|
|
C<ACK_PAGER_COLOR> overrides C<ACK_PAGER> if both are specified.
|
|
|
|
=item ACK_PAGER_COLOR
|
|
|
|
Specifies a pager program that understands ANSI color sequences.
|
|
Using C<ACK_PAGER_COLOR> does not suppress grouping and coloring
|
|
like piping output on the command-line does.
|
|
|
|
If you are not on Windows, you never need to use C<ACK_PAGER_COLOR>.
|
|
|
|
=back
|
|
|
|
=head1 ACK & OTHER TOOLS
|
|
|
|
=head2 Vim integration
|
|
|
|
F<ack> integrates easily with the Vim text editor. Set this in your
|
|
F<.vimrc> to use F<ack> instead of F<grep>:
|
|
|
|
set grepprg=ack\ -a
|
|
|
|
That examples uses C<-a> to search through all files, but you may
|
|
use other default flags. Now you can search with F<ack> and easily
|
|
step through the results in Vim:
|
|
|
|
:grep Dumper perllib
|
|
|
|
=head2 Emacs integration
|
|
|
|
Phil Jackson put together an F<ack.el> extension that "provides a
|
|
simple compilation mode ... has the ability to guess what files you
|
|
want to search for based on the major-mode."
|
|
|
|
L<http://www.shellarchive.co.uk/content/emacs.html>
|
|
|
|
=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<http://www.simplicidade.org/notes/archives/2008/03/search_in_proje.html>"
|
|
|
|
=head2 Shell and Return Code
|
|
|
|
For greater compatibility with I<grep>, I<ack> in normal use returns
|
|
shell return or exit code of 0 only if something is found and 1 if
|
|
no match is found.
|
|
|
|
(Shell exit code 1 is C<$?=256> in perl with C<system> or backticks.)
|
|
|
|
The I<grep> code 2 for errors is not used.
|
|
|
|
If C<-f> or C<-g> are specified, then 0 is returned if at least one
|
|
file is found. If no files are found, then 1 is returned.
|
|
|
|
=cut
|
|
|
|
=head1 DEBUGGING ACK PROBLEMS
|
|
|
|
If ack gives you output you're not expecting, start with a few simple steps.
|
|
|
|
=head2 Use B<--noenv>
|
|
|
|
Your environment variables and F<.ackrc> may be doing things you're
|
|
not expecting, or forgotten you specified. Use B<--noenv> to ignore
|
|
your environment and F<.ackrc>.
|
|
|
|
=head2 Use B<-f> to see what files you're scanning
|
|
|
|
The reason I created B<-f> in the first place was as a debugging
|
|
tool. If ack is not finding matches you think it should find, run
|
|
F<ack -f> to see what files are being checked.
|
|
|
|
=head1 TIPS
|
|
|
|
=head2 Use the F<.ackrc> file.
|
|
|
|
The F<.ackrc> is the place to put all your options you use most of
|
|
the time but don't want to remember. Put all your --type-add and
|
|
--type-set definitions in it. If you like --smart-case, set it
|
|
there, too. I also set --sort-files there.
|
|
|
|
=head2 Use F<-f> for working with big codesets
|
|
|
|
Ack does more than search files. C<ack -f --perl> will create a
|
|
list of all the Perl files in a tree, ideal for sending into F<xargs>.
|
|
For example:
|
|
|
|
# Change all "this" to "that" in all Perl files in a tree.
|
|
ack -f --perl | xargs perl -p -i -e's/this/that/g'
|
|
|
|
or if you prefer:
|
|
|
|
perl -p -i -e's/this/thatg/' $(ack -f --perl)
|
|
|
|
=head2 Use F<-Q> when in doubt about metacharacters
|
|
|
|
If you're searching for something with a regular expression
|
|
metacharacter, most often a period in a filename or IP address, add
|
|
the -Q to avoid false positives without all the backslashing. See
|
|
the following example for more...
|
|
|
|
=head2 Use ack to watch log files
|
|
|
|
Here's one I used the other day to find trouble spots for a website
|
|
visitor. The user had a problem loading F<troublesome.gif>, so I
|
|
took the access log and scanned it with ack twice.
|
|
|
|
ack -Q aa.bb.cc.dd /path/to/access.log | ack -Q -B5 troublesome.gif
|
|
|
|
The first ack finds only the lines in the Apache log for the given
|
|
IP. The second finds the match on my troublesome GIF, and shows
|
|
the previous five lines from the log in each case.
|
|
|
|
=head2 Share your knowledge
|
|
|
|
Join the ack-users mailing list. Send me your tips and I may add
|
|
them here.
|
|
|
|
=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<If ack doesn't know
|
|
what kind of file it is, ack ignores the file.>
|
|
|
|
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.
|
|
|
|
If you want ack to search every file, even ones that it always
|
|
ignores like coredumps and backup files, use the C<-u> switch.
|
|
|
|
=head2 Why does ack ignore unknown files by default?
|
|
|
|
ack is designed by a programmer, for programmers, for searching
|
|
large trees of code. Most codebases have a lot files in them which
|
|
aren't source files (like compiled object files, source control
|
|
metadata, etc), and grep wastes a lot of time searching through all
|
|
of those as well and returning matches from those files.
|
|
|
|
That's why ack's behavior of not searching things it doesn't recognize
|
|
is one of its greatest strengths: the speed you get from only
|
|
searching the things that you want to be looking at.
|
|
|
|
=head2 Wouldn't it be great if F<ack> did search & replace?
|
|
|
|
No, ack will always be read-only. Perl has a perfectly good way
|
|
to do search & replace in files, using the C<-i>, C<-p> and C<-n>
|
|
switches.
|
|
|
|
You can certainly use ack to select your files to update. For
|
|
example, to change all "foo" to "bar" in all PHP files, you can do
|
|
this from the Unix shell:
|
|
|
|
$ perl -i -p -e's/foo/bar/g' $(ack -f --php)
|
|
|
|
=head2 Can you make ack recognize F<.xyz> files?
|
|
|
|
That's an enhancement. Please see the section in the manual about
|
|
enhancements.
|
|
|
|
=head2 There's already a program/package called ack.
|
|
|
|
Yes, I know.
|
|
|
|
=head2 Why is it called ack if it's called ack-grep?
|
|
|
|
The name of the program is "ack". Some packagers have called it
|
|
"ack-grep" when creating packages because there's already a package
|
|
out there called "ack" that has nothing to do with this ack.
|
|
|
|
I suggest you make a symlink named F<ack> that points to F<ack-grep>
|
|
because one of the crucial benefits of ack is having a name that's
|
|
so short and simple to type.
|
|
|
|
To do that, run this with F<sudo> or as root:
|
|
|
|
ln -s /usr/bin/ack-grep /usr/bin/ack
|
|
|
|
=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
|
|
|
|
Andy Lester, C<< <andy at petdance.com> >>
|
|
|
|
=head1 BUGS
|
|
|
|
Please report any bugs or feature requests to the issues list at
|
|
Github: L<http://github.com/petdance/ack/issues>
|
|
|
|
=head1 ENHANCEMENTS
|
|
|
|
All enhancement requests MUST first be posted to the ack-users
|
|
mailing list at L<http://groups.google.com/group/ack-users>. I
|
|
will not consider a request without it first getting seen by other
|
|
ack users. This includes requests for new filetypes.
|
|
|
|
There is a list of enhancements I want to make to F<ack> in the ack
|
|
issues list at Github: L<http://github.com/petdance/ack/issues>
|
|
|
|
Patches are always welcome, but patches with tests get the most
|
|
attention.
|
|
|
|
=head1 SUPPORT
|
|
|
|
Support for and information about F<ack> can be found at:
|
|
|
|
=over 4
|
|
|
|
=item * The ack homepage
|
|
|
|
L<http://betterthangrep.com/>
|
|
|
|
=item * The ack issues list at Github
|
|
|
|
L<http://github.com/petdance/ack/issues>
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation
|
|
|
|
L<http://annocpan.org/dist/ack>
|
|
|
|
=item * CPAN Ratings
|
|
|
|
L<http://cpanratings.perl.org/d/ack>
|
|
|
|
=item * Search CPAN
|
|
|
|
L<http://search.cpan.org/dist/ack>
|
|
|
|
=item * Git source repository
|
|
|
|
L<http://github.com/petdance/ack>
|
|
|
|
=back
|
|
|
|
=head1 ACKNOWLEDGEMENTS
|
|
|
|
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,
|
|
Ryan Niebur,
|
|
Kent Fredric,
|
|
Mike Morearty,
|
|
Ingmar Vanhassel,
|
|
Eric Van Dewoestine,
|
|
Sitaram Chamarty,
|
|
Adam James,
|
|
Richard Carlsson,
|
|
Pedro Melo,
|
|
AJ Schuster,
|
|
Phil Jackson,
|
|
Michael Schwern,
|
|
Jan Dubois,
|
|
Christopher J. Madsen,
|
|
Matthew Wickline,
|
|
David Dyck,
|
|
Jason Porritt,
|
|
Jjgod Jiang,
|
|
Thomas Klausner,
|
|
Uri Guttman,
|
|
Peter Lewis,
|
|
Kevin Riggle,
|
|
Ori Avtalion,
|
|
Torsten Blix,
|
|
Nigel Metheringham,
|
|
GE<aacute>bor SzabE<oacute>,
|
|
Tod Hagan,
|
|
Michael Hendricks,
|
|
E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason,
|
|
Piers Cawley,
|
|
Stephen Steneker,
|
|
Elias Lutfallah,
|
|
Mark Leighton Fisher,
|
|
Matt Diephouse,
|
|
Christian Jaeger,
|
|
Bill Sully,
|
|
Bill Ricker,
|
|
David Golden,
|
|
Nilson Santos F. Jr,
|
|
Elliot Shank,
|
|
Merijn Broeren,
|
|
Uwe Voelker,
|
|
Rick Scott,
|
|
Ask BjE<oslash>rn Hansen,
|
|
Jerry Gay,
|
|
Will Coleda,
|
|
Mike O'Regan,
|
|
Slaven ReziE<0x107>,
|
|
Mark Stosberg,
|
|
David Alan Pisoni,
|
|
Adriano Ferreira,
|
|
James Keenan,
|
|
Leland Johnson,
|
|
Ricardo Signes
|
|
and Pete Krawczyk.
|
|
|
|
=head1 COPYRIGHT & LICENSE
|
|
|
|
Copyright 2005-2011 Andy Lester.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the Artistic License v2.0.
|
|
|
|
=cut
|
|
package File::Next;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
|
|
our $VERSION = '1.06';
|
|
|
|
|
|
|
|
use File::Spec ();
|
|
|
|
|
|
our $name; # name of the current file
|
|
our $dir; # dir of the current file
|
|
|
|
our %files_defaults;
|
|
our %skip_dirs;
|
|
|
|
BEGIN {
|
|
%files_defaults = (
|
|
file_filter => undef,
|
|
descend_filter => undef,
|
|
error_handler => sub { CORE::die @_ },
|
|
sort_files => undef,
|
|
follow_symlinks => 1,
|
|
);
|
|
%skip_dirs = map {($_,1)} (File::Spec->curdir, File::Spec->updir);
|
|
}
|
|
|
|
|
|
sub files {
|
|
($_[0] eq __PACKAGE__) && die 'File::Next::files must not be invoked as File::Next->files';
|
|
|
|
my ($parms,@queue) = _setup( \%files_defaults, @_ );
|
|
my $filter = $parms->{file_filter};
|
|
|
|
return sub {
|
|
while (@queue) {
|
|
my ($dir,$file,$fullpath) = splice( @queue, 0, 3 );
|
|
if ( -f $fullpath ) {
|
|
if ( $filter ) {
|
|
local $_ = $file;
|
|
local $File::Next::dir = $dir;
|
|
local $File::Next::name = $fullpath;
|
|
next if not $filter->();
|
|
}
|
|
return wantarray ? ($dir,$file,$fullpath) : $fullpath;
|
|
}
|
|
elsif ( -d _ ) {
|
|
unshift( @queue, _candidate_files( $parms, $fullpath ) );
|
|
}
|
|
} # while
|
|
|
|
return;
|
|
}; # iterator
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sub sort_standard($$) { return $_[0]->[1] cmp $_[1]->[1] }
|
|
sub sort_reverse($$) { return $_[1]->[1] cmp $_[0]->[1] }
|
|
|
|
sub reslash {
|
|
my $path = shift;
|
|
|
|
my @parts = split( /\//, $path );
|
|
|
|
return $path if @parts < 2;
|
|
|
|
return File::Spec->catfile( @parts );
|
|
}
|
|
|
|
|
|
|
|
sub _setup {
|
|
my $defaults = shift;
|
|
my $passed_parms = ref $_[0] eq 'HASH' ? {%{+shift}} : {}; # copy parm hash
|
|
|
|
my %passed_parms = %{$passed_parms};
|
|
|
|
my $parms = {};
|
|
for my $key ( keys %{$defaults} ) {
|
|
$parms->{$key} =
|
|
exists $passed_parms{$key}
|
|
? delete $passed_parms{$key}
|
|
: $defaults->{$key};
|
|
}
|
|
|
|
# Any leftover keys are bogus
|
|
for my $badkey ( keys %passed_parms ) {
|
|
my $sub = (caller(1))[3];
|
|
$parms->{error_handler}->( "Invalid option passed to $sub(): $badkey" );
|
|
}
|
|
|
|
# If it's not a code ref, assume standard sort
|
|
if ( $parms->{sort_files} && ( ref($parms->{sort_files}) ne 'CODE' ) ) {
|
|
$parms->{sort_files} = \&sort_standard;
|
|
}
|
|
my @queue;
|
|
|
|
for ( @_ ) {
|
|
my $start = reslash( $_ );
|
|
if (-d $start) {
|
|
push @queue, ($start,undef,$start);
|
|
}
|
|
else {
|
|
push @queue, (undef,$start,$start);
|
|
}
|
|
}
|
|
|
|
return ($parms,@queue);
|
|
}
|
|
|
|
|
|
sub _candidate_files {
|
|
my $parms = shift;
|
|
my $dir = shift;
|
|
|
|
my $dh;
|
|
if ( !opendir $dh, $dir ) {
|
|
$parms->{error_handler}->( "$dir: $!" );
|
|
return;
|
|
}
|
|
|
|
my @newfiles;
|
|
my $descend_filter = $parms->{descend_filter};
|
|
my $follow_symlinks = $parms->{follow_symlinks};
|
|
my $sort_sub = $parms->{sort_files};
|
|
|
|
for my $file ( grep { !exists $skip_dirs{$_} } readdir $dh ) {
|
|
my $has_stat;
|
|
|
|
# Only do directory checking if we have a descend_filter
|
|
my $fullpath = File::Spec->catdir( $dir, $file );
|
|
if ( !$follow_symlinks ) {
|
|
next if -l $fullpath;
|
|
$has_stat = 1;
|
|
}
|
|
|
|
if ( $descend_filter ) {
|
|
if ( $has_stat ? (-d _) : (-d $fullpath) ) {
|
|
local $File::Next::dir = $fullpath;
|
|
local $_ = $file;
|
|
next if not $descend_filter->();
|
|
}
|
|
}
|
|
if ( $sort_sub ) {
|
|
push( @newfiles, [ $dir, $file, $fullpath ] );
|
|
}
|
|
else {
|
|
push( @newfiles, $dir, $file, $fullpath );
|
|
}
|
|
}
|
|
closedir $dh;
|
|
|
|
if ( $sort_sub ) {
|
|
return map { @{$_} } sort $sort_sub @newfiles;
|
|
}
|
|
|
|
return @newfiles;
|
|
}
|
|
|
|
|
|
1; # End of File::Next
|
|
package App::Ack;
|
|
|
|
use warnings;
|
|
use strict;
|
|
|
|
|
|
|
|
|
|
our $VERSION;
|
|
our $COPYRIGHT;
|
|
BEGIN {
|
|
$VERSION = '1.96';
|
|
$COPYRIGHT = 'Copyright 2005-2011 Andy Lester.';
|
|
}
|
|
|
|
our $fh;
|
|
|
|
BEGIN {
|
|
$fh = *STDOUT;
|
|
}
|
|
|
|
|
|
our %types;
|
|
our %type_wanted;
|
|
our %mappings;
|
|
our %ignore_dirs;
|
|
|
|
our $input_from_pipe;
|
|
our $output_to_pipe;
|
|
|
|
our $dir_sep_chars;
|
|
our $is_cygwin;
|
|
our $is_windows;
|
|
|
|
use File::Spec ();
|
|
use File::Glob ':glob';
|
|
use Getopt::Long ();
|
|
|
|
BEGIN {
|
|
%ignore_dirs = (
|
|
'.bzr' => 'Bazaar',
|
|
'.cdv' => 'Codeville',
|
|
'~.dep' => 'Interface Builder',
|
|
'~.dot' => 'Interface Builder',
|
|
'~.nib' => 'Interface Builder',
|
|
'~.plst' => 'Interface Builder',
|
|
'.git' => 'Git',
|
|
'.hg' => 'Mercurial',
|
|
'.pc' => 'quilt',
|
|
'.svn' => 'Subversion',
|
|
_MTN => 'Monotone',
|
|
blib => 'Perl module building',
|
|
CVS => 'CVS',
|
|
RCS => 'RCS',
|
|
SCCS => 'SCCS',
|
|
_darcs => 'darcs',
|
|
_sgbak => 'Vault/Fortress',
|
|
'autom4te.cache' => 'autoconf',
|
|
'cover_db' => 'Devel::Cover',
|
|
_build => 'Module::Build',
|
|
);
|
|
|
|
%mappings = (
|
|
actionscript => [qw( as mxml )],
|
|
ada => [qw( ada adb ads )],
|
|
asm => [qw( asm s )],
|
|
batch => [qw( bat cmd )],
|
|
binary => q{Binary files, as defined by Perl's -B op (default: off)},
|
|
cc => [qw( c h xs )],
|
|
cfmx => [qw( cfc cfm cfml )],
|
|
clojure => [qw( clj )],
|
|
cpp => [qw( cpp cc cxx m hpp hh h hxx )],
|
|
csharp => [qw( cs )],
|
|
css => [qw( css )],
|
|
delphi => [qw( pas int dfm nfm dof dpk dproj groupproj bdsgroup bdsproj )],
|
|
elisp => [qw( el )],
|
|
erlang => [qw( erl hrl )],
|
|
fortran => [qw( f f77 f90 f95 f03 for ftn fpp )],
|
|
go => [qw( go )],
|
|
groovy => [qw( groovy gtmpl gpp grunit )],
|
|
haskell => [qw( hs lhs )],
|
|
hh => [qw( h )],
|
|
html => [qw( htm html shtml xhtml )],
|
|
java => [qw( java properties )],
|
|
js => [qw( js )],
|
|
jsp => [qw( jsp jspx jhtm jhtml )],
|
|
lisp => [qw( lisp lsp )],
|
|
lua => [qw( lua )],
|
|
make => q{Makefiles (including *.mk and *.mak)},
|
|
mason => [qw( mas mhtml mpl mtxt )],
|
|
objc => [qw( m h )],
|
|
objcpp => [qw( mm h )],
|
|
ocaml => [qw( ml mli )],
|
|
parrot => [qw( pir pasm pmc ops pod pg tg )],
|
|
perl => [qw( pl pm pm6 pod t )],
|
|
php => [qw( php phpt php3 php4 php5 phtml)],
|
|
plone => [qw( pt cpt metadata cpy py )],
|
|
python => [qw( py )],
|
|
rake => q{Rakefiles},
|
|
ruby => [qw( rb rhtml rjs rxml erb rake spec )],
|
|
scala => [qw( scala )],
|
|
scheme => [qw( scm ss )],
|
|
shell => [qw( sh bash csh tcsh ksh zsh )],
|
|
skipped => q{Files, but not directories, normally skipped by ack (default: off)},
|
|
smalltalk => [qw( st )],
|
|
sql => [qw( sql ctl )],
|
|
tcl => [qw( tcl itcl itk )],
|
|
tex => [qw( tex cls sty )],
|
|
text => q{Text files, as defined by Perl's -T op (default: off)},
|
|
tt => [qw( tt tt2 ttml )],
|
|
vb => [qw( bas cls frm ctl vb resx )],
|
|
verilog => [qw( v vh sv )],
|
|
vhdl => [qw( vhd vhdl )],
|
|
vim => [qw( vim )],
|
|
yaml => [qw( yaml yml )],
|
|
xml => [qw( xml dtd xsl xslt ent )],
|
|
);
|
|
|
|
while ( my ($type,$exts) = each %mappings ) {
|
|
if ( ref $exts ) {
|
|
for my $ext ( @{$exts} ) {
|
|
push( @{$types{$ext}}, $type );
|
|
}
|
|
}
|
|
}
|
|
# add manually Makefile extensions
|
|
push @{$types{$_}}, 'make' for qw{ mk mak };
|
|
|
|
# These have to be checked before any filehandle diddling.
|
|
$output_to_pipe = not -t *STDOUT;
|
|
$input_from_pipe = -p STDIN;
|
|
|
|
$is_cygwin = ($^O eq 'cygwin');
|
|
$is_windows = ($^O =~ /MSWin32/);
|
|
$dir_sep_chars = $is_windows ? quotemeta( '\\/' ) : quotemeta( File::Spec->catfile( '', '' ) );
|
|
}
|
|
|
|
|
|
sub read_ackrc {
|
|
my @files = ( $ENV{ACKRC} );
|
|
my @dirs =
|
|
$is_windows
|
|
? ( $ENV{HOME}, $ENV{USERPROFILE} )
|
|
: ( '~', $ENV{HOME} );
|
|
for my $dir ( grep { defined } @dirs ) {
|
|
for my $file ( '.ackrc', '_ackrc' ) {
|
|
push( @files, bsd_glob( "$dir/$file", GLOB_TILDE ) );
|
|
}
|
|
}
|
|
for my $filename ( @files ) {
|
|
if ( defined $filename && -e $filename ) {
|
|
open( my $fh, '<', $filename ) or App::Ack::die( "$filename: $!\n" );
|
|
my @lines = grep { /./ && !/^\s*#/ } <$fh>;
|
|
chomp @lines;
|
|
close $fh or App::Ack::die( "$filename: $!\n" );
|
|
|
|
# get rid of leading and trailing whitespaces
|
|
for ( @lines ) {
|
|
s/^\s+//;
|
|
s/\s+$//;
|
|
}
|
|
|
|
return @lines;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
sub get_command_line_options {
|
|
my %opt = (
|
|
pager => $ENV{ACK_PAGER_COLOR} || $ENV{ACK_PAGER},
|
|
);
|
|
|
|
my $getopt_specs = {
|
|
1 => sub { $opt{1} = $opt{m} = 1 },
|
|
'A|after-context=i' => \$opt{after_context},
|
|
'B|before-context=i' => \$opt{before_context},
|
|
'C|context:i' => sub { shift; my $val = shift; $opt{before_context} = $opt{after_context} = ($val || 2) },
|
|
'a|all-types' => \$opt{all},
|
|
'break!' => \$opt{break},
|
|
c => \$opt{count},
|
|
'color|colour!' => \$opt{color},
|
|
'color-match=s' => \$ENV{ACK_COLOR_MATCH},
|
|
'color-filename=s' => \$ENV{ACK_COLOR_FILENAME},
|
|
'color-lineno=s' => \$ENV{ACK_COLOR_LINENO},
|
|
'column!' => \$opt{column},
|
|
count => \$opt{count},
|
|
'env!' => sub { }, # ignore this option, it is handled beforehand
|
|
f => \$opt{f},
|
|
flush => \$opt{flush},
|
|
'follow!' => \$opt{follow},
|
|
'g=s' => sub { shift; $opt{G} = shift; $opt{f} = 1 },
|
|
'G=s' => \$opt{G},
|
|
'group!' => sub { shift; $opt{heading} = $opt{break} = shift },
|
|
'heading!' => \$opt{heading},
|
|
'h|no-filename' => \$opt{h},
|
|
'H|with-filename' => \$opt{H},
|
|
'i|ignore-case' => \$opt{i},
|
|
'invert-file-match' => \$opt{invert_file_match},
|
|
'lines=s' => sub { shift; my $val = shift; push @{$opt{lines}}, $val },
|
|
'l|files-with-matches' => \$opt{l},
|
|
'L|files-without-matches' => sub { $opt{l} = $opt{v} = 1 },
|
|
'm|max-count=i' => \$opt{m},
|
|
'match=s' => \$opt{regex},
|
|
'n|no-recurse' => \$opt{n},
|
|
o => sub { $opt{output} = '$&' },
|
|
'output=s' => \$opt{output},
|
|
'pager=s' => \$opt{pager},
|
|
'nopager' => sub { $opt{pager} = undef },
|
|
'passthru' => \$opt{passthru},
|
|
'print0' => \$opt{print0},
|
|
'Q|literal' => \$opt{Q},
|
|
'r|R|recurse' => sub { $opt{n} = 0 },
|
|
'show-types' => \$opt{show_types},
|
|
'smart-case!' => \$opt{smart_case},
|
|
'sort-files' => \$opt{sort_files},
|
|
'u|unrestricted' => \$opt{u},
|
|
'v|invert-match' => \$opt{v},
|
|
'w|word-regexp' => \$opt{w},
|
|
|
|
'ignore-dirs=s' => sub { shift; my $dir = remove_dir_sep( shift ); $ignore_dirs{$dir} = '--ignore-dirs' },
|
|
'noignore-dirs=s' => sub { shift; my $dir = remove_dir_sep( shift ); delete $ignore_dirs{$dir} },
|
|
|
|
'version' => sub { print_version_statement(); exit; },
|
|
'help|?:s' => sub { shift; show_help(@_); exit; },
|
|
'help-types'=> sub { show_help_types(); exit; },
|
|
'man' => sub {
|
|
require Pod::Usage;
|
|
Pod::Usage::pod2usage({
|
|
-verbose => 2,
|
|
-exitval => 0,
|
|
});
|
|
},
|
|
|
|
'type=s' => sub {
|
|
# Whatever --type=xxx they specify, set it manually in the hash
|
|
my $dummy = shift;
|
|
my $type = shift;
|
|
my $wanted = ($type =~ s/^no//) ? 0 : 1; # must not be undef later
|
|
|
|
if ( exists $type_wanted{ $type } ) {
|
|
$type_wanted{ $type } = $wanted;
|
|
}
|
|
else {
|
|
App::Ack::die( qq{Unknown --type "$type"} );
|
|
}
|
|
}, # type sub
|
|
};
|
|
|
|
# Stick any default switches at the beginning, so they can be overridden
|
|
# by the command line switches.
|
|
unshift @ARGV, split( ' ', $ENV{ACK_OPTIONS} ) if defined $ENV{ACK_OPTIONS};
|
|
|
|
# first pass through options, looking for type definitions
|
|
def_types_from_ARGV();
|
|
|
|
for my $i ( filetypes_supported() ) {
|
|
$getopt_specs->{ "$i!" } = \$type_wanted{ $i };
|
|
}
|
|
|
|
|
|
my $parser = Getopt::Long::Parser->new();
|
|
$parser->configure( 'bundling', 'no_ignore_case', );
|
|
$parser->getoptions( %{$getopt_specs} ) or
|
|
App::Ack::die( 'See ack --help, ack --help-types or ack --man for options.' );
|
|
|
|
my $to_screen = not output_to_pipe();
|
|
my %defaults = (
|
|
all => 0,
|
|
color => $to_screen,
|
|
follow => 0,
|
|
break => $to_screen,
|
|
heading => $to_screen,
|
|
before_context => 0,
|
|
after_context => 0,
|
|
);
|
|
if ( $is_windows && $defaults{color} && not $ENV{ACK_PAGER_COLOR} ) {
|
|
if ( $ENV{ACK_PAGER} || not eval { require Win32::Console::ANSI } ) {
|
|
$defaults{color} = 0;
|
|
}
|
|
}
|
|
if ( $to_screen && $ENV{ACK_PAGER_COLOR} ) {
|
|
$defaults{color} = 1;
|
|
}
|
|
|
|
while ( my ($key,$value) = each %defaults ) {
|
|
if ( not defined $opt{$key} ) {
|
|
$opt{$key} = $value;
|
|
}
|
|
}
|
|
|
|
if ( defined $opt{m} && $opt{m} <= 0 ) {
|
|
App::Ack::die( '-m must be greater than zero' );
|
|
}
|
|
|
|
for ( qw( before_context after_context ) ) {
|
|
if ( defined $opt{$_} && $opt{$_} < 0 ) {
|
|
App::Ack::die( "--$_ may not be negative" );
|
|
}
|
|
}
|
|
|
|
if ( defined( my $val = $opt{output} ) ) {
|
|
$opt{output} = eval qq[ sub { "$val" } ];
|
|
}
|
|
if ( defined( my $l = $opt{lines} ) ) {
|
|
# --line=1 --line=5 is equivalent to --line=1,5
|
|
my @lines = split( /,/, join( ',', @{$l} ) );
|
|
|
|
# --line=1-3 is equivalent to --line=1,2,3
|
|
@lines = map {
|
|
my @ret;
|
|
if ( /-/ ) {
|
|
my ($from, $to) = split /-/, $_;
|
|
if ( $from > $to ) {
|
|
App::Ack::warn( "ignoring --line=$from-$to" );
|
|
@ret = ();
|
|
}
|
|
else {
|
|
@ret = ( $from .. $to );
|
|
}
|
|
}
|
|
else {
|
|
@ret = ( $_ );
|
|
};
|
|
@ret
|
|
} @lines;
|
|
|
|
if ( @lines ) {
|
|
my %uniq;
|
|
@uniq{ @lines } = ();
|
|
$opt{lines} = [ sort { $a <=> $b } keys %uniq ]; # numerical sort and each line occurs only once!
|
|
}
|
|
else {
|
|
# happens if there are only ignored --line directives
|
|
App::Ack::die( 'All --line options are invalid.' );
|
|
}
|
|
}
|
|
|
|
return \%opt;
|
|
}
|
|
|
|
|
|
sub def_types_from_ARGV {
|
|
my @typedef;
|
|
|
|
my $parser = Getopt::Long::Parser->new();
|
|
# pass_through => leave unrecognized command line arguments alone
|
|
# no_auto_abbrev => otherwise -c is expanded and not left alone
|
|
$parser->configure( 'no_ignore_case', 'pass_through', 'no_auto_abbrev' );
|
|
$parser->getoptions(
|
|
'type-set=s' => sub { shift; push @typedef, ['c', shift] },
|
|
'type-add=s' => sub { shift; push @typedef, ['a', shift] },
|
|
) or App::Ack::die( 'See ack --help or ack --man for options.' );
|
|
|
|
for my $td (@typedef) {
|
|
my ($type, $ext) = split /=/, $td->[1];
|
|
|
|
if ( $td->[0] eq 'c' ) {
|
|
# type-set
|
|
if ( exists $mappings{$type} ) {
|
|
# can't redefine types 'make', 'skipped', 'text' and 'binary'
|
|
App::Ack::die( qq{--type-set: Builtin type "$type" cannot be changed.} )
|
|
if ref $mappings{$type} ne 'ARRAY';
|
|
|
|
delete_type($type);
|
|
}
|
|
}
|
|
else {
|
|
# type-add
|
|
|
|
# can't append to types 'make', 'skipped', 'text' and 'binary'
|
|
App::Ack::die( qq{--type-add: Builtin type "$type" cannot be changed.} )
|
|
if exists $mappings{$type} && ref $mappings{$type} ne 'ARRAY';
|
|
|
|
App::Ack::warn( qq{--type-add: Type "$type" does not exist, creating with "$ext" ...} )
|
|
unless exists $mappings{$type};
|
|
}
|
|
|
|
my @exts = split /,/, $ext;
|
|
s/^\.// for @exts;
|
|
|
|
if ( !exists $mappings{$type} || ref($mappings{$type}) eq 'ARRAY' ) {
|
|
push @{$mappings{$type}}, @exts;
|
|
for my $e ( @exts ) {
|
|
push @{$types{$e}}, $type;
|
|
}
|
|
}
|
|
else {
|
|
App::Ack::die( qq{Cannot append to type "$type".} );
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
sub delete_type {
|
|
my $type = shift;
|
|
|
|
App::Ack::die( qq{Internal error: Cannot delete builtin type "$type".} )
|
|
unless ref $mappings{$type} eq 'ARRAY';
|
|
|
|
delete $mappings{$type};
|
|
delete $type_wanted{$type};
|
|
for my $ext ( keys %types ) {
|
|
$types{$ext} = [ grep { $_ ne $type } @{$types{$ext}} ];
|
|
}
|
|
}
|
|
|
|
|
|
sub ignoredir_filter {
|
|
return !exists $ignore_dirs{$_} && !exists $ignore_dirs{$File::Next::dir};
|
|
}
|
|
|
|
|
|
sub remove_dir_sep {
|
|
my $path = shift;
|
|
$path =~ s/[$dir_sep_chars]$//;
|
|
|
|
return $path;
|
|
}
|
|
|
|
|
|
use constant TEXT => 'text';
|
|
|
|
sub filetypes {
|
|
my $filename = shift;
|
|
|
|
my $basename = $filename;
|
|
$basename =~ s{.*[$dir_sep_chars]}{};
|
|
|
|
return 'skipped' unless is_searchable( $basename );
|
|
|
|
my $lc_basename = lc $basename;
|
|
return ('make',TEXT) if $lc_basename eq 'makefile' || $lc_basename eq 'gnumakefile';
|
|
return ('rake','ruby',TEXT) if $lc_basename eq 'rakefile';
|
|
|
|
# If there's an extension, look it up
|
|
if ( $filename =~ m{\.([^\.$dir_sep_chars]+)$}o ) {
|
|
my $ref = $types{lc $1};
|
|
return (@{$ref},TEXT) if $ref;
|
|
}
|
|
|
|
# At this point, we can't tell from just the name. Now we have to
|
|
# open it and look inside.
|
|
|
|
return unless -e $filename;
|
|
# From Elliot Shank:
|
|
# I can't see any reason that -r would fail on these-- the ACLs look
|
|
# fine, and no program has any of them open, so the busted Windows
|
|
# file locking model isn't getting in there. If I comment the if
|
|
# statement out, everything works fine
|
|
# So, for cygwin, don't bother trying to check for readability.
|
|
if ( !$is_cygwin ) {
|
|
if ( !-r $filename ) {
|
|
App::Ack::warn( "$filename: Permission denied" );
|
|
return;
|
|
}
|
|
}
|
|
|
|
return 'binary' if -B $filename;
|
|
|
|
# If there's no extension, or we don't recognize it, check the shebang line
|
|
my $fh;
|
|
if ( !open( $fh, '<', $filename ) ) {
|
|
App::Ack::warn( "$filename: $!" );
|
|
return;
|
|
}
|
|
my $header = <$fh>;
|
|
close $fh;
|
|
|
|
if ( $header =~ /^#!/ ) {
|
|
return ($1,TEXT) if $header =~ /\b(ruby|lua|p(?:erl|hp|ython))-?(\d[\d.]*)?\b/;
|
|
return ('shell',TEXT) if $header =~ /\b(?:ba|t?c|k|z)?sh\b/;
|
|
}
|
|
else {
|
|
return ('xml',TEXT) if $header =~ /\Q<?xml /i;
|
|
}
|
|
|
|
return (TEXT);
|
|
}
|
|
|
|
|
|
sub is_searchable {
|
|
my $filename = shift;
|
|
|
|
# If these are updated, update the --help message
|
|
return if $filename =~ /[.]bak$/;
|
|
return if $filename =~ /~$/;
|
|
return if $filename =~ m{^#.*#$}o;
|
|
return if $filename =~ m{^core\.\d+$}o;
|
|
return if $filename =~ m{[._].*\.swp$}o;
|
|
return if $filename =~ /[.-]min\.js$/;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
sub build_regex {
|
|
my $str = shift;
|
|
my $opt = shift;
|
|
|
|
defined $str or App::Ack::die( 'No regular expression found.' );
|
|
|
|
$str = quotemeta( $str ) if $opt->{Q};
|
|
if ( $opt->{w} ) {
|
|
$str = "\\b$str" if $str =~ /^\w/;
|
|
$str = "$str\\b" if $str =~ /\w$/;
|
|
}
|
|
|
|
my $regex_is_lc = $str eq lc $str;
|
|
if ( $opt->{i} || ($opt->{smart_case} && $regex_is_lc) ) {
|
|
$str = "(?i)$str";
|
|
}
|
|
|
|
return $str;
|
|
}
|
|
|
|
|
|
sub check_regex {
|
|
my $regex = shift;
|
|
|
|
return unless defined $regex;
|
|
|
|
eval { qr/$regex/ };
|
|
if ($@) {
|
|
(my $error = $@) =~ s/ at \S+ line \d+.*//;
|
|
chomp($error);
|
|
App::Ack::die( "Invalid regex '$regex':\n $error" );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
|
|
sub warn {
|
|
return CORE::warn( _my_program(), ': ', @_, "\n" );
|
|
}
|
|
|
|
|
|
sub die {
|
|
return CORE::die( _my_program(), ': ', @_, "\n" );
|
|
}
|
|
|
|
sub _my_program {
|
|
require File::Basename;
|
|
return File::Basename::basename( $0 );
|
|
}
|
|
|
|
|
|
|
|
sub filetypes_supported {
|
|
return keys %mappings;
|
|
}
|
|
|
|
sub _get_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 _key {
|
|
my $str = lc shift;
|
|
$str =~ s/[^a-z]//g;
|
|
|
|
return $str;
|
|
}
|
|
|
|
|
|
sub show_help {
|
|
my $help_arg = shift || 0;
|
|
|
|
return show_help_types() if $help_arg =~ /^types?/;
|
|
|
|
my $ignore_dirs = _listify( sort { _key($a) cmp _key($b) } keys %ignore_dirs );
|
|
|
|
App::Ack::print( <<"END_OF_HELP" );
|
|
Usage: ack [OPTION]... PATTERN [FILE]
|
|
|
|
Search for PATTERN in each source file in the tree from cwd on down.
|
|
If [FILES] is specified, then only those files/directories are checked.
|
|
ack may also search STDIN, but only if no FILE are specified, or if
|
|
one of FILES is "-".
|
|
|
|
Default switches may be specified in ACK_OPTIONS environment variable or
|
|
an .ackrc file. If you want no dependency on the environment, turn it
|
|
off with --noenv.
|
|
|
|
Example: ack -i select
|
|
|
|
Searching:
|
|
-i, --ignore-case Ignore case distinctions in PATTERN
|
|
--[no]smart-case Ignore case distinctions in PATTERN,
|
|
only if PATTERN contains no upper case
|
|
Ignored if -i is specified
|
|
-v, --invert-match Invert match: select non-matching lines
|
|
-w, --word-regexp Force PATTERN to match only whole words
|
|
-Q, --literal Quote all metacharacters; PATTERN is literal
|
|
|
|
Search output:
|
|
--line=NUM Only print line(s) NUM of each file
|
|
-l, --files-with-matches
|
|
Only print filenames containing matches
|
|
-L, --files-without-matches
|
|
Only print filenames with no matches
|
|
-o Show only the part of a line matching PATTERN
|
|
(turns off text highlighting)
|
|
--passthru Print all lines, whether matching or not
|
|
--output=expr Output the evaluation of expr for each line
|
|
(turns off text highlighting)
|
|
--match PATTERN Specify PATTERN explicitly.
|
|
-m, --max-count=NUM Stop searching in each file after NUM matches
|
|
-1 Stop searching after one match of any kind
|
|
-H, --with-filename Print the filename for each match
|
|
-h, --no-filename Suppress the prefixing filename on output
|
|
-c, --count Show number of lines matching per file
|
|
--column Show the column number of the first match
|
|
|
|
-A NUM, --after-context=NUM
|
|
Print NUM lines of trailing context after matching
|
|
lines.
|
|
-B NUM, --before-context=NUM
|
|
Print NUM lines of leading context before matching
|
|
lines.
|
|
-C [NUM], --context[=NUM]
|
|
Print NUM lines (default 2) of output context.
|
|
|
|
--print0 Print null byte as separator between filenames,
|
|
only works with -f, -g, -l, -L or -c.
|
|
|
|
File presentation:
|
|
--pager=COMMAND Pipes all ack output through COMMAND. For example,
|
|
--pager="less -R". Ignored if output is redirected.
|
|
--nopager Do not send output through a pager. Cancels any
|
|
setting in ~/.ackrc, ACK_PAGER or ACK_PAGER_COLOR.
|
|
--[no]heading Print a filename heading above each file's results.
|
|
(default: on when used interactively)
|
|
--[no]break Print a break between results from different files.
|
|
(default: on when used interactively)
|
|
--group Same as --heading --break
|
|
--nogroup Same as --noheading --nobreak
|
|
--[no]color Highlight the matching text (default: on unless
|
|
output is redirected, or on Windows)
|
|
--[no]colour Same as --[no]color
|
|
--color-filename=COLOR
|
|
--color-match=COLOR
|
|
--color-lineno=COLOR Set the color for filenames, matches, and line numbers.
|
|
--flush Flush output immediately, even when ack is used
|
|
non-interactively (when output goes to a pipe or
|
|
file).
|
|
|
|
File finding:
|
|
-f Only print the files found, without searching.
|
|
The PATTERN must not be specified.
|
|
-g REGEX Same as -f, but only print files matching REGEX.
|
|
--sort-files Sort the found files lexically.
|
|
--invert-file-match Print/search handle files that do not match -g/-G.
|
|
--show-types Show which types each file has.
|
|
|
|
File inclusion/exclusion:
|
|
-a, --all-types All file types searched;
|
|
Ignores CVS, .svn and other ignored directories
|
|
-u, --unrestricted All files and directories searched
|
|
--[no]ignore-dir=name Add/Remove directory from the list of ignored dirs
|
|
-r, -R, --recurse Recurse into subdirectories (ack's default behavior)
|
|
-n, --no-recurse No descending into subdirectories
|
|
-G REGEX Only search files that match REGEX
|
|
|
|
--perl Include only Perl files.
|
|
--type=perl Include only Perl files.
|
|
--noperl Exclude Perl files.
|
|
--type=noperl Exclude Perl files.
|
|
See "ack --help type" for supported filetypes.
|
|
|
|
--type-set TYPE=.EXTENSION[,.EXT2[,...]]
|
|
Files with the given EXTENSION(s) are recognized as
|
|
being of type TYPE. This replaces an existing
|
|
definition for type TYPE.
|
|
--type-add TYPE=.EXTENSION[,.EXT2[,...]]
|
|
Files with the given EXTENSION(s) are recognized as
|
|
being of (the existing) type TYPE
|
|
|
|
--[no]follow Follow symlinks. Default is off.
|
|
|
|
Directories ignored by default:
|
|
$ignore_dirs
|
|
|
|
Files not checked for type:
|
|
/~\$/ - Unix backup files
|
|
/#.+#\$/ - Emacs swap files
|
|
/[._].*\\.swp\$/ - Vi(m) swap files
|
|
/core\\.\\d+\$/ - core dumps
|
|
/[.-]min\\.js\$/ - Minified javascript files
|
|
|
|
Miscellaneous:
|
|
--noenv Ignore environment variables and ~/.ackrc
|
|
--help This help
|
|
--man Man page
|
|
--version Display version & copyright
|
|
--thpppt Bill the Cat
|
|
|
|
Exit status is 0 if match, 1 if no match.
|
|
|
|
This is version $VERSION of ack.
|
|
END_OF_HELP
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
sub show_help_types {
|
|
App::Ack::print( <<'END_OF_HELP' );
|
|
Usage: ack [OPTION]... PATTERN [FILES]
|
|
|
|
The following is the list of filetypes supported by ack. You can
|
|
specify a file type with the --type=TYPE format, or the --TYPE
|
|
format. For example, both --type=perl and --perl work.
|
|
|
|
Note that some extensions may appear in multiple types. For example,
|
|
.pod files are both Perl and Parrot.
|
|
|
|
END_OF_HELP
|
|
|
|
my @types = filetypes_supported();
|
|
my $maxlen = 0;
|
|
for ( @types ) {
|
|
$maxlen = length if $maxlen < length;
|
|
}
|
|
for my $type ( sort @types ) {
|
|
next if $type =~ /^-/; # Stuff to not show
|
|
my $ext_list = $mappings{$type};
|
|
|
|
if ( ref $ext_list ) {
|
|
$ext_list = join( ' ', map { ".$_" } @{$ext_list} );
|
|
}
|
|
App::Ack::print( sprintf( " --[no]%-*.*s %s\n", $maxlen, $maxlen, $type, $ext_list ) );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
sub _listify {
|
|
my @whats = @_;
|
|
|
|
return '' if !@whats;
|
|
|
|
my $end = pop @whats;
|
|
my $str = @whats ? join( ', ', @whats ) . " and $end" : $end;
|
|
|
|
no warnings 'once';
|
|
require Text::Wrap;
|
|
$Text::Wrap::columns = 75;
|
|
return Text::Wrap::wrap( '', ' ', $str );
|
|
}
|
|
|
|
|
|
sub get_version_statement {
|
|
require Config;
|
|
|
|
my $copyright = get_copyright();
|
|
my $this_perl = $Config::Config{perlpath};
|
|
if ($^O ne 'VMS') {
|
|
my $ext = $Config::Config{_exe};
|
|
$this_perl .= $ext unless $this_perl =~ m/$ext$/i;
|
|
}
|
|
my $ver = sprintf( '%vd', $^V );
|
|
|
|
return <<"END_OF_VERSION";
|
|
ack $VERSION
|
|
Running under Perl $ver at $this_perl
|
|
|
|
$copyright
|
|
|
|
This program is free software. You may modify or distribute it
|
|
under the terms of the Artistic License v2.0.
|
|
END_OF_VERSION
|
|
}
|
|
|
|
|
|
sub print_version_statement {
|
|
App::Ack::print( get_version_statement() );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
sub get_copyright {
|
|
return $COPYRIGHT;
|
|
}
|
|
|
|
|
|
sub load_colors {
|
|
eval 'use Term::ANSIColor ()';
|
|
|
|
$ENV{ACK_COLOR_MATCH} ||= 'black on_yellow';
|
|
$ENV{ACK_COLOR_FILENAME} ||= 'bold green';
|
|
$ENV{ACK_COLOR_LINENO} ||= 'bold yellow';
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
sub is_interesting {
|
|
return if /^\./;
|
|
|
|
my $include;
|
|
|
|
for my $type ( filetypes( $File::Next::name ) ) {
|
|
if ( defined $type_wanted{$type} ) {
|
|
if ( $type_wanted{$type} ) {
|
|
$include = 1;
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $include;
|
|
}
|
|
|
|
|
|
|
|
# print subs added in order to make it easy for a third party
|
|
# module (such as App::Wack) to redefine the display methods
|
|
# and show the results in a different way.
|
|
sub print { print {$fh} @_ }
|
|
sub print_first_filename { App::Ack::print( $_[0], "\n" ) }
|
|
sub print_blank_line { App::Ack::print( "\n" ) }
|
|
sub print_separator { App::Ack::print( "--\n" ) }
|
|
sub print_filename { App::Ack::print( $_[0], $_[1] ) }
|
|
sub print_line_no { App::Ack::print( $_[0], $_[1] ) }
|
|
sub print_column_no { App::Ack::print( $_[0], $_[1] ) }
|
|
sub print_count {
|
|
my $filename = shift;
|
|
my $nmatches = shift;
|
|
my $ors = shift;
|
|
my $count = shift;
|
|
my $show_filename = shift;
|
|
|
|
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;
|
|
|
|
if ($show_filename) {
|
|
App::Ack::print( $filename, ':0', $ors );
|
|
}
|
|
else {
|
|
App::Ack::print( '0', $ors );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
{
|
|
my $filename;
|
|
my $regex;
|
|
my $display_filename;
|
|
|
|
my $keep_context;
|
|
|
|
my $last_output_line; # number of the last line that has been output
|
|
my $any_output; # has there been any output for the current file yet
|
|
my $context_overall_output_count; # has there been any output at all
|
|
|
|
sub search_resource {
|
|
my $res = shift;
|
|
my $opt = shift;
|
|
|
|
$filename = $res->name();
|
|
|
|
my $v = $opt->{v};
|
|
my $passthru = $opt->{passthru};
|
|
my $max = $opt->{m};
|
|
my $nmatches = 0;
|
|
|
|
$display_filename = undef;
|
|
|
|
# for --line processing
|
|
my $has_lines = 0;
|
|
my @lines;
|
|
if ( defined $opt->{lines} ) {
|
|
$has_lines = 1;
|
|
@lines = ( @{$opt->{lines}}, -1 );
|
|
undef $regex; # Don't match when printing matching line
|
|
}
|
|
else {
|
|
$regex = qr/$opt->{regex}/;
|
|
}
|
|
|
|
# for context processing
|
|
$last_output_line = -1;
|
|
$any_output = 0;
|
|
my $before_context = $opt->{before_context};
|
|
my $after_context = $opt->{after_context};
|
|
|
|
$keep_context = ($before_context || $after_context) && !$passthru;
|
|
|
|
my @before;
|
|
my $before_starts_at_line;
|
|
my $after = 0; # number of lines still to print after a match
|
|
|
|
while ( $res->next_text ) {
|
|
# XXX Optimize away the case when there are no more @lines to find.
|
|
# XXX $has_lines, $passthru and $v never change. Optimize.
|
|
if ( $has_lines
|
|
? $. != $lines[0] # $lines[0] should be a scalar
|
|
: $v ? m/$regex/ : !m/$regex/ ) {
|
|
if ( $passthru ) {
|
|
App::Ack::print( $_ );
|
|
next;
|
|
}
|
|
|
|
if ( $keep_context ) {
|
|
if ( $after ) {
|
|
print_match_or_context( $opt, 0, $., $-[0], $+[0], $_ );
|
|
$after--;
|
|
}
|
|
elsif ( $before_context ) {
|
|
if ( @before ) {
|
|
if ( @before >= $before_context ) {
|
|
shift @before;
|
|
++$before_starts_at_line;
|
|
}
|
|
}
|
|
else {
|
|
$before_starts_at_line = $.;
|
|
}
|
|
push @before, $_;
|
|
}
|
|
last if $max && ( $nmatches >= $max ) && !$after;
|
|
}
|
|
next;
|
|
} # not a match
|
|
|
|
++$nmatches;
|
|
|
|
# print an empty line as a divider before first line in each file (not before the first file)
|
|
if ( !$any_output && $opt->{show_filename} && $opt->{break} && defined( $context_overall_output_count ) ) {
|
|
App::Ack::print_blank_line();
|
|
}
|
|
|
|
shift @lines if $has_lines;
|
|
|
|
if ( $res->is_binary ) {
|
|
App::Ack::print( "Binary file $filename matches\n" );
|
|
last;
|
|
}
|
|
if ( $keep_context ) {
|
|
if ( @before ) {
|
|
print_match_or_context( $opt, 0, $before_starts_at_line, $-[0], $+[0], @before );
|
|
@before = ();
|
|
$before_starts_at_line = 0;
|
|
}
|
|
if ( $max && $nmatches > $max ) {
|
|
--$after;
|
|
}
|
|
else {
|
|
$after = $after_context;
|
|
}
|
|
}
|
|
print_match_or_context( $opt, 1, $., $-[0], $+[0], $_ );
|
|
|
|
last if $max && ( $nmatches >= $max ) && !$after;
|
|
} # while
|
|
|
|
return $nmatches;
|
|
} # search_resource()
|
|
|
|
|
|
|
|
sub print_match_or_context {
|
|
my $opt = shift; # opts array
|
|
my $is_match = shift; # is there a match on the line?
|
|
my $line_no = shift;
|
|
my $match_start = shift;
|
|
my $match_end = shift;
|
|
|
|
my $color = $opt->{color};
|
|
my $heading = $opt->{heading};
|
|
my $show_filename = $opt->{show_filename};
|
|
my $show_column = $opt->{column};
|
|
|
|
if ( $show_filename ) {
|
|
if ( not defined $display_filename ) {
|
|
$display_filename =
|
|
$color
|
|
? Term::ANSIColor::colored( $filename, $ENV{ACK_COLOR_FILENAME} )
|
|
: $filename;
|
|
if ( $heading && !$any_output ) {
|
|
App::Ack::print_first_filename($display_filename);
|
|
}
|
|
}
|
|
}
|
|
|
|
# Modified for Atom by Nathan Sobo to print a "\0" instead of a ":" as a separator
|
|
my $sep = $is_match ? "\0" : '-';
|
|
my $output_func = $opt->{output};
|
|
for ( @_ ) {
|
|
if ( $keep_context && !$output_func ) {
|
|
if ( ( $last_output_line != $line_no - 1 ) &&
|
|
( $any_output || ( !$heading && defined( $context_overall_output_count ) ) ) ) {
|
|
App::Ack::print_separator();
|
|
}
|
|
# to ensure separators between different files when --noheading
|
|
|
|
$last_output_line = $line_no;
|
|
}
|
|
|
|
if ( $show_filename ) {
|
|
App::Ack::print_filename($display_filename, $sep) if not $heading;
|
|
my $display_line_no =
|
|
$color
|
|
? Term::ANSIColor::colored( $line_no, $ENV{ACK_COLOR_LINENO} )
|
|
: $line_no;
|
|
App::Ack::print_line_no($display_line_no, $sep);
|
|
}
|
|
|
|
if ( $output_func ) {
|
|
while ( /$regex/go ) {
|
|
App::Ack::print( $output_func->() . "\n" );
|
|
}
|
|
}
|
|
else {
|
|
if ( $color && $is_match && $regex &&
|
|
s/$regex/Term::ANSIColor::colored( substr($_, $-[0], $+[0] - $-[0]), $ENV{ACK_COLOR_MATCH} )/eg ) {
|
|
# At the end of the line reset the color and remove newline
|
|
s/[\r\n]*\z/\e[0m\e[K/;
|
|
}
|
|
else {
|
|
# remove any kind of newline at the end of the line
|
|
s/[\r\n]*\z//;
|
|
}
|
|
if ( $show_column ) {
|
|
App::Ack::print_column_no( $match_start+1, $sep );
|
|
}
|
|
App::Ack::print($_ . "\n");
|
|
}
|
|
$any_output = 1;
|
|
++$context_overall_output_count;
|
|
++$line_no;
|
|
}
|
|
|
|
return;
|
|
} # print_match_or_context()
|
|
|
|
} # 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 $opt = shift;
|
|
|
|
my $nmatches = 0;
|
|
my $count = $opt->{count};
|
|
my $ors = $opt->{print0} ? "\0" : "\n"; # output record separator
|
|
my $show_filename = $opt->{show_filename};
|
|
|
|
my $regex = qr/$opt->{regex}/;
|
|
|
|
if ( $opt->{v} ) {
|
|
while ( $res->next_text ) {
|
|
if ( /$regex/ ) {
|
|
return 0 unless $count;
|
|
}
|
|
else {
|
|
++$nmatches;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
while ( $res->next_text ) {
|
|
if ( /$regex/ ) {
|
|
++$nmatches;
|
|
last unless $count;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( $opt->{show_total} ) {
|
|
$total_count += $nmatches;
|
|
}
|
|
else {
|
|
if ( $nmatches ) {
|
|
App::Ack::print_count( $res->name, $nmatches, $ors, $count, $show_filename );
|
|
}
|
|
elsif ( $count && !$opt->{l} ) {
|
|
App::Ack::print_count0( $res->name, $ors, $show_filename );
|
|
}
|
|
}
|
|
|
|
return $nmatches ? 1 : 0;
|
|
} # search_and_list()
|
|
|
|
} # scope around $total_count
|
|
|
|
|
|
|
|
sub filetypes_supported_set {
|
|
return grep { defined $type_wanted{$_} && ($type_wanted{$_} == 1) } filetypes_supported();
|
|
}
|
|
|
|
|
|
|
|
sub print_files {
|
|
my $iter = shift;
|
|
my $opt = shift;
|
|
|
|
my $ors = $opt->{print0} ? "\0" : "\n";
|
|
|
|
my $nmatches = 0;
|
|
while ( defined ( my $file = $iter->() ) ) {
|
|
App::Ack::print $file, $opt->{show_types} ? " => " . join( ',', filetypes( $file ) ) : (), $ors;
|
|
$nmatches++;
|
|
last if $opt->{1};
|
|
}
|
|
|
|
return $nmatches;
|
|
}
|
|
|
|
|
|
sub print_files_with_matches {
|
|
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 );
|
|
my $res;
|
|
while ( $res = $repo->next_resource() ) {
|
|
$nmatches += search_and_list( $res, $opt );
|
|
$res->close();
|
|
last if $nmatches && $opt->{1};
|
|
}
|
|
$repo->close();
|
|
}
|
|
|
|
if ( $nmatches && $opt->{show_total} ) {
|
|
App::Ack::print_count('', get_total_count(), "\n", 1, 0 )
|
|
}
|
|
|
|
return $nmatches;
|
|
}
|
|
|
|
|
|
sub print_matches {
|
|
my $iter = shift;
|
|
my $opt = shift;
|
|
|
|
$opt->{show_filename} = 0 if $opt->{h};
|
|
$opt->{show_filename} = 1 if $opt->{H};
|
|
|
|
my $nmatches = 0;
|
|
while ( defined ( my $filename = $iter->() ) ) {
|
|
my $repo;
|
|
my $tarballs_work = 0;
|
|
if ( $tarballs_work && $filename =~ /\.tar\.gz$/ ) {
|
|
App::Ack::die( 'Not working here yet' );
|
|
require App::Ack::Repository::Tar; # XXX Error checking
|
|
$repo = App::Ack::Repository::Tar->new( $filename );
|
|
}
|
|
else {
|
|
$repo = App::Ack::Repository::Basic->new( $filename );
|
|
}
|
|
$repo or next;
|
|
|
|
while ( my $res = $repo->next_resource() ) {
|
|
my $needs_line_scan;
|
|
if ( $opt->{regex} && !$opt->{passthru} ) {
|
|
$needs_line_scan = $res->needs_line_scan( $opt );
|
|
if ( $needs_line_scan ) {
|
|
$res->reset();
|
|
}
|
|
}
|
|
else {
|
|
$needs_line_scan = 1;
|
|
}
|
|
if ( $needs_line_scan ) {
|
|
$nmatches += search_resource( $res, $opt );
|
|
}
|
|
$res->close();
|
|
}
|
|
last if $nmatches && $opt->{1};
|
|
$repo->close();
|
|
}
|
|
return $nmatches;
|
|
}
|
|
|
|
|
|
sub filetype_setup {
|
|
my $filetypes_supported_set = filetypes_supported_set();
|
|
# If anyone says --no-whatever, we assume all other types must be on.
|
|
if ( !$filetypes_supported_set ) {
|
|
for my $i ( keys %type_wanted ) {
|
|
$type_wanted{$i} = 1 unless ( defined( $type_wanted{$i} ) || $i eq 'binary' || $i eq 'text' || $i eq 'skipped' );
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
EXPAND_FILENAMES_SCOPE: {
|
|
my $filter;
|
|
|
|
sub expand_filenames {
|
|
my $argv = shift;
|
|
|
|
my $attr;
|
|
my @files;
|
|
|
|
foreach my $pattern ( @{$argv} ) {
|
|
my @results = bsd_glob( $pattern );
|
|
|
|
if (@results == 0) {
|
|
@results = $pattern; # Glob didn't match, pass it thru unchanged
|
|
}
|
|
elsif ( (@results > 1) or ($results[0] ne $pattern) ) {
|
|
if (not defined $filter) {
|
|
eval 'require Win32::File;';
|
|
if ($@) {
|
|
$filter = 0;
|
|
}
|
|
else {
|
|
$filter = Win32::File::HIDDEN()|Win32::File::SYSTEM();
|
|
}
|
|
} # end unless we've tried to load Win32::File
|
|
if ( $filter ) {
|
|
# Filter out hidden and system files:
|
|
@results = grep { not(Win32::File::GetAttributes($_, $attr) and $attr & $filter) } @results;
|
|
App::Ack::warn( "$pattern: Matched only hidden files" ) unless @results;
|
|
} # end if we can filter by file attributes
|
|
} # end elsif this pattern got expanded
|
|
|
|
push @files, @results;
|
|
} # end foreach pattern
|
|
|
|
return \@files;
|
|
} # end expand_filenames
|
|
} # EXPAND_FILENAMES_SCOPE
|
|
|
|
|
|
|
|
sub get_starting_points {
|
|
my $argv = shift;
|
|
my $opt = shift;
|
|
|
|
my @what;
|
|
|
|
if ( @{$argv} ) {
|
|
@what = @{ $is_windows ? expand_filenames($argv) : $argv };
|
|
$_ = File::Next::reslash( $_ ) for @what;
|
|
|
|
# Show filenames unless we've specified one single file
|
|
$opt->{show_filename} = (@what > 1) || (!-f $what[0]);
|
|
}
|
|
else {
|
|
@what = '.'; # Assume current directory
|
|
$opt->{show_filename} = 1;
|
|
}
|
|
|
|
for my $start_point (@what) {
|
|
App::Ack::warn( "$start_point: No such file or directory" ) unless -e $start_point;
|
|
}
|
|
return \@what;
|
|
}
|
|
|
|
sub _match {
|
|
my ( $target, $expression, $invert_flag ) = @_;
|
|
|
|
if ( $invert_flag ) {
|
|
return $target !~ $expression;
|
|
}
|
|
else {
|
|
return $target =~ $expression;
|
|
}
|
|
}
|
|
|
|
|
|
sub get_iterator {
|
|
my $what = shift;
|
|
my $opt = shift;
|
|
|
|
# Starting points are always searched, no matter what
|
|
my %starting_point = map { ($_ => 1) } @{$what};
|
|
|
|
my $g_regex = defined $opt->{G} ? qr/$opt->{G}/ : undef;
|
|
my $file_filter;
|
|
|
|
if ( $g_regex ) {
|
|
$file_filter
|
|
= $opt->{u} ? sub { _match( $File::Next::name, qr/$g_regex/, $opt->{invert_file_match} ) } # XXX Maybe this should be a 1, no?
|
|
: $opt->{all} ? sub { $starting_point{ $File::Next::name } || ( _match( $File::Next::name, qr/$g_regex/, $opt->{invert_file_match} ) && is_searchable( $_ ) ) }
|
|
: sub { $starting_point{ $File::Next::name } || ( _match( $File::Next::name, qr/$g_regex/, $opt->{invert_file_match} ) && is_interesting( @ _) ) }
|
|
;
|
|
}
|
|
else {
|
|
$file_filter
|
|
= $opt->{u} ? sub {1}
|
|
: $opt->{all} ? sub { $starting_point{ $File::Next::name } || is_searchable( $_ ) }
|
|
: sub { $starting_point{ $File::Next::name } || is_interesting( @_ ) }
|
|
;
|
|
}
|
|
|
|
my $descend_filter
|
|
= $opt->{n} ? sub {0}
|
|
: $opt->{u} ? sub {1}
|
|
: \&ignoredir_filter;
|
|
|
|
my $iter =
|
|
File::Next::files( {
|
|
file_filter => $file_filter,
|
|
descend_filter => $descend_filter,
|
|
error_handler => sub { my $msg = shift; App::Ack::warn( $msg ) },
|
|
sort_files => $opt->{sort_files},
|
|
follow_symlinks => $opt->{follow},
|
|
}, @{$what} );
|
|
return $iter;
|
|
}
|
|
|
|
|
|
sub set_up_pager {
|
|
my $command = shift;
|
|
|
|
return if App::Ack::output_to_pipe();
|
|
|
|
my $pager;
|
|
if ( not open( $pager, '|-', $command ) ) {
|
|
App::Ack::die( qq{Unable to pipe to pager "$command": $!} );
|
|
}
|
|
$fh = $pager;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
sub input_from_pipe {
|
|
return $input_from_pipe;
|
|
}
|
|
|
|
|
|
|
|
sub output_to_pipe {
|
|
return $output_to_pipe;
|
|
}
|
|
|
|
|
|
sub exit_from_ack {
|
|
my $nmatches = shift;
|
|
|
|
my $rc = $nmatches ? 0 : 1;
|
|
exit $rc;
|
|
}
|
|
|
|
|
|
|
|
1; # End of App::Ack
|
|
package App::Ack::Repository;
|
|
|
|
|
|
use warnings;
|
|
use strict;
|
|
|
|
sub FAIL {
|
|
require Carp;
|
|
Carp::confess( 'Must be overloaded' );
|
|
}
|
|
|
|
|
|
sub new {
|
|
FAIL();
|
|
}
|
|
|
|
|
|
sub next_resource {
|
|
FAIL();
|
|
}
|
|
|
|
|
|
sub close {
|
|
FAIL();
|
|
}
|
|
|
|
1;
|
|
package App::Ack::Resource;
|
|
|
|
|
|
use warnings;
|
|
use strict;
|
|
|
|
sub FAIL {
|
|
require Carp;
|
|
Carp::confess( 'Must be overloaded' );
|
|
}
|
|
|
|
|
|
sub new {
|
|
FAIL();
|
|
}
|
|
|
|
|
|
sub name {
|
|
FAIL();
|
|
}
|
|
|
|
|
|
sub is_binary {
|
|
FAIL();
|
|
}
|
|
|
|
|
|
|
|
sub needs_line_scan {
|
|
FAIL();
|
|
}
|
|
|
|
|
|
sub reset {
|
|
FAIL();
|
|
}
|
|
|
|
|
|
sub next_text {
|
|
FAIL();
|
|
}
|
|
|
|
|
|
sub close {
|
|
FAIL();
|
|
}
|
|
|
|
1;
|
|
package App::Ack::Plugin::Basic;
|
|
|
|
|
|
|
|
package App::Ack::Resource::Basic;
|
|
|
|
|
|
use warnings;
|
|
use strict;
|
|
|
|
|
|
our @ISA = qw( App::Ack::Resource );
|
|
|
|
|
|
sub new {
|
|
my $class = shift;
|
|
my $filename = shift;
|
|
|
|
my $self = bless {
|
|
filename => $filename,
|
|
fh => undef,
|
|
could_be_binary => undef,
|
|
opened => undef,
|
|
id => undef,
|
|
}, $class;
|
|
|
|
if ( $self->{filename} eq '-' ) {
|
|
$self->{fh} = *STDIN;
|
|
$self->{could_be_binary} = 0;
|
|
}
|
|
else {
|
|
if ( !open( $self->{fh}, '<', $self->{filename} ) ) {
|
|
App::Ack::warn( "$self->{filename}: $!" );
|
|
return;
|
|
}
|
|
$self->{could_be_binary} = 1;
|
|
}
|
|
|
|
return $self;
|
|
}
|
|
|
|
|
|
sub name {
|
|
my $self = shift;
|
|
|
|
return $self->{filename};
|
|
}
|
|
|
|
|
|
sub is_binary {
|
|
my $self = shift;
|
|
|
|
if ( $self->{could_be_binary} ) {
|
|
return -B $self->{filename};
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
sub 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 ( not defined $rc ) {
|
|
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;
|
|
|
|
seek( $self->{fh}, 0, 0 )
|
|
or App::Ack::warn( "$self->{filename}: $!" );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
sub next_text {
|
|
if ( defined ($_ = readline $_[0]->{fh}) ) {
|
|
$. = ++$_[0]->{line};
|
|
return 1;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
sub close {
|
|
my $self = shift;
|
|
|
|
if ( not close $self->{fh} ) {
|
|
App::Ack::warn( $self->name() . ": $!" );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
package App::Ack::Repository::Basic;
|
|
|
|
|
|
our @ISA = qw( App::Ack::Repository );
|
|
|
|
|
|
use warnings;
|
|
use strict;
|
|
|
|
sub new {
|
|
my $class = shift;
|
|
my $filename = shift;
|
|
|
|
my $self = bless {
|
|
filename => $filename,
|
|
nexted => 0,
|
|
}, $class;
|
|
|
|
return $self;
|
|
}
|
|
|
|
|
|
sub next_resource {
|
|
my $self = shift;
|
|
|
|
return if $self->{nexted};
|
|
$self->{nexted} = 1;
|
|
|
|
return App::Ack::Resource::Basic->new( $self->{filename} );
|
|
}
|
|
|
|
|
|
sub close {
|
|
}
|
|
|
|
|
|
|
|
1;
|