mirror of
				https://github.com/KevinMidboe/linguist.git
				synced 2025-10-29 17:50:22 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			2845 lines
		
	
	
		
			78 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			2845 lines
		
	
	
		
			78 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
 | |
| # https://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 and --bar checking out of the way.
 | |
|         /^--th[pt]+t+$/ && App::Ack::_thpppt($_);
 | |
|         /^--bar$/ && App::Ack::_bar();
 | |
| 
 | |
|         # 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<--bar>
 | |
| 
 | |
| Check with the admiral for traps.
 | |
| 
 | |
| =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/that/g' $(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<https://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<https://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-users mailing list
 | |
| 
 | |
| L<http://groups.google.com/group/ack-users>
 | |
| 
 | |
| =item * The ack issues list at Github
 | |
| 
 | |
| L<https://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<https://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
 | |
| Shlomi Fish,
 | |
| Karen Etheridge,
 | |
| Olivier Mengue,
 | |
| 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-2012 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 psgi )],
 | |
|         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 _bar {
 | |
|     my $x;
 | |
|     $x = <<'_BAR';
 | |
|  6?!I'7!I"?%+!
 | |
|  3~!I#7#I"7#I!?!+!="+"="+!:!
 | |
|  2?#I!7!I!?#I!7!I"+"=%+"=#
 | |
|  1?"+!?*+!=#~"=!+#?"="+!
 | |
|  0?"+!?"I"?&+!="~!=!~"=!+%="+"
 | |
|  /I!+!?)+!?!+!=$~!=!~!="+!="+"?!="?!
 | |
|  .?%I"?%+%='?!=#~$="
 | |
|  ,,!?%I"?(+$=$~!=#:"~$:!~!
 | |
|  ,I!?!I!?"I"?!+#?"+!?!+#="~$:!~!:!~!:!,!:!,":#~!
 | |
|  +I!?&+!="+!?#+$=!~":!~!:!~!:!,!:#,!:!,%:"
 | |
|  *+!I!?!+$=!+!=!+!?$+#=!~":!~":#,$:",#:!,!:!
 | |
|  *I!?"+!?!+!=$+!?#+#=#~":$,!:",!:!,&:"
 | |
|  )I!?$=!~!=#+"?!+!=!+!=!~!="~!:!~":!,'.!,%:!~!
 | |
|  (=!?"+!?!=!~$?"+!?!+!=#~"=",!="~$,$.",#.!:!=!
 | |
|  (I"+"="~"=!+&=!~"=!~!,!~!+!=!?!+!?!=!I!?!+"=!.",!.!,":!
 | |
|  %I$?!+!?!=%+!~!+#~!=!~#:#=!~!+!~!=#:!,%.!,!.!:"
 | |
|  $I!?!=!?!I!+!?"+!=!~!=!~!?!I!?!=!+!=!~#:",!~"=!~!:"~!=!:",&:" '-/
 | |
|  $?!+!I!?"+"=!+"~!,!:"+#~#:#,"=!~"=!,!~!,!.",!:".!:! */! !I!t!'!s! !a! !g!r!e!p!!! !/!
 | |
|  $+"=!+!?!+"~!=!:!~!:"I!+!,!~!=!:!~!,!:!,$:!~".&:"~!,# (-/
 | |
|  %~!=!~!=!:!.!+"~!:!,!.!,!~!=!:$.!,":!,!.!:!~!,!:!=!.#="~!,!:" ./!
 | |
|  %=!~!?!+"?"+!=!~",!.!:!?!~!.!:!,!:!,#.!,!:","~!:!=!~!=!:",!~! ./!
 | |
|  %+"~":!~!=#~!:!~!,!.!~!:",!~!=!~!.!:!,!.",!:!,":!=":!.!,!:!7! -/!
 | |
|  %~",!:".#:!=!:!,!:"+!:!~!:!.!,!~!,!.#,!.!,$:"~!,":"~!=! */!
 | |
|  &=!~!=#+!=!~",!.!:",#:#,!.",+:!,!.",!=!+!?!
 | |
|  &~!=!~!=!~!:"~#:",!.!,#~!:!.!+!,!.",$.",$.#,!+!I!?!
 | |
|  &~!="~!:!~":!~",!~!=!~":!,!:!~!,!:!,&.$,#."+!?!I!?!I!
 | |
|  &~!=!~!=!+!,!:!~!:!=!,!:!~&:$,!.!,".!,".!,#."~!+!?$I!
 | |
|  &~!=!~!="~!=!:!~":!,!~%:#,!:",!.!,#.",#I!7"I!?!+!?"I"
 | |
|  &+!I!7!:#~"=!~!:!,!:"~$.!=!.!,!~!,$.#,!~!7!I#?!+!?"I"7!
 | |
|  %7#?!+!~!:!=!~!=!~":!,!:"~":#.!,)7#I"?"I!7&
 | |
|  %7#I!=":!=!~!:"~$:"~!:#,!:!,!:!~!:#,!7#I!?#7)
 | |
|  $7$+!,!~!=#~!:!~!:!~$:#,!.!~!:!=!,":!7#I"?#7+=!?!
 | |
|  $7#I!~!,!~#=!~!:"~!:!,!:!,#:!=!~",":!7$I!?#I!7*+!=!+"
 | |
|  "I!7$I!,":!,!.!=":$,!:!,$:$7$I!+!?"I!7+?"I!7!I!7!,!
 | |
|  !,!7%I!:",!."~":!,&.!,!:!~!I!7$I!+!?"I!7,?!I!7',!
 | |
|  !7(,!.#~":!,%.!,!7%I!7!?#I"7,+!?!7*
 | |
| 7+:!,!~#,"=!7'I!?#I"7/+!7+
 | |
| 77I!+!7!?!7!I"71+!7,
 | |
| _BAR
 | |
| 
 | |
|     $x =~ s/(.)(.)/$1x(ord($2)-32)/eg;
 | |
|     App::Ack::print( $x );
 | |
|     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
 | |
|   --bar                 The warning admiral
 | |
| 
 | |
| 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);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     my $sep = $is_match ? ':' : '-';
 | |
|     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;
 |