From af87be48b6cc85aaf142725acef73c8440bd52a6 Mon Sep 17 00:00:00 2001 From: Jesse Morgan Date: Thu, 12 Apr 2018 16:29:32 -0700 Subject: Add new scripts from laptop --- ack | 5346 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ imap-pass | 21 + md | 5 + 3 files changed, 5372 insertions(+) create mode 100755 ack create mode 100755 imap-pass create mode 100755 md diff --git a/ack b/ack new file mode 100755 index 0000000..80329a4 --- /dev/null +++ b/ack @@ -0,0 +1,5346 @@ +#!/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/beyondgrep/ack2 +# and submit patches against the individual files +# that build ack. +# + +package main; + +use strict; +use warnings; +our $VERSION = '2.22'; # Check https://beyondgrep.com/ for updates + +use 5.008008; +use Getopt::Long 2.38 (); +use Carp 1.04 (); + +use File::Spec (); + + +# XXX Don't make this so brute force +# See also: https://github.com/beyondgrep/ack2/issues/89 + +our $opt_after_context; +our $opt_before_context; +our $opt_output; +our $opt_print0; +our $opt_color; +our $opt_heading; +our $opt_show_filename; +our $opt_regex; +our $opt_break; +our $opt_count; +our $opt_v; +our $opt_m; +our $opt_g; +our $opt_f; +our $opt_lines; +our $opt_L; +our $opt_l; +our $opt_passthru; +our $opt_column; + +# Flag if we need any context tracking. +our $is_tracking_context; + +# These are all our globals. + +MAIN: { + $App::Ack::orig_program_name = $0; + $0 = join(' ', 'ack', $0); + 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 my $arg ( @ARGV ) { + last if ( $arg eq '--' ); + + # Get the --thpppt, --bar, --cathy checking out of the way. + $arg =~ /^--th[pt]+t+$/ and App::Ack::thpppt($arg); + $arg eq '--bar' and App::Ack::ackbar(); + $arg eq '--cathy' and App::Ack::cathy(); + + # See if we want to ignore the environment. (Don't tell Al Gore.) + $arg eq '--env' and $env_is_usable = 1; + $arg eq '--noenv' and $env_is_usable = 0; + } + + if ( !$env_is_usable ) { + my @keys = ( 'ACKRC', grep { /^ACK_/ } keys %ENV ); + delete @ENV{@keys}; + } + load_colors(); + + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); + Getopt::Long::Configure('pass_through', 'no_auto_abbrev'); + Getopt::Long::GetOptions( + 'help' => sub { App::Ack::show_help(); exit; }, + 'version' => sub { App::Ack::print_version_statement(); exit; }, + 'man' => sub { App::Ack::show_man(); exit; }, + ); + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); + + if ( !@ARGV ) { + App::Ack::show_help(); + exit 1; + } + + main(); +} + +sub _compile_descend_filter { + my ( $opt ) = @_; + + my $idirs = 0; + my $dont_ignore_dirs = 0; + + for my $filter (@{$opt->{idirs} || []}) { + if ($filter->is_inverted()) { + $dont_ignore_dirs++; + } + else { + $idirs++; + } + } + + # If we have one or more --noignore-dir directives, we can't ignore + # entire subdirectory hierarchies, so we return an "accept all" + # filter and scrutinize the files more in _compile_file_filter. + return if $dont_ignore_dirs; + return unless $idirs; + + $idirs = $opt->{idirs}; + + return sub { + my $resource = App::Ack::Resource->new($File::Next::dir); + return !grep { $_->filter($resource) } @{$idirs}; + }; +} + +sub _compile_file_filter { + my ( $opt, $start ) = @_; + + my $ifiles_filters = $opt->{ifiles}; + + my $filters = $opt->{'filters'} || []; + my $direct_filters = App::Ack::Filter::Collection->new(); + my $inverse_filters = App::Ack::Filter::Collection->new(); + + foreach my $filter (@{$filters}) { + if ($filter->is_inverted()) { + # We want to check if files match the uninverted filters + $inverse_filters->add($filter->invert()); + } + else { + $direct_filters->add($filter); + } + } + + my %is_member_of_starting_set = map { (get_file_id($_) => 1) } @{$start}; + + my @ignore_dir_filter = @{$opt->{idirs} || []}; + my @is_inverted = map { $_->is_inverted() } @ignore_dir_filter; + # This depends on InverseFilter->invert returning the original filter (for optimization). + @ignore_dir_filter = map { $_->is_inverted() ? $_->invert() : $_ } @ignore_dir_filter; + my $dont_ignore_dir_filter = grep { $_ } @is_inverted; + my $previous_dir = ''; + my $previous_dir_ignore_result; + + return sub { + if ( $opt_g ) { + if ( $File::Next::name =~ /$opt_regex/ && $opt_v ) { + return 0; + } + if ( $File::Next::name !~ /$opt_regex/ && !$opt_v ) { + return 0; + } + } + # ack always selects files that are specified on the command + # line, regardless of filetype. If you want to ack a JPEG, + # and say "ack foo whatever.jpg" it will do it for you. + return 1 if $is_member_of_starting_set{ get_file_id($File::Next::name) }; + + if ( $dont_ignore_dir_filter ) { + if ( $previous_dir eq $File::Next::dir ) { + if ( $previous_dir_ignore_result ) { + return 0; + } + } + else { + my @dirs = File::Spec->splitdir($File::Next::dir); + + my $is_ignoring = 0; + + for ( my $i = 0; $i < @dirs; $i++) { + my $dir_rsrc = App::Ack::Resource->new(File::Spec->catfile(@dirs[0 .. $i])); + + my $j = 0; + for my $filter (@ignore_dir_filter) { + if ( $filter->filter($dir_rsrc) ) { + $is_ignoring = !$is_inverted[$j]; + } + $j++; + } + } + + $previous_dir = $File::Next::dir; + $previous_dir_ignore_result = $is_ignoring; + + if ( $is_ignoring ) { + return 0; + } + } + } + + # Ignore named pipes found in directory searching. Named + # pipes created by subprocesses get specified on the command + # line, so the rule of "always select whatever is on the + # command line" wins. + return 0 if -p $File::Next::name; + + # We can't handle unreadable filenames; report them. + if ( not -r _ ) { + use filetest 'access'; + + if ( not -R $File::Next::name ) { + if ( $App::Ack::report_bad_filenames ) { + App::Ack::warn( "${File::Next::name}: cannot open file for reading" ); + } + return 0; + } + } + + my $resource = App::Ack::Resource->new($File::Next::name); + + if ( $ifiles_filters && $ifiles_filters->filter($resource) ) { + return 0; + } + + my $match_found = $direct_filters->filter($resource); + + # Don't bother invoking inverse filters unless we consider the current resource a match + if ( $match_found && $inverse_filters->filter( $resource ) ) { + $match_found = 0; + } + return $match_found; + }; +} + +sub show_types { + my $resource = shift; + my $ors = shift; + + my @types = filetypes( $resource ); + my $types = join( ',', @types ); + my $arrow = @types ? ' => ' : ' =>'; + App::Ack::print( $resource->name, $arrow, join( ',', @types ), $ors ); + + return; +} + +# Set default colors, load Term::ANSIColor +sub load_colors { + eval 'use Term::ANSIColor 1.10 ()'; + eval 'use Win32::Console::ANSI' if $App::Ack::is_windows; + + $ENV{ACK_COLOR_MATCH} ||= 'black on_yellow'; + $ENV{ACK_COLOR_FILENAME} ||= 'bold green'; + $ENV{ACK_COLOR_LINENO} ||= 'bold yellow'; + + return; +} + +sub filetypes { + my ( $resource ) = @_; + + my @matches; + + foreach my $k (keys %App::Ack::mappings) { + my $filters = $App::Ack::mappings{$k}; + + foreach my $filter (@{$filters}) { + # Clone the resource. + my $clone = $resource->clone; + if ( $filter->filter($clone) ) { + push @matches, $k; + last; + } + } + } + + # http://search.cpan.org/dist/Perl-Critic/lib/Perl/Critic/Policy/Subroutines/ProhibitReturnSort.pm + @matches = sort @matches; + return @matches; +} + +# Returns a (fairly) unique identifier for a file. +# Use this function to compare two files to see if they're +# equal (ie. the same file, but with a different path/links/etc). +sub get_file_id { + my ( $filename ) = @_; + + if ( $App::Ack::is_windows ) { + return File::Next::reslash( $filename ); + } + else { + # XXX Is this the best method? It always hits the FS. + if( my ( $dev, $inode ) = (stat($filename))[0, 1] ) { + return join(':', $dev, $inode); + } + else { + # XXX This could be better. + return $filename; + } + } +} + +# Returns a regex object based on a string and command-line options. +# Dies when the regex $str is undefined (i.e. not given on command line). + +sub build_regex { + my $str = shift; + my $opt = shift; + + defined $str or App::Ack::die( 'No regular expression found.' ); + + $str = quotemeta( $str ) if $opt->{Q}; + if ( $opt->{w} ) { + my $pristine_str = $str; + + $str = "(?:$str)"; + $str = "\\b$str" if $pristine_str =~ /^\w/; + $str = "$str\\b" if $pristine_str =~ /\w$/; + } + + my $regex_is_lc = $str eq lc $str; + if ( $opt->{i} || ($opt->{smart_case} && $regex_is_lc) ) { + $str = "(?i)$str"; + } + + my $re = eval { qr/$str/m }; + if ( !$re ) { + die "Invalid regex '$str':\n $@"; + } + + return $re; + +} + +my $match_column_number; + +{ + +# Number of context lines +my $n_before_ctx_lines; +my $n_after_ctx_lines; + +# Array to keep track of lines that might be required for a "before" context +my @before_context_buf; +# Position to insert next line in @before_context_buf +my $before_context_pos; + +# Number of "after" context lines still pending +my $after_context_pending; + +# Number of latest line that got printed +my $printed_line_no; + +my $is_iterating; + +my $is_first_match; +my $has_printed_something; + +BEGIN { + $has_printed_something = 0; +} + +# Set up context tracking variables. +sub set_up_line_context { + $n_before_ctx_lines = $opt_output ? 0 : ($opt_before_context || 0); + $n_after_ctx_lines = $opt_output ? 0 : ($opt_after_context || 0); + + @before_context_buf = (undef) x $n_before_ctx_lines; + $before_context_pos = 0; + + $is_tracking_context = $n_before_ctx_lines || $n_after_ctx_lines; + + $is_first_match = 1; + + return; +} + +# Adjust context tracking variables when entering a new file. +sub set_up_line_context_for_file { + $printed_line_no = 0; + $after_context_pending = 0; + if ( $opt_heading && !$opt_lines ) { + $is_first_match = 1; + } + + return; +} + +=begin Developers + +This subroutine jumps through a number of optimization hoops to +try to be fast in the more common use cases of ack. For one thing, +in non-context tracking searches (not using -A, -B, or -C), +conditions that normally would be checked inside the loop happen +outside, resulting in three nearly identical loops for -v, --passthru, +and normal searching. Any changes that happen to one should propagate +to the others if they make sense. The non-context branches also inline +does_match for performance reasons; any relevant changes that happen here +must also happen there. + +=end Developers + +=cut + +sub print_matches_in_resource { + my ( $resource ) = @_; + + my $max_count = $opt_m || -1; + my $nmatches = 0; + my $filename = $resource->name; + my $ors = $opt_print0 ? "\0" : "\n"; + + my $has_printed_for_this_resource = 0; + + $is_iterating = 1; + + my $fh = $resource->open(); + if ( !$fh ) { + if ( $App::Ack::report_bad_filenames ) { + App::Ack::warn( "$filename: $!" ); + } + return 0; + } + + my $display_filename = $filename; + if ( $opt_show_filename && $opt_heading && $opt_color ) { + $display_filename = Term::ANSIColor::colored($display_filename, $ENV{ACK_COLOR_FILENAME}); + } + + # Check for context before the main loop, so we don't pay for it if we don't need it. + if ( $is_tracking_context ) { + $after_context_pending = 0; + while ( <$fh> ) { + if ( does_match( $_ ) && $max_count ) { + if ( !$has_printed_for_this_resource ) { + if ( $opt_break && $has_printed_something ) { + App::Ack::print_blank_line(); + } + if ( $opt_show_filename && $opt_heading ) { + App::Ack::print_filename( $display_filename, $ors ); + } + } + print_line_with_context( $filename, $_, $. ); + $has_printed_for_this_resource = 1; + $nmatches++; + $max_count--; + } + elsif ( $opt_passthru ) { + chomp; # XXX Proper newline handling? + # XXX Inline this call? + if ( $opt_break && !$has_printed_for_this_resource && $has_printed_something ) { + App::Ack::print_blank_line(); + } + print_line_with_options( $filename, $_, $., ':' ); + $has_printed_for_this_resource = 1; + } + else { + chomp; # XXX Proper newline handling? + print_line_if_context( $filename, $_, $., '-' ); + } + + last if ($max_count == 0) && ($after_context_pending == 0); + } + } + else { + if ( $opt_passthru ) { + local $_; + + while ( <$fh> ) { + $match_column_number = undef; + if ( $opt_v ? !/$opt_regex/o : /$opt_regex/o ) { + if ( !$opt_v ) { + $match_column_number = $-[0] + 1; + } + if ( !$has_printed_for_this_resource ) { + if ( $opt_break && $has_printed_something ) { + App::Ack::print_blank_line(); + } + if ( $opt_show_filename && $opt_heading ) { + App::Ack::print_filename( $display_filename, $ors ); + } + } + print_line_with_context( $filename, $_, $. ); + $has_printed_for_this_resource = 1; + $nmatches++; + $max_count--; + } + else { + chomp; # XXX proper newline handling? + if ( $opt_break && !$has_printed_for_this_resource && $has_printed_something ) { + App::Ack::print_blank_line(); + } + print_line_with_options( $filename, $_, $., ':' ); + $has_printed_for_this_resource = 1; + } + last unless $max_count != 0; + } + } + elsif ( $opt_v ) { + local $_; + + $match_column_number = undef; + while ( <$fh> ) { + if ( !/$opt_regex/o ) { + if ( !$has_printed_for_this_resource ) { + if ( $opt_break && $has_printed_something ) { + App::Ack::print_blank_line(); + } + if ( $opt_show_filename && $opt_heading ) { + App::Ack::print_filename( $display_filename, $ors ); + } + } + print_line_with_context( $filename, $_, $. ); + $has_printed_for_this_resource = 1; + $nmatches++; + $max_count--; + } + last unless $max_count != 0; + } + } + else { + local $_; + + while ( <$fh> ) { + $match_column_number = undef; + if ( /$opt_regex/o ) { + $match_column_number = $-[0] + 1; + if ( !$has_printed_for_this_resource ) { + if ( $opt_break && $has_printed_something ) { + App::Ack::print_blank_line(); + } + if ( $opt_show_filename && $opt_heading ) { + App::Ack::print_filename( $display_filename, $ors ); + } + } + s/[\r\n]+$//g; + print_line_with_options( $filename, $_, $., ':' ); + $has_printed_for_this_resource = 1; + $nmatches++; + $max_count--; + } + last unless $max_count != 0; + } + } + + } + + $is_iterating = 0; + + return $nmatches; +} + +sub print_line_with_options { + my ( $filename, $line, $line_no, $separator ) = @_; + + $has_printed_something = 1; + $printed_line_no = $line_no; + + my $ors = $opt_print0 ? "\0" : "\n"; + + my @line_parts; + + if( $opt_color ) { + $filename = Term::ANSIColor::colored($filename, + $ENV{ACK_COLOR_FILENAME}); + $line_no = Term::ANSIColor::colored($line_no, + $ENV{ACK_COLOR_LINENO}); + } + + if($opt_show_filename) { + if( $opt_heading ) { + push @line_parts, $line_no; + } + else { + push @line_parts, $filename, $line_no; + } + + if( $opt_column ) { + push @line_parts, get_match_column(); + } + } + if( $opt_output ) { + while ( $line =~ /$opt_regex/og ) { + # XXX We need to stop using eval() for --output. See https://github.com/beyondgrep/ack2/issues/421 + my $output = eval $opt_output; + App::Ack::print( join( $separator, @line_parts, $output ), $ors ); + } + } + else { + if ( $opt_color ) { + # This match is redundant, but we need to perfom it in order to get if capture groups are set. + $line =~ /$opt_regex/o; + + if ( @+ > 1 ) { # If we have captures... + while ( $line =~ /$opt_regex/og ) { + my $offset = 0; # Additional offset for when we add stuff. + my $previous_match_end = 0; + + last if $-[0] == $+[0]; + + for ( my $i = 1; $i < @+; $i++ ) { + my ( $match_start, $match_end ) = ( $-[$i], $+[$i] ); + + next unless defined($match_start); + next if $match_start < $previous_match_end; + + my $substring = substr( $line, + $offset + $match_start, $match_end - $match_start ); + my $substitution = Term::ANSIColor::colored( $substring, + $ENV{ACK_COLOR_MATCH} ); + + substr( $line, $offset + $match_start, + $match_end - $match_start, $substitution ); + + $previous_match_end = $match_end; # Offsets do not need to be applied. + $offset += length( $substitution ) - length( $substring ); + } + + pos($line) = $+[0] + $offset; + } + } + else { + my $matched = 0; # If matched, need to escape afterwards. + + while ( $line =~ /$opt_regex/og ) { + + $matched = 1; + my ( $match_start, $match_end ) = ($-[0], $+[0]); + next unless defined($match_start); + last if $match_start == $match_end; + + my $substring = substr( $line, $match_start, + $match_end - $match_start ); + my $substitution = Term::ANSIColor::colored( $substring, + $ENV{ACK_COLOR_MATCH} ); + + substr( $line, $match_start, $match_end - $match_start, + $substitution ); + + pos($line) = $match_end + + (length( $substitution ) - length( $substring )); + } + # XXX Why do we do this? + $line .= "\033[0m\033[K" if $matched; + } + } + + push @line_parts, $line; + App::Ack::print( join( $separator, @line_parts ), $ors ); + } + + return; +} + +sub iterate { + my ( $resource, $cb ) = @_; + + $is_iterating = 1; + + my $fh = $resource->open(); + if ( !$fh ) { + if ( $App::Ack::report_bad_filenames ) { + App::Ack::warn( $resource->name . ': ' . $! ); + } + return; + } + + # Check for context before the main loop, so we don't pay for it if we don't need it. + if ( $is_tracking_context ) { + $after_context_pending = 0; + + while ( <$fh> ) { + last unless $cb->(); + } + } + else { + local $_; + + while ( <$fh> ) { + last unless $cb->(); + } + } + + $is_iterating = 0; + + return; +} + +sub print_line_with_context { + my ( $filename, $matching_line, $line_no ) = @_; + + my $ors = $opt_print0 ? "\0" : "\n"; + my $is_tracking_context = $opt_after_context || $opt_before_context; + + $matching_line =~ s/[\r\n]+$//g; + + # Check if we need to print context lines first. + if ( $is_tracking_context ) { + my $before_unprinted = $line_no - $printed_line_no - 1; + if ( !$is_first_match && ( !$printed_line_no || $before_unprinted > $n_before_ctx_lines ) ) { + App::Ack::print('--', $ors); + } + + # We want at most $n_before_ctx_lines of context. + if ( $before_unprinted > $n_before_ctx_lines ) { + $before_unprinted = $n_before_ctx_lines; + } + + while ( $before_unprinted > 0 ) { + my $line = $before_context_buf[($before_context_pos - $before_unprinted + $n_before_ctx_lines) % $n_before_ctx_lines]; + + chomp $line; + + # Disable $opt->{column} since there are no matches in the context lines. + local $opt_column = 0; + + print_line_with_options( $filename, $line, $line_no-$before_unprinted, '-' ); + $before_unprinted--; + } + } + + print_line_with_options( $filename, $matching_line, $line_no, ':' ); + + # We want to get the next $n_after_ctx_lines printed. + $after_context_pending = $n_after_ctx_lines; + + $is_first_match = 0; + + return; +} + +# Print the line only if it's part of a context we need to display. +sub print_line_if_context { + my ( $filename, $line, $line_no, $separator ) = @_; + + if ( $after_context_pending ) { + # Disable $opt_column since there are no matches in the context lines. + local $opt_column = 0; + print_line_with_options( $filename, $line, $line_no, $separator ); + --$after_context_pending; + } + elsif ( $n_before_ctx_lines ) { + # Save line for "before" context. + $before_context_buf[$before_context_pos] = $_; + $before_context_pos = ($before_context_pos+1) % $n_before_ctx_lines; + } + + return; +} + +} + +# does_match() MUST have an $opt_regex set. + +=begin Developers + +This subroutine is inlined a few places in print_matches_in_resource +for performance reasons, so any changes here must be copied there as +well. + +=end Developers + +=cut + +sub does_match { + my ( $line ) = @_; + + $match_column_number = undef; + + if ( $opt_v ) { + return ( $line !~ /$opt_regex/o ); + } + else { + if ( $line =~ /$opt_regex/o ) { + # @- = @LAST_MATCH_START + # @+ = @LAST_MATCH_END + $match_column_number = $-[0] + 1; + return 1; + } + else { + return; + } + } +} + +sub get_match_column { + return $match_column_number; +} + +sub resource_has_match { + my ( $resource ) = @_; + + my $has_match = 0; + my $fh = $resource->open(); + if ( !$fh ) { + if ( $App::Ack::report_bad_filenames ) { + App::Ack::warn( $resource->name . ': ' . $! ); + } + } + else { + while ( <$fh> ) { + if (/$opt_regex/o xor $opt_v) { + $has_match = 1; + last; + } + } + close $fh; + } + + return $has_match; +} + +sub count_matches_in_resource { + my ( $resource ) = @_; + + my $nmatches = 0; + my $fh = $resource->open(); + if ( !$fh ) { + if ( $App::Ack::report_bad_filenames ) { + App::Ack::warn( $resource->name . ': ' . $! ); + } + } + else { + while ( <$fh> ) { + ++$nmatches if (/$opt_regex/o xor $opt_v); + } + close $fh; + } + + return $nmatches; +} + +sub main { + my @arg_sources = App::Ack::ConfigLoader::retrieve_arg_sources(); + + my $opt = App::Ack::ConfigLoader::process_args( @arg_sources ); + + $opt_after_context = $opt->{after_context}; + $opt_before_context = $opt->{before_context}; + $opt_output = $opt->{output}; + $opt_print0 = $opt->{print0}; + $opt_color = $opt->{color}; + $opt_heading = $opt->{heading}; + $opt_show_filename = $opt->{show_filename}; + $opt_regex = $opt->{regex}; + $opt_break = $opt->{break}; + $opt_count = $opt->{count}; + $opt_v = $opt->{v}; + $opt_m = $opt->{m}; + $opt_g = $opt->{g}; + $opt_f = $opt->{f}; + $opt_lines = $opt->{lines}; + $opt_L = $opt->{L}; + $opt_l = $opt->{l}; + $opt_passthru = $opt->{passthru}; + $opt_column = $opt->{column}; + + $App::Ack::report_bad_filenames = !$opt->{dont_report_bad_filenames}; + + if ( $opt->{flush} ) { + $| = 1; + } + + if ( !defined($opt_color) && !$opt_g ) { + my $windows_color = 1; + if ( $App::Ack::is_windows ) { + $windows_color = eval { require Win32::Console::ANSI; }; + } + $opt_color = !App::Ack::output_to_pipe() && $windows_color; + } + if ( not defined $opt_heading and not defined $opt_break ) { + $opt_heading = $opt_break = $opt->{break} = !App::Ack::output_to_pipe(); + } + + if ( defined($opt->{H}) || defined($opt->{h}) ) { + $opt_show_filename = $opt->{show_filename} = $opt->{H} && !$opt->{h}; + } + + if ( my $output = $opt_output ) { + $output =~ s{\\}{\\\\}g; + $output =~ s{"}{\\"}g; + $opt_output = qq{"$output"}; + } + + my $resources; + if ( $App::Ack::is_filter_mode && !$opt->{files_from} ) { # probably -x + $resources = App::Ack::Resources->from_stdin( $opt ); + $opt_regex = shift @ARGV if not defined $opt_regex; + $opt_regex = $opt->{regex} = build_regex( $opt_regex, $opt ); + } + else { + if ( $opt_f || $opt_lines ) { + if ( $opt_regex ) { + App::Ack::warn( "regex ($opt_regex) specified with -f or --lines" ); + App::Ack::exit_from_ack( 0 ); # XXX the 0 is misleading + } + } + else { + $opt_regex = shift @ARGV if not defined $opt_regex; + $opt_regex = $opt->{regex} = build_regex( $opt_regex, $opt ); + } + if ( $opt_regex && $opt_regex =~ /\n/ ) { + App::Ack::exit_from_ack( 0 ); + } + my @start; + if ( not defined $opt->{files_from} ) { + @start = @ARGV; + } + if ( !exists($opt->{show_filename}) ) { + unless(@start == 1 && !(-d $start[0])) { + $opt_show_filename = $opt->{show_filename} = 1; + } + } + + if ( defined $opt->{files_from} ) { + $resources = App::Ack::Resources->from_file( $opt, $opt->{files_from} ); + exit 1 unless $resources; + } + else { + @start = ('.') unless @start; + foreach my $target (@start) { + if ( !-e $target && $App::Ack::report_bad_filenames) { + App::Ack::warn( "$target: No such file or directory" ); + } + } + + $opt->{file_filter} = _compile_file_filter($opt, \@start); + $opt->{descend_filter} = _compile_descend_filter($opt); + + $resources = App::Ack::Resources->from_argv( $opt, \@start ); + } + } + App::Ack::set_up_pager( $opt->{pager} ) if defined $opt->{pager}; + + my $ors = $opt_print0 ? "\0" : "\n"; + my $only_first = $opt->{1}; + + my $nmatches = 0; + my $total_count = 0; + + set_up_line_context(); + +RESOURCES: + while ( my $resource = $resources->next ) { + if ($is_tracking_context) { + set_up_line_context_for_file(); + } + + if ( $opt_f ) { + if ( $opt->{show_types} ) { + show_types( $resource, $ors ); + } + else { + App::Ack::print( $resource->name, $ors ); + } + ++$nmatches; + last RESOURCES if defined($opt_m) && $nmatches >= $opt_m; + } + elsif ( $opt_g ) { + if ( $opt->{show_types} ) { + show_types( $resource, $ors ); + } + else { + local $opt_show_filename = 0; # XXX Why is this local? + + print_line_with_options( '', $resource->name, 0, $ors ); + } + ++$nmatches; + last RESOURCES if defined($opt_m) && $nmatches >= $opt_m; + } + elsif ( $opt_lines ) { + my %line_numbers; + foreach my $line ( @{ $opt_lines } ) { + my @lines = split /,/, $line; + @lines = map { + /^(\d+)-(\d+)$/ + ? ( $1 .. $2 ) + : $_ + } @lines; + @line_numbers{@lines} = (1) x @lines; + } + + my $filename = $resource->name; + + local $opt_color = 0; + + iterate( $resource, sub { + chomp; + + if ( $line_numbers{$.} ) { + print_line_with_context( $filename, $_, $. ); + } + elsif ( $opt_passthru ) { + print_line_with_options( $filename, $_, $., ':' ); + } + elsif ( $is_tracking_context ) { + print_line_if_context( $filename, $_, $., '-' ); + } + return 1; + }); + } + elsif ( $opt_count ) { + my $matches_for_this_file = count_matches_in_resource( $resource ); + + if ( not $opt_show_filename ) { + $total_count += $matches_for_this_file; + next RESOURCES; + } + + if ( !$opt_l || $matches_for_this_file > 0) { + if ( $opt_show_filename ) { + App::Ack::print( $resource->name, ':', $matches_for_this_file, $ors ); + } + else { + App::Ack::print( $matches_for_this_file, $ors ); + } + } + } + elsif ( $opt_l || $opt_L ) { + my $is_match = resource_has_match( $resource ); + + if ( $opt_L ? !$is_match : $is_match ) { + App::Ack::print( $resource->name, $ors ); + ++$nmatches; + + last RESOURCES if $only_first; + last RESOURCES if defined($opt_m) && $nmatches >= $opt_m; + } + } + else { + $nmatches += print_matches_in_resource( $resource, $opt ); + if ( $nmatches && $only_first ) { + last RESOURCES; + } + } + } + + if ( $opt_count && !$opt_show_filename ) { + App::Ack::print( $total_count, "\n" ); + } + + close $App::Ack::fh; + App::Ack::exit_from_ack( $nmatches ); +} + +=pod + +=encoding UTF-8 + +=head1 NAME + +ack - grep-like text finder + +=head1 SYNOPSIS + + ack [options] PATTERN [FILE...] + ack -f [options] [DIRECTORY...] + +=head1 DESCRIPTION + +ack is designed as an alternative to F for programmers. + +ack searches the named input files or directories for lines containing a +match to the given PATTERN. By default, ack prints the matching lines. +If no FILE or DIRECTORY is given, the current directory will be searched. + +PATTERN is a Perl regular expression. Perl regular expressions +are commonly found in other programming languages, but for the particulars +of their behavior, please consult +L. If you don't know +how to use regular expression but are interested in learning, you may +consult L. If you do not +need or want ack to use regular expressions, please see the +C<-Q>/C<--literal> option. + +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 + +If files are not specified for searching, either on the command +line or piped in with the C<-x> option, I delves into +subdirectories selecting files for searching. + +I 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 selection, I searches through regular files that +are not explicitly excluded by B<--ignore-dir> and B<--ignore-file> +options, either present in F files or on the command line. + +The default options for I ignore certain files and directories. These +include: + +=over 4 + +=item * Backup files: Files matching F<#*#> or ending with F<~>. + +=item * Coredumps: Files matching F + +=item * Version control directories like F<.svn> and F<.git>. + +=back + +Run I with the C<--dump> option to see what settings are set. + +However, I always searches the files given on the command line, +no matter what type. If you tell I to search in a coredump, +it will search in a coredump. + +=head1 DIRECTORY SELECTION + +I descends through the directory tree of the starting directories +specified. If no directories are specified, the current working directory is +used. 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 +C. + +=head1 WHEN TO USE GREP + +I trumps I as an everyday tool 99% of the time, but don't +throw I 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 syntax should be quicker with I. + +If your script or parent program uses I C<--quiet> or C<--silent> +or needs exit 2 on IO error, use I. + +=head1 OPTIONS + +=over 4 + +=item B<--ackrc> + +Specifies an ackrc file to load after all others; see L. + +=item B<-A I>, B<--after-context=I> + +Print I lines of trailing context after matching lines. + +=item B<-B I>, B<--before-context=I> + +Print I lines of leading context before matching lines. + +=item B<--[no]break> + +Print a break between results from different files. On by default +when used interactively. + +=item B<-C [I]>, B<--context[=I]> + +Print I lines (default 2) of context around matching lines. +You can specify zero lines of context to override another context +specified in an ackrc. + +=item B<-c>, B<--count> + +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<--[no]color>, B<--[no]colour> + +B<--color> highlights the matching text. B<--nocolor> suppresses +the color. This is on by default unless the output is redirected. + +On Windows, this option is off by default unless the +L module is installed or the C +environment variable is used. + +=item B<--color-filename=I> + +Sets the color to be used for filenames. + +=item B<--color-match=I> + +Sets the color to be used for matches. + +=item B<--color-lineno=I> + +Sets the color to be used for line numbers. + +=item B<--[no]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<--create-ackrc> + +Dumps the default ack options to standard output. This is useful for +when you want to customize the defaults. + +=item B<--dump> + +Writes the list of options loaded and where they came from to standard +output. Handy for debugging. + +=item B<--[no]env> + +B<--noenv> disables all environment processing. No F<.ackrc> is +read and all environment variables are ignored. By default, F +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<--files-from=I> + +The list of files to be searched is specified in I. The list of +files are separated by newlines. If I is C<->, the list is loaded +from standard input. + +=item B<--[no]filter> + +Forces ack to act as if it were receiving input via a pipe. + +=item B<--[no]follow> + +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> + +Print searchable files where the relative path + filename matches +I. + +Note that + + ack -g foo + +is exactly the same as + + ack -f | ack foo + +This means that just as ack will not search, for example, F<.jpg> +files, C<-g> will not list F<.jpg> files either. ack is not intended +to be a general-purpose file finder. + +Note also that if you have C<-i> in your .ackrc that the filenames +to be matched will be case-insensitive as well. + +This option can be combined with B<--color> to make it easier to +spot the match. + +=item B<--[no]group> + +B<--group> groups matches by file name. 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. This is the default unless searching +a single explicitly specified file. + +=item B<-h>, B<--no-filename> + +Suppress the prefixing of filenames on output when multiple files are +searched. + +=item B<--[no]heading> + +Print a filename heading above each file's results. This is the default +when used interactively. + +=item B<--help>, B<-?> + +Print a short help statement. + +=item B<--help-types>, B<--help=types> + +Print all known types. + +=item B<-i>, B<--ignore-case> + +Ignore case distinctions in PATTERN + +=item B<--ignore-ack-defaults> + +Tells ack to completely ignore the default definitions provided with ack. +This is useful in combination with B<--create-ackrc> if you I want +to customize ack. + +=item B<--[no]ignore-dir=I>, B<--[no]ignore-directory=I> + +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 must always be a simple directory name. Nested +directories like F 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<--ignore-file=I> + +Ignore files matching I. The filters are specified +identically to file type filters as seen in L. + +=item B<-k>, B<--known-types> + +Limit selected files to those with types that ack knows about. This is +equivalent to the default behavior found in ack 1. + +=item B<--lines=I> + +Only print line I of each file. Multiple lines can be given with multiple +B<--lines> options or as a comma separated list (B<--lines=3,5,7>). B<--lines=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 match. + +=item B<--match I> + +Specify the I 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>, B<--max-count=I> + +Stop reading a file after I 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> + +Output the evaluation of I for each line (turns off text +highlighting) +If PATTERN matches more than once then a line is output for each non-overlapping match. +For more information please see the section L">. + +=item B<--pager=I>, B<--nopager> + +B<--pager> directs ack's output through I. This can also be specified +via the C and C environment variables. + +Using --pager does not suppress grouping and coloring like piping +output on the command-line does. + +B<--nopager> cancels any setting in ~/.ackrc, C or C. +No output will be sent through a pager. + +=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. + +=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<-s> + +Suppress error messages about nonexistent or unreadable files. This is taken +from fgrep. + +=item B<--[no]smart-case>, B<--no-smart-case> + +Ignore case in the search strings if PATTERN contains no uppercase +characters. This is similar to C in vim. This option is +off by default, and ignored if C<-i> is specified. + +B<-i> always overrides this option. + +=item B<--sort-files> + +Sorts the found files lexicographically. Use this if you want your file +listings to be deterministic between runs of I. + +=item B<--show-types> + +Outputs the filetypes that ack associates with each file. + +Works with B<-f> and B<-g> options. + +=item B<--type=[no]TYPE> + +Specify the types of files to include or exclude from a search. +TYPE is a filetype, like I or I. 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 for a list of valid types. + +=item B<--type-add I:I:I> + +Files with the given FILTERARGS applied to the given FILTER +are recognized as being of (the existing) type TYPE. +See also L. + + +=item B<--type-set I:I:I> + +Files with the given FILTERARGS applied to the given FILTER are recognized as +being of type TYPE. This replaces an existing definition for type TYPE. See +also L. + +=item B<--type-del I> + +The filters associated with TYPE are removed from Ack, and are no longer considered +for searches. + +=item B<-v>, B<--invert-match> + +Invert match: select non-matching lines + +=item B<--version> + +Display version and copyright information. + +=item B<-w>, B<--word-regexp> + +=item B<-w>, B<--word-regexp> + +Turn on "words mode". This sometimes matches a whole word, but the +semantics is quite subtle. If the passed regexp begins with a word +character, then a word boundary is required before the match. If the +passed regexp ends with a word character, or with a word character +followed by newline, then a word boundary is required after the match. + +Thus, for example, B<-w> with the regular expression C will not +match the strings C or C. However, if the regular +expression is C<(ox|ass)> then it will match those strings. Because +the regular expression's first character is C<(>, the B<-w> flag has +no effect at the start, and because the last character is C<)>, it has +no effect at the end. + +Force PATTERN to match only whole words. The PATTERN is wrapped with +C<\b> metacharacters. + +=item B<-x> + +An abbreviation for B<--files-from=->; the list of files to search are read +from standard input, with one line per file. + +=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. + +=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<--cathy> + +Chocolate, Chocolate, Chocolate! + +=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 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 +in the F<.ackrc> file is interpreted as one element of C<@ARGV>. + +F looks in several locations for F<.ackrc> files; the searching +process is detailed in L. These +files are not considered if B<--noenv> is specified on the command line. + +=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. + +File types can be specified both with the the I<--type=xxx> option, +or the file type as an option itself. For example, if you create +a filetype of "cobol", you can specify I<--type=cobol> or simply +I<--cobol>. File types must be at least two characters long. This +is why the C language is I<--cc> and the R language is I<--rr>. + +I searches for foo in all perl files. I +tells you, that perl files are files ending +in .pl, .pm, .pod or .t. So what if you would like to include .xs +files as well when searching for --perl files? I +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 defines +the type I to include files with +the extensions .e or .eiffel. So to search for all eiffel files +containing the word Bertrand use I. +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 +and I<.xs> files no longer belong to the type I. + +When defining your own types in the F<.ackrc> file you have to use +the following: + + --type-set=eiffel:ext:e,eiffel + +or writing on separate lines + + --type-set + eiffel:ext:e,eiffel + +The following does B work in the F<.ackrc> file: + + --type-set eiffel:ext:e,eiffel + +In order to see all currently defined types, use I<--help-types>, e.g. +I + +In addition to filtering based on extension (like ack 1.x allowed), ack 2 +offers additional filter types. The generic syntax is +I<--type-set TYPE:FILTER:FILTERARGS>; I depends on the value +of I. + +=over 4 + +=item is:I + +I filters match the target filename exactly. It takes exactly one +argument, which is the name of the file to match. + +Example: + + --type-set make:is:Makefile + +=item ext:I[,I[,...]] + +I filters match the extension of the target file against a list +of extensions. No leading dot is needed for the extensions. + +Example: + + --type-set perl:ext:pl,pm,t + +=item match:I + +I filters match the target filename against a regular expression. +The regular expression is made case insensitive for the search. + +Example: + + --type-set make:match:/(gnu)?makefile/ + +=item firstlinematch:I + +I matches the first line of the target file against a +regular expression. Like I, the regular expression is made +case insensitive. + +Example: + + --type-add perl:firstlinematch:/perl/ + +=back + +More filter types may be made available in the future. + +=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 user's F<.ackrc> file. If this file doesn't +exist, F 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 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 for the color specifications. + +=item ACK_PAGER + +Specifies a pager program, such as C, C or C, to which +ack will send its output. + +Using C does not suppress grouping and coloring like +piping output on the command-line does, except that on Windows +ack will assume that C does not support color. + +C overrides C if both are specified. + +=item ACK_PAGER_COLOR + +Specifies a pager program that understands ANSI color sequences. +Using C 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. + +=back + +=head1 AVAILABLE COLORS + +F uses the colors available in Perl's L module, which +provides the following listed values. Note that case does not matter when using +these values. + +=head2 Foreground colors + + black red green yellow blue magenta cyan white + + bright_black bright_red bright_green bright_yellow + bright_blue bright_magenta bright_cyan bright_white + +=head2 Background colors + + on_black on_red on_green on_yellow + on_blue on_magenta on_cyan on_white + + on_bright_black on_bright_red on_bright_green on_bright_yellow + on_bright_blue on_bright_magenta on_bright_cyan on_bright_white + +=head1 ACK & OTHER TOOLS + +=head2 Simple vim integration + +F integrates easily with the Vim text editor. Set this in your +F<.vimrc> to use F instead of F: + + set grepprg=ack\ -k + +That example uses C<-k> to search through only files of the types ack +knows about, but you may use other default flags. Now you can search +with F and easily step through the results in Vim: + + :grep Dumper perllib + +=head2 Editor integration + +Many users have integrated ack into their preferred text editors. +For details and links, see L. + +=head2 Shell and Return Code + +For greater compatibility with I, I 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 or backticks.) + +The I 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 have been selected + +Ack's B<-f> was originally added as a debugging tool. If ack is +not finding matches you think it should find, run F to see +what files have been selected. You can also add the C<--show-types> +options to show the type of each file selected. + +=head2 Use B<--dump> + +This lists the ackrc files that are loaded and the options loaded +from them. +So for example you can find a list of directories that do not get searched or where filetypes are defined. + +=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 will create a +list of all the Perl files in a tree, ideal for sending into F. +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, 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 Examples of F<--output> + +Following variables are useful in the expansion string: + +=over 4 + +=item C<$&> + +The whole string matched by PATTERN. + +=item C<$1>, C<$2>, ... + +The contents of the 1st, 2nd ... bracketed group in PATTERN. + +=item C<$`> + +The string before the match. + +=item C<$'> + +The string after the match. + +=back + +For more details and other variables see +L. + +This example shows how to add text around a particular pattern +(in this case adding _ around word with "e") + + ack2.pl "\w*e\w*" quick.txt --output="$`_$&_$'" + _The_ quick brown fox jumps over the lazy dog + The quick brown fox jumps _over_ the lazy dog + The quick brown fox jumps over _the_ lazy dog + +This shows how to pick out particular parts of a match using ( ) within regular expression. + + ack '=head(\d+)\s+(.*)' --output=' $1 : $2' + input file contains "=head1 NAME" + output "1 : NAME" + +=head1 COMMUNITY + +There are ack mailing lists and a Slack channel for ack. See +L for details. + +=head1 FAQ + +=head2 Why isn't ack finding a match in (some file)? + +First, take a look and see if ack is even looking at the file. ack is +intelligent in what files it will search and which ones it won't, but +sometimes that can be surprising. + +Use the C<-f> switch, with no regex, to see a list of files that ack +will search for you. If your file doesn't show up in the list of files +that C shows, then ack never looks in it. + +NOTE: If you're using an old ack before 2.0, it's probably because it's of +a type that ack doesn't recognize. In ack 1.x, the searching behavior is +driven by filetype. B You can use the C<--show-types> switch to show +which type ack thinks each file is. + +=head2 Wouldn't it be great if F did search & replace? + +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 I make ack recognize F<.xyz> files? + +Yes! Please see L. If you think +that F should recognize a type by default, please see +L. + +=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 that points to F +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 or as root: + + ln -s /usr/bin/ack-grep /usr/bin/ack + +Alternatively, you could use a shell alias: + + # bash/zsh + alias ack=ack-grep + + # csh + alias ack ack-grep + +=head2 What does F 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. + +=head2 Why is ack telling me I have an invalid option when searching for C<+foo>? + +ack treats command line options beginning with C<+> or C<-> as options; if you +would like to search for these, you may prefix your search term with C<--> or +use the C<--match> option. (However, don't forget that C<+> is a regular +expression metacharacter!) + +=head2 Why does C<"ack '.{40000,}'"> fail? Isn't that a valid regex? + +The Perl language limits the repetition quantifier to 32K. You +can search for C<.{32767}> but not C<.{32768}>. + +=head2 Ack does "X" and shouldn't, should it? + +We try to remain as close to grep's behavior as possible, so when in doubt, +see what grep does! If there's a mismatch in functionality there, please +bring it up on the ack-users mailing list. + +=head1 ACKRC LOCATION SEMANTICS + +Ack can load its configuration from many sources. The following list +specifies the sources Ack looks for configuration files; each one +that is found is loaded in the order specified here, and +each one overrides options set in any of the sources preceding +it. (For example, if I set --sort-files in my user ackrc, and +--nosort-files on the command line, the command line takes +precedence) + +=over 4 + +=item * + +Defaults are loaded from App::Ack::ConfigDefaults. This can be omitted +using C<--ignore-ack-defaults>. + +=item * Global ackrc + +Options are then loaded from the global ackrc. This is located at +C on Unix-like systems. + +Under Windows XP and earlier, the global ackrc is at +C + +Under Windows Vista/7, the global ackrc is at +C + +The C<--noenv> option prevents all ackrc files from being loaded. + +=item * User ackrc + +Options are then loaded from the user's ackrc. This is located at +C<$HOME/.ackrc> on Unix-like systems. + +Under Windows XP and earlier, the user's ackrc is at +C. + +Under Windows Vista/7, the user's ackrc is at +C. + +If you want to load a different user-level ackrc, it may be specified +with the C<$ACKRC> environment variable. + +The C<--noenv> option prevents all ackrc files from being loaded. + +=item * Project ackrc + +Options are then loaded from the project ackrc. The project ackrc is +the first ackrc file with the name C<.ackrc> or C<_ackrc>, first searching +in the current directory, then the parent directory, then the grandparent +directory, etc. This can be omitted using C<--noenv>. + +=item * --ackrc + +The C<--ackrc> option may be included on the command line to specify an +ackrc file that can override all others. It is consulted even if C<--noenv> +is present. + +=item * ACK_OPTIONS + +Options are then loaded from the environment variable C. This can +be omitted using C<--noenv>. + +=item * Command line + +Options are then loaded from the command line. + +=back + +=head1 DIFFERENCES BETWEEN ACK 1.X AND ACK 2.X + +A lot of changes were made for ack 2; here is a list of them. + +=head2 GENERAL CHANGES + +=over 4 + +=item * + +When no selectors are specified, ack 1.x only searches through files that +it can map to a file type. ack 2.x, by contrast, will search through +every regular, non-binary file that is not explicitly ignored via +B<--ignore-file> or B<--ignore-dir>. This is similar to the behavior of the +B<-a/--all> option in ack 1.x. + +=item * + +A more flexible filter system has been added, so that more powerful file types +may be created by the user. For details, please consult +L. + +=item * + +ack now loads multiple ackrc files; see L for +details. + +=item * + +ack's default filter definitions aren't special; you may tell ack to +completely disregard them if you don't like them. + +=back + +=head2 REMOVED OPTIONS + +=over 4 + +=item * + +Because of the change in default search behavior, the B<-a/--all> and +B<-u/--unrestricted> options have been removed. In addition, the +B<-k/--known-types> option was added to cause ack to behave with +the default search behavior of ack 1.x. + +=item * + +The B<-G> option has been removed. Two regular expressions on the +command line was considered too confusing; to simulate B<-G>'s functionality, +you may use the new B<-x> option to pipe filenames from one invocation of +ack into another. + +=item * + +The B<--binary> option has been removed. + +=item * + +The B<--skipped> option has been removed. + +=item * + +The B<--text> option has been removed. + +=item * + +The B<--invert-file-match> option has been removed. Instead, you may +use B<-v> with B<-g>. + +=back + +=head2 CHANGED OPTIONS + +=over 4 + +=item * + +The options that modify the regular expression's behavior (B<-i>, B<-w>, +B<-Q>, and B<-v>) may now be used with B<-g>. + +=back + +=head2 ADDED OPTIONS + +=over 4 + +=item * + +B<--files-from> was added so that a user may submit a list of filenames as +a list of files to search. + +=item * + +B<-x> was added to tell ack to accept a list of filenames via standard input; +this list is the list of filenames that will be used for the search. + +=item * + +B<-s> was added to tell ack to suppress error messages about non-existent or +unreadable files. + +=item * + +B<--ignore-directory> and B<--noignore-directory> were added as aliases for +B<--ignore-dir> and B<--noignore-dir> respectively. + +=item * + +B<--ignore-file> was added so that users may specify patterns of files to +ignore (ex. /.*~$/). + +=item * + +B<--dump> was added to allow users to easily find out which options are +set where. + +=item * + +B<--create-ackrc> was added so that users may create custom ackrc files based +on the default settings loaded by ack, and so that users may easily view those +defaults. + +=item * + +B<--type-del> was added to selectively remove file type definitions. + +=item * + +B<--ignore-ack-defaults> was added so that users may ignore ack's default +options in favor of their own. + +=item * + +B<--bar> was added so ack users may consult Admiral Ackbar. + +=back + +=head1 AUTHOR + +Andy Lester, C<< >> + +=head1 BUGS + +Please report any bugs or feature requests to the issues list at +Github: L + +=head1 ENHANCEMENTS + +All enhancement requests MUST first be posted to the ack-users +mailing list at L. 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 in the ack +issues list at Github: L + +Patches are always welcome, but patches with tests get the most +attention. + +=head1 SUPPORT + +Support for and information about F can be found at: + +=over 4 + +=item * The ack homepage + +L + +=item * The ack-users mailing list + +L + +=item * The ack issues list at Github + +L + +=item * AnnoCPAN: Annotated CPAN documentation + +L + +=item * CPAN Ratings + +L + +=item * Search CPAN + +L + +=item * MetaCPAN + +L + +=item * Git source repository + +L + +=back + +=head1 ACKNOWLEDGEMENTS + +How appropriate to have Inowledgements! + +Thanks to everyone who has contributed to ack in any way, including +Michele Campeotto, +H.Merijn Brand, +Duke Leto, +Gerhard Poul, +Ethan Mallove, +Marek Kubica, +Ray Donnelly, +Nikolaj Schumacher, +Ed Avis, +Nick Morrott, +Austin Chamberlin, +Varadinsky, +SEbastien FeugEre, +Jakub Wilk, +Pete Houston, +Stephen Thirlwall, +Jonah Bishop, +Chris Rebert, +Denis Howe, +RaEl GundEn, +James McCoy, +Daniel Perrett, +Steven Lee, +Jonathan Perret, +Fraser Tweedale, +RaEl GundEn, +Steffen Jaeckel, +Stephan Hohe, +Michael Beijen, +Alexandr Ciornii, +Christian Walde, +Charles Lee, +Joe McMahon, +John Warwick, +David Steinbrunner, +Kara Martens, +Volodymyr Medvid, +Ron Savage, +Konrad Borowski, +Dale Sedivic, +Michael McClimon, +Andrew Black, +Ralph Bodenner, +Shaun Patterson, +Ryan Olson, +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, +GEbor SzabE, +Tod Hagan, +Michael Hendricks, +Evar ArnfjErE 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 BjErn Hansen, +Jerry Gay, +Will Coleda, +Mike O'Regan, +Slaven ReziE<0x107>, +Mark Stosberg, +David Alan Pisoni, +Adriano Ferreira, +James Keenan, +Leland Johnson, +Ricardo Signes, +Pete Krawczyk and +Rob Hoelz. + +=head1 COPYRIGHT & LICENSE + +Copyright 2005-2017 Andy Lester. + +This program is free software; you can redistribute it and/or modify +it under the terms of the Artistic License v2.0. + +See http://www.perlfoundation.org/artistic_license_2_0 or the LICENSE.md +file that comes with the ack distribution. + +=cut +package App::Ack; + +use warnings; +use strict; + + +our $VERSION; +our $COPYRIGHT; +BEGIN { + $VERSION = '2.22'; + $COPYRIGHT = 'Copyright 2005-2017 Andy Lester.'; +} + +our $fh; + +BEGIN { + $fh = *STDOUT; +} + + +our %types; +our %type_wanted; +our %mappings; +our %ignore_dirs; + +our $is_filter_mode; +our $output_to_pipe; + +our $dir_sep_chars; +our $is_cygwin; +our $is_windows; + +use File::Spec 1.00015 (); + +BEGIN { + # These have to be checked before any filehandle diddling. + $output_to_pipe = not -t *STDOUT; + $is_filter_mode = -p STDIN; + + $is_cygwin = ($^O eq 'cygwin' || $^O eq 'msys'); + $is_windows = ($^O eq 'MSWin32'); + $dir_sep_chars = $is_windows ? quotemeta( '\\/' ) : quotemeta( File::Spec->catfile( '', '' ) ); +} + + + +sub remove_dir_sep { + my $path = shift; + $path =~ s/[$dir_sep_chars]$//; + + return $path; +} + + + +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 thpppt { + my $y = q{_ /|,\\'!.x',=(www)=, U }; + $y =~ tr/,x!w/\nOo_/; + + App::Ack::print( "$y ack $_[0]!\n" ); + exit 0; +} + +sub ackbar { + 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 + + return _pic_decode($x); +} + +sub cathy { + my $x = <<'CATHY'; + 0+!--+! + 0|! "C!H!O!C!O!L!A!T!E!!! !|! + 0|! "C!H!O!C!O!L!A!T!E!!! !|! + 0|! "C!H!O!C!O!L!A!T!E!!! !|! + 0|! $A"C!K!!! $|! + 0+!--+! + 6\! 1:!,!.! ! + 7\! /.!M!~!Z!M!~! + 8\! /~!D! "M! ! + 4.! $\! /M!~!.!8! +.!M# 4 + 0,!.! (\! .~!M!N! ,+!I!.!M!.! 3 + /?!O!.!M!:! '\! .O!.! +~!Z!=!N!.! 4 + ..! !D!Z!.!Z!.! '\! 9=!M".! 6 + /.! !.!~!M".! '\! 8~! 9 + 4M!.! /.!7!N!M!.! F + 4.! &:!M! !N"M# !M"N!M! #D!M&=! = + :M!7!M#:! !~!M!7!,!$!M!:! #.! !O!N!.!M!:!M# ; + 8Z!M"~!N!$!D!.!N!?! !I!N!.! (?!M! !M!,!D!M".! 9 + (?!Z!M!N!:! )=!M!O!8!.!M!+!M! !M!,! !O!M! +,!M!.!M!~!Z!N!M!:! &:!~! 0 + &8!7!.!~!M"D!M!,! &M!?!=!8! !M!,!O! !M!+! !+!O!.!M! $M#~! !.!8!M!Z!.!M! !O!M"Z! %:!~!M!Z!M!Z!.! + + &:!M!7!,! *M!.!Z!M! !8"M!.!M!~! !.!M!.!=! #~!8!.!M! !7!M! "N!Z#I! !D!M!,!M!.! $."M!,! !M!.! * + 2$!O! "N! !.!M!I! !7" "M! "+!O! !~!M! !d!O!.!7!I!M!.! !.!O!=!M!.! !M",!M!.! %.!$!O!D! + + 1~!O! "M!+! !8!$! "M! "?!O! %Z!8!D!M!?!8!I!O!7!M! #M!.!M! "M",!M! 4 + 07!~! ".!8! !.!M! "I!+! !.!M! &Z!D!.!7!=!M! !:!.!M! #:!8"+! !.!+!8! !8! 3 + /~!M! #N! !~!M!$! !.!M! !.!M" &~!M! "~!M!O! "D! $M! !8! "M!,!M!+!D!.! 1 + #.! #?!M!N!.! #~!O! $M!.!7!$! "?" !?!~!M! '7!8!?!M!.!+!M"O! $?"$!D! !.!O! !$!7!I!.! 0 + $,!M!:!O!?! ".! !?!=! $=!:!O! !M! "M! !M! !+!$! (.! +.!M! !M!.! !8! !+"Z!~! $:!M!$! !.! ' + #.!8!.!I!$! $7!I! %M" !=!M! !~!M!D! "7!I! .I!O! %?!=!,!D! !,!M! !D!~!8!~! %D!M! ( + #.!M"?! $=!O! %=!N! "8!.! !Z!M! #M!~! (M!:! #.!M" &O! !M!.! !?!,! !8!.!N!~! $8!N!M!,!.! % + *$!O! &M!,! "O! !.!M!.! #M! (~!M( &O!.! !7! "M! !.!M!.!M!,! #.!M! !M! & + )=!8!.! $.!M!O!.! "$!.!I!N! !I!M# (7!M(I! %D"Z!M! "=!I! "M! !M!:! #~!D! ' + )D! &8!N!:! ".!O! !M!="M! "M! (7!M) %." !M!D!."M!.! !$!=! !M!,! + + (M! &+!.!M! #Z!7!O!M!.!~!8! +,!M#D!?!M#D! #.!Z!M#,!Z!?! !~!N! "N!.! !M! + + 'D!:! %$!D! !?! #M!Z! !8!.! !M"?!7!?!7! '+!I!D! !?!O!:!M!:! ":!M!:! !M!7".!M! "8!+! !:!D! !.!M! * + %.!O!:! $.!O!+! !D!.! #M! "M!.!+!N!I!Z! "7!M!N!M!N!?!I!7!Z!=!M'D"~! #M!.!8!$! !:! !.!M! "N!?! !,!O! ) + !.!?!M!:!M!I! %8!,! "M!.! #M! "N! !M!.! !M!.! !+!~! !.!M!.! ':!M! $M! $M!Z!$! !M!.! "D! "M! "?!M! ( + !7!8! !+!I! ".! "$!=! ":!$! "+! !M!.! !O! !M!I!M".! !=!~! ",!O! '=!M! $$!,! #N!:! ":!8!.! !D!~! !,!M!.! !:!M!.! & + !:!,!.! &Z" #D! !.!8!."M!.! !8!?!Z!M!.!M! #Z!~! !?!M!Z!.! %~!O!.!8!$!N!8!O!I!:!~! !+! #M!.! !.!M!.! !+!M! ".!~!M!+! $ + !.! 'D!I! #?!M!.!M!,! !.!Z! !.!8! #M&O!I!?! (~!I!M"." !M!Z!.! !M!N!.! "+!$!.! "M!.! !M!?!.! "8!M! $ + (O!8! $M! !M!.! ".!:! !+!=! #M! #.!M! !+" *$!M":!.! !M!~! "M!7! #M! #7!Z! "M"$!M!.! !.! # + '$!Z! #.!7!+!M! $.!,! !+!:! #N! #.!M!.!+!M! +D!M! #=!N! ":!O! #=!M! #Z!D! $M!I! % + $,! ".! $.!M" %$!.! !?!~! "+!7!." !.!M!,! !M! *,!N!M!.$M!?! "D!,! #M!.! #N! + + ,M!Z! &M! "I!,! "M! %I!M! !?!=!.! (Z!8!M! $:!M!.! !,!M! $D! #.!M!.! ) + +8!O! &.!8! "I!,! !~!M! &N!M! !M!D! '?!N!O!." $?!7! "?!~! #M!.! #I!D!.! ( + 3M!,! "N!.! !D" &.!+!M!.! !M":!.":!M!7!M!D! 'M!.! "M!.! "M!,! $I! ) + 3I! #M! "M!,! !:! &.!M" ".!,! !.!$!M!I! #.! !:! !.!M!?! "N!+! ".! / + 1M!,! #.!M!8!M!=!.! +~!N"O!Z"~! *+!M!.! "M! 2 + 0.!M! &M!.! 8:! %.!M!Z! "M!=! *O!,! % + 0?!$! &N! )." .,! %."M! ":!M!.! 0 + 0N!:! %?!O! #.! ..! &,! &.!D!,! "N!I! 0 +CATHY + return _pic_decode($x); +} + +sub _pic_decode { + my($compressed) = @_; + $compressed =~ s/(.)(.)/$1x(ord($2)-32)/eg; + App::Ack::print( $compressed ); + exit 0; +} + + +sub show_help { + my $help_arg = shift || 0; + + return show_help_types() if $help_arg =~ /^types?/; + + App::Ack::print( <<"END_OF_HELP" ); +Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES] + +Search for PATTERN in each source file in the tree from the current +directory on down. If any files or directories are specified, then +only those files and directories are checked. ack may also search +STDIN, but only if no file or directory arguments are specified, +or if one of them 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: + --lines=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 + --output=expr Output the evaluation of expr for each line + (turns off text highlighting) + -o Show only the part of a line matching PATTERN + Same as --output='\$&' + --passthru Print all lines, whether matching or not + --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 (default: + on unless explicitly searching a single file) + -h, --no-filename Suppress the prefixing filename on output + -c, --count Show number of lines matching per file + --[no]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. + + -s Suppress error messages about nonexistent or + unreadable files. + + +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 selected, without + searching. The PATTERN must not be specified. + -g Same as -f, but only select files matching + PATTERN. + --sort-files Sort the found files lexically. + --show-types Show which types each file has. + --files-from=FILE Read the list of files to search from FILE. + -x Read the list of files to search from STDIN. + +File inclusion/exclusion: + --[no]ignore-dir=name Add/remove directory from list of ignored dirs + --[no]ignore-directory=name Synonym for ignore-dir + --ignore-file=filter Add filter for ignoring files + -r, -R, --recurse Recurse into subdirectories (default: on) + -n, --no-recurse No descending into subdirectories + --[no]follow Follow symlinks. Default is off. + -k, --known-types Include only files of types that ack recognizes. + + --type=X Include only X files, where X is a recognized + filetype. + --type=noX Exclude X files. + See "ack --help-types" for supported filetypes. + +File type specification: + --type-set TYPE:FILTER:FILTERARGS + Files with the given FILTERARGS applied to the + given FILTER are recognized as being of type + TYPE. This replaces an existing definition for + type TYPE. + --type-add TYPE:FILTER:FILTERARGS + Files with the given FILTERARGS applied to the + given FILTER are recognized as being type TYPE. + --type-del TYPE Removes all filters associated with TYPE. + + +Miscellaneous: + --[no]env Ignore environment variables and global ackrc + files. --env is legal but redundant. + --ackrc=filename Specify an ackrc file to use + --ignore-ack-defaults Ignore default definitions included with ack. + --create-ackrc Outputs a default ackrc for your customization + to standard output. + --help, -? This help + --help-types Display all known types + --dump Dump information on which options are loaded + from which RC files + --[no]filter Force ack to treat standard input as a pipe + (--filter) or tty (--nofilter) + --man Man page + --version Display version & copyright + --thpppt Bill the Cat + --bar The warning admiral + --cathy Chocolate! Chocolate! Chocolate! + +Exit status is 0 if match, 1 if no match. + +ack's home page is at https://beyondgrep.com/ + +The full ack manual is available by running "ack --man". + +This is version $VERSION of ack. Run "ack --version" for full version info. +END_OF_HELP + + return; + } + + + +sub show_help_types { + App::Ack::print( <<'END_OF_HELP' ); +Usage: ack [OPTION]... PATTERN [FILES OR DIRECTORIES] + +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 { $_->to_string } @{$ext_list} ); + } + App::Ack::print( sprintf( " --[no]%-*.*s %s\n", $maxlen, $maxlen, $type, $ext_list ) ); + } + + return; +} + +sub show_man { + require Pod::Usage; + + Pod::Usage::pod2usage({ + -input => $App::Ack::orig_program_name, + -verbose => 2, + -exitval => 0, + }); + + return; +} + + +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 print { print {$fh} @_; return; } +sub print_blank_line { App::Ack::print( "\n" ); return; } +sub print_filename { App::Ack::print( $_[0], $_[1] ); return; } + +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 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::Resource; + + +use warnings; +use strict; +use overload + '""' => 'name'; + + +sub new { + my $class = shift; + my $filename = shift; + + my $self = bless { + filename => $filename, + fh => undef, + opened => 0, + }, $class; + + if ( $self->{filename} eq '-' ) { + $self->{fh} = *STDIN; + $self->{opened} = 1; + } + + return $self; +} + + + +sub name { + return $_[0]->{filename}; +} + + + +sub basename { + my ( $self ) = @_; + + # XXX Definedness? Pre-populate the slot with an undef? + unless ( exists $self->{basename} ) { + $self->{basename} = (File::Spec->splitpath($self->name))[2]; + } + + return $self->{basename}; +} + + + +sub open { + my ( $self ) = @_; + + if ( !$self->{opened} ) { + if ( open $self->{fh}, '<', $self->{filename} ) { + $self->{opened} = 1; + } + else { + $self->{fh} = undef; + } + } + + return $self->{fh}; +} + + + +sub needs_line_scan { + my $self = shift; + my $opt = shift; + + return 1 if $opt->{v}; + + my $size = -s $self->{fh}; + if ( $size == 0 ) { + return 0; + } + elsif ( $size > 100_000 ) { + return 1; + } + + my $buffer; + my $rc = sysread( $self->{fh}, $buffer, $size ); + if ( !defined($rc) && $App::Ack::report_bad_filenames ) { + App::Ack::warn( "$self->{filename}: $!" ); + return 1; + } + return 0 unless $rc && ( $rc == $size ); + + return $buffer =~ /$opt->{regex}/m; +} + + + +sub reset { + my $self = shift; + + # Return if we haven't opened the file yet. + if ( !defined($self->{fh}) ) { + return; + } + + if ( !seek( $self->{fh}, 0, 0 ) && $App::Ack::report_bad_filenames ) { + App::Ack::warn( "$self->{filename}: $!" ); + } + + return; +} + + + +sub close { + my $self = shift; + + # Return if we haven't opened the file yet. + if ( !defined($self->{fh}) ) { + return; + } + + if ( !close($self->{fh}) && $App::Ack::report_bad_filenames ) { + App::Ack::warn( $self->name() . ": $!" ); + } + + $self->{opened} = 0; + + return; +} + + + +sub clone { + my ( $self ) = @_; + + return __PACKAGE__->new($self->name); +} + + + +sub firstliney { + my ( $self ) = @_; + + if ( !exists $self->{firstliney} ) { + my $fh = $self->open(); + if ( !$fh ) { + if ( $App::Ack::report_bad_filenames ) { + App::Ack::warn( $self->name . ': ' . $! ); + } + return ''; + } + + my $buffer = ''; + my $rc = sysread( $fh, $buffer, 250 ); + unless($rc) { # XXX handle this better? + $buffer = ''; + } + $buffer =~ s/[\r\n].*//s; + $self->{firstliney} = $buffer; + $self->reset; + + $self->close; + } + + return $self->{firstliney}; +} + +1; +package App::Ack::Resources; + + + +use Errno qw(EACCES); + +use warnings; +use strict; + +sub _generate_error_handler { + my $opt = shift; + + if ( $opt->{dont_report_bad_filenames} ) { + return sub { + my $msg = shift; + if ( $! == EACCES ) { + return; + } + App::Ack::warn( $msg ); + }; + } + else { + return sub { + my $msg = shift; + App::Ack::warn( $msg ); + }; + } +} + + +sub from_argv { + my $class = shift; + my $opt = shift; + my $start = shift; + + my $self = bless {}, $class; + + my $file_filter = undef; + my $descend_filter = $opt->{descend_filter}; + + if( $opt->{n} ) { + $descend_filter = sub { + return 0; + }; + } + + $self->{iter} = + File::Next::files( { + file_filter => $opt->{file_filter}, + descend_filter => $descend_filter, + error_handler => _generate_error_handler($opt), + warning_handler => sub {}, + sort_files => $opt->{sort_files}, + follow_symlinks => $opt->{follow}, + }, @{$start} ); + + return $self; +} + + +sub from_file { + my $class = shift; + my $opt = shift; + my $file = shift; + + my $iter = + File::Next::from_file( { + error_handler => _generate_error_handler($opt), + warning_handler => _generate_error_handler($opt), + sort_files => $opt->{sort_files}, + }, $file ) or return undef; + + return bless { + iter => $iter, + }, $class; +} + +# This is for reading input lines from STDIN, not the list of files from STDIN +sub from_stdin { + my $class = shift; + my $opt = shift; + + my $self = bless {}, $class; + + my $has_been_called = 0; + + $self->{iter} = sub { + if ( !$has_been_called ) { + $has_been_called = 1; + return '-'; + } + return; + }; + + return $self; +} + +sub next { + my $self = shift; + + my $file = $self->{iter}->() or return; + + return App::Ack::Resource->new( $file ); +} + +1; +package App::Ack::ConfigDefault; + +use warnings; +use strict; + + + + +sub options { + return split( /\n/, _options_block() ); +} + + +sub options_clean { + return grep { /./ && !/^#/ } options(); +} + + +sub _options_block { + my $lines = <<'HERE'; +# This is the default ackrc for ack version ==VERSION==. + +# There are four different ways to match +# +# is: Match the filename exactly +# +# ext: Match the extension of the filename exactly +# +# match: Match the filename against a Perl regular expression +# +# firstlinematch: Match the first 250 characters of the first line +# of text against a Perl regular expression. This is only for +# the --type-add option. + + +### Directories to ignore + +# Bazaar +# http://bazaar.canonical.com/ +--ignore-directory=is:.bzr + +# Codeville +# http://freecode.com/projects/codeville +--ignore-directory=is:.cdv + +# Interface Builder (Xcode) +# http://en.wikipedia.org/wiki/Interface_Builder +--ignore-directory=is:~.dep +--ignore-directory=is:~.dot +--ignore-directory=is:~.nib +--ignore-directory=is:~.plst + +# Git +# http://git-scm.com/ +--ignore-directory=is:.git +# When using submodules, .git is a file. +--ignore-file=is:.git + +# Mercurial +# http://mercurial.selenic.com/ +--ignore-directory=is:.hg + +# quilt +# http://directory.fsf.org/wiki/Quilt +--ignore-directory=is:.pc + +# Subversion +# http://subversion.tigris.org/ +--ignore-directory=is:.svn + +# Monotone +# http://www.monotone.ca/ +--ignore-directory=is:_MTN + +# CVS +# http://savannah.nongnu.org/projects/cvs +--ignore-directory=is:CVS + +# RCS +# http://www.gnu.org/software/rcs/ +--ignore-directory=is:RCS + +# SCCS +# http://en.wikipedia.org/wiki/Source_Code_Control_System +--ignore-directory=is:SCCS + +# darcs +# http://darcs.net/ +--ignore-directory=is:_darcs + +# Vault/Fortress +--ignore-directory=is:_sgbak + +# autoconf +# http://www.gnu.org/software/autoconf/ +--ignore-directory=is:autom4te.cache + +# Perl module building +--ignore-directory=is:blib +--ignore-directory=is:_build + +# Perl Devel::Cover module's output directory +# https://metacpan.org/release/Devel-Cover +--ignore-directory=is:cover_db + +# Node modules created by npm +--ignore-directory=is:node_modules + +# CMake cache +# http://www.cmake.org/ +--ignore-directory=is:CMakeFiles + +# Eclipse workspace folder +# http://eclipse.org/ +--ignore-directory=is:.metadata + +# Cabal (Haskell) sandboxes +# http://www.haskell.org/cabal/users-guide/installing-packages.html +--ignore-directory=is:.cabal-sandbox + +### Files to ignore + +# Backup files +--ignore-file=ext:bak +--ignore-file=match:/~$/ + +# Emacs swap files +--ignore-file=match:/^#.+#$/ + +# vi/vim swap files http://vim.org/ +--ignore-file=match:/[._].*\.swp$/ + +# core dumps +--ignore-file=match:/core\.\d+$/ + +# minified Javascript +--ignore-file=match:/[.-]min[.]js$/ +--ignore-file=match:/[.]js[.]min$/ + +# minified CSS +--ignore-file=match:/[.]min[.]css$/ +--ignore-file=match:/[.]css[.]min$/ + +# JS and CSS source maps +--ignore-file=match:/[.]js[.]map$/ +--ignore-file=match:/[.]css[.]map$/ + +# PDFs, because they pass Perl's -T detection +--ignore-file=ext:pdf + +# Common graphics, just as an optimization +--ignore-file=ext:gif,jpg,jpeg,png + + +### Filetypes defined + +# Perl +# http://perl.org/ +--type-add=perl:ext:pl,pm,pod,t,psgi +--type-add=perl:firstlinematch:/^#!.*\bperl/ + +# Perl tests +--type-add=perltest:ext:t + +# Makefiles +# http://www.gnu.org/s/make/ +--type-add=make:ext:mk +--type-add=make:ext:mak +--type-add=make:is:makefile +--type-add=make:is:Makefile +--type-add=make:is:Makefile.Debug +--type-add=make:is:Makefile.Release + +# Rakefiles +# http://rake.rubyforge.org/ +--type-add=rake:is:Rakefile + +# CMake +# http://www.cmake.org/ +--type-add=cmake:is:CMakeLists.txt +--type-add=cmake:ext:cmake + +# Actionscript +--type-add=actionscript:ext:as,mxml + +# Ada +# http://www.adaic.org/ +--type-add=ada:ext:ada,adb,ads + +# ASP +# http://msdn.microsoft.com/en-us/library/aa286483.aspx +--type-add=asp:ext:asp + +# ASP.Net +# http://www.asp.net/ +--type-add=aspx:ext:master,ascx,asmx,aspx,svc + +# Assembly +--type-add=asm:ext:asm,s + +# Batch +--type-add=batch:ext:bat,cmd + +# ColdFusion +# http://en.wikipedia.org/wiki/ColdFusion +--type-add=cfmx:ext:cfc,cfm,cfml + +# Clojure +# http://clojure.org/ +--type-add=clojure:ext:clj,cljs,edn,cljc + +# C +# .xs are Perl C files +--type-add=cc:ext:c,h,xs + +# C header files +--type-add=hh:ext:h + +# CoffeeScript +# http://coffeescript.org/ +--type-add=coffeescript:ext:coffee + +# C++ +--type-add=cpp:ext:cpp,cc,cxx,m,hpp,hh,h,hxx + +# C++ header files +--type-add=hpp:ext:hpp,hh,h,hxx + +# C# +--type-add=csharp:ext:cs + +# CSS +# http://www.w3.org/Style/CSS/ +--type-add=css:ext:css + +# Dart +# http://www.dartlang.org/ +--type-add=dart:ext:dart + +# Delphi +# http://en.wikipedia.org/wiki/Embarcadero_Delphi +--type-add=delphi:ext:pas,int,dfm,nfm,dof,dpk,dproj,groupproj,bdsgroup,bdsproj + +# Elixir +# http://elixir-lang.org/ +--type-add=elixir:ext:ex,exs + +# Emacs Lisp +# http://www.gnu.org/software/emacs +--type-add=elisp:ext:el + +# Erlang +# http://www.erlang.org/ +--type-add=erlang:ext:erl,hrl + +# Fortran +# http://en.wikipedia.org/wiki/Fortran +--type-add=fortran:ext:f,f77,f90,f95,f03,for,ftn,fpp + +# Go +# http://golang.org/ +--type-add=go:ext:go + +# Groovy +# http://groovy.codehaus.org/ +--type-add=groovy:ext:groovy,gtmpl,gpp,grunit,gradle + +# GSP +# http://groovy.codehaus.org/GSP +--type-add=gsp:ext:gsp + +# Haskell +# http://www.haskell.org/ +--type-add=haskell:ext:hs,lhs + +# HTML +--type-add=html:ext:htm,html,xhtml + +# Jade +# http://jade-lang.com/ +--type-add=jade:ext:jade + +# Java +# http://www.oracle.com/technetwork/java/index.html +--type-add=java:ext:java,properties + +# JavaScript +--type-add=js:ext:js + +# JSP +# http://www.oracle.com/technetwork/java/javaee/jsp/index.html +--type-add=jsp:ext:jsp,jspx,jspf,jhtm,jhtml + +# JSON +# http://www.json.org/ +--type-add=json:ext:json + +# Kotlin +# https://kotlinlang.org/ +--type-add=kotlin:ext:kt,kts + +# Less +# http://www.lesscss.org/ +--type-add=less:ext:less + +# Common Lisp +# http://common-lisp.net/ +--type-add=lisp:ext:lisp,lsp + +# Lua +# http://www.lua.org/ +--type-add=lua:ext:lua +--type-add=lua:firstlinematch:/^#!.*\blua(jit)?/ + +# Objective-C +--type-add=objc:ext:m,h + +# Objective-C++ +--type-add=objcpp:ext:mm,h + +# OCaml +# http://caml.inria.fr/ +--type-add=ocaml:ext:ml,mli,mll,mly + +# Matlab +# http://en.wikipedia.org/wiki/MATLAB +--type-add=matlab:ext:m + +# Parrot +# http://www.parrot.org/ +--type-add=parrot:ext:pir,pasm,pmc,ops,pod,pg,tg + +# PHP +# http://www.php.net/ +--type-add=php:ext:php,phpt,php3,php4,php5,phtml +--type-add=php:firstlinematch:/^#!.*\bphp/ + +# Plone +# http://plone.org/ +--type-add=plone:ext:pt,cpt,metadata,cpy,py + +# Python +# http://www.python.org/ +--type-add=python:ext:py +--type-add=python:firstlinematch:/^#!.*\bpython/ + +# R +# http://www.r-project.org/ +--type-add=rr:ext:R + +# reStructured Text +# http://docutils.sourceforge.net/rst.html +--type-add=rst:ext:rst + +# Ruby +# http://www.ruby-lang.org/ +--type-add=ruby:ext:rb,rhtml,rjs,rxml,erb,rake,spec +--type-add=ruby:is:Rakefile +--type-add=ruby:firstlinematch:/^#!.*\bruby/ + +# Rust +# http://www.rust-lang.org/ +--type-add=rust:ext:rs + +# Sass +# http://sass-lang.com +--type-add=sass:ext:sass,scss + +# Scala +# http://www.scala-lang.org/ +--type-add=scala:ext:scala + +# Scheme +# http://groups.csail.mit.edu/mac/projects/scheme/ +--type-add=scheme:ext:scm,ss + +# Shell +--type-add=shell:ext:sh,bash,csh,tcsh,ksh,zsh,fish +--type-add=shell:firstlinematch:/^#!.*\b(?:ba|t?c|k|z|fi)?sh\b/ + +# Smalltalk +# http://www.smalltalk.org/ +--type-add=smalltalk:ext:st + +# Smarty +# http://www.smarty.net/ +--type-add=smarty:ext:tpl + +# SQL +# http://www.iso.org/iso/catalogue_detail.htm?csnumber=45498 +--type-add=sql:ext:sql,ctl + +# Stylus +# http://learnboost.github.io/stylus/ +--type-add=stylus:ext:styl + +# Swift +# https://developer.apple.com/swift/ +--type-add=swift:ext:swift +--type-add=swift:firstlinematch:/^#!.*\bswift/ + +# Tcl +# http://www.tcl.tk/ +--type-add=tcl:ext:tcl,itcl,itk + +# LaTeX +# http://www.latex-project.org/ +--type-add=tex:ext:tex,cls,sty + +# Template Toolkit (Perl) +# http://template-toolkit.org/ +--type-add=tt:ext:tt,tt2,ttml + +# Visual Basic +--type-add=vb:ext:bas,cls,frm,ctl,vb,resx + +# Verilog +--type-add=verilog:ext:v,vh,sv + +# VHDL +# http://www.eda.org/twiki/bin/view.cgi/P1076/WebHome +--type-add=vhdl:ext:vhd,vhdl + +# Vim +# http://www.vim.org/ +--type-add=vim:ext:vim + +# XML +# http://www.w3.org/TR/REC-xml/ +--type-add=xml:ext:xml,dtd,xsd,xsl,xslt,ent,wsdl +--type-add=xml:firstlinematch:/<[?]xml/ + +# YAML +# http://yaml.org/ +--type-add=yaml:ext:yaml,yml +HERE + $lines =~ s/==VERSION==/$App::Ack::VERSION/sm; + + return $lines; +} + +1; +package App::Ack::ConfigFinder; + + +use strict; +use warnings; + +use Cwd 3.00 (); +use File::Spec 3.00; + +use if ($^O eq 'MSWin32'), 'Win32'; + + +sub new { + my ( $class ) = @_; + + return bless {}, $class; +} + + +sub _remove_redundancies { + my @configs = @_; + + my %seen; + foreach my $config (@configs) { + my $key = $config->{path}; + if ( not $App::Ack::is_windows ) { + # On Unix, uniquify on inode. + my ($dev, $inode) = (stat $key)[0, 1]; + $key = "$dev:$inode" if defined $dev; + } + undef $config if $seen{$key}++; + } + return grep { defined } @configs; +} + + +sub _check_for_ackrc { + return unless defined $_[0]; + + my @files = grep { -f } + map { File::Spec->catfile(@_, $_) } + qw(.ackrc _ackrc); + + die File::Spec->catdir(@_) . " contains both .ackrc and _ackrc.\n" . + "Please remove one of those files.\n" + if @files > 1; + + return wantarray ? @files : $files[0]; +} # end _check_for_ackrc + + + +sub find_config_files { + my @config_files; + + if ( $App::Ack::is_windows ) { + push @config_files, map { +{ path => File::Spec->catfile($_, 'ackrc') } } ( + Win32::GetFolderPath(Win32::CSIDL_COMMON_APPDATA()), + Win32::GetFolderPath(Win32::CSIDL_APPDATA()), + ); + } + else { + push @config_files, { path => '/etc/ackrc' }; + } + + + if ( $ENV{'ACKRC'} && -f $ENV{'ACKRC'} ) { + push @config_files, { path => $ENV{'ACKRC'} }; + } + else { + push @config_files, map { +{ path => $_ } } _check_for_ackrc($ENV{'HOME'}); + } + + my $cwd = Cwd::getcwd(); + return () unless defined $cwd; + + # XXX This should go through some untainted cwd-fetching function, and not get untainted brute-force like this. + $cwd =~ /(.+)/; + $cwd = $1; + my @dirs = File::Spec->splitdir( $cwd ); + while(@dirs) { + my $ackrc = _check_for_ackrc(@dirs); + if(defined $ackrc) { + push @config_files, { project => 1, path => $ackrc }; + last; + } + pop @dirs; + } + + # We only test for existence here, so if the file is deleted out from under us, this will fail later. + return _remove_redundancies( @config_files ); +} + + + +sub read_rcfile { + my $file = shift; + + return unless defined $file && -e $file; + + my @lines; + + open( my $fh, '<', $file ) or App::Ack::die( "Unable to read $file: $!" ); + while ( my $line = <$fh> ) { + chomp $line; + $line =~ s/^\s+//; + $line =~ s/\s+$//; + + next if $line eq ''; + next if $line =~ /^\s*#/; + + push( @lines, $line ); + } + close $fh or App::Ack::die( "Unable to close $file: $!" ); + + return @lines; +} + +1; +package App::Ack::ConfigLoader; + +use strict; +use warnings; + +use Carp 1.04 (); +use Getopt::Long 2.38 (); +use Text::ParseWords 3.1 (); + + +my @INVALID_COMBINATIONS; + +BEGIN { + my @context = qw( -A -B -C --after-context --before-context --context ); + my @pretty = qw( --heading --group --break ); + my @filename = qw( -h -H --with-filename --no-filename ); + + @INVALID_COMBINATIONS = ( + # XXX normalize + [qw(-l)] => [@context, @pretty, @filename, qw(-L -o --passthru --output --max-count --column -f -g --show-types)], + [qw(-L)] => [@context, @pretty, @filename, qw(-l -o --passthru --output --max-count --column -f -g --show-types -c --count)], + [qw(--line)] => [@context, @pretty, @filename, qw(-l --files-with-matches --files-without-matches -L -o --passthru --match -m --max-count -1 -c --count --column --print0 -f -g --show-types)], + [qw(-o)] => [@context, qw(--output -c --count --column --column -f --show-types)], + [qw(--passthru)] => [@context, qw(--output --column -m --max-count -1 -c --count -f -g)], + [qw(--output)] => [@context, qw(-c --count -f -g)], + [qw(--match)] => [qw(-f -g)], + [qw(-m --max-count)] => [qw(-1 -f -g -c --count)], + [qw(-h --no-filename)] => [qw(-H --with-filename -f -g --group --heading)], + [qw(-H --with-filename)] => [qw(-h --no-filename -f -g)], + [qw(-c --count)] => [@context, @pretty, qw(--column -f -g)], + [qw(--column)] => [qw(-f -g)], + [@context] => [qw(-f -g)], + [qw(-f)] => [qw(-g), @pretty], + [qw(-g)] => [qw(-f), @pretty], + ); +} + +sub _generate_ignore_dir { + my ( $option_name, $opt ) = @_; + + my $is_inverted = $option_name =~ /^--no/; + + return sub { + my ( undef, $dir ) = @_; + + $dir = App::Ack::remove_dir_sep( $dir ); + if ( $dir !~ /:/ ) { + $dir = 'is:' . $dir; + } + + my ( $filter_type, $args ) = split /:/, $dir, 2; + + if ( $filter_type eq 'firstlinematch' ) { + Carp::croak( qq{Invalid filter specification "$filter_type" for option '$option_name'} ); + } + + my $filter = App::Ack::Filter->create_filter($filter_type, split(/,/, $args)); + my $collection; + + my $previous_inversion_matches = $opt->{idirs} && !($is_inverted xor $opt->{idirs}[-1]->is_inverted()); + + if ( $previous_inversion_matches ) { + $collection = $opt->{idirs}[-1]; + + if ( $is_inverted ) { + # XXX this relies on invert of an inverted filter + # to return the original + $collection = $collection->invert() + } + } + else { + $collection = App::Ack::Filter::Collection->new(); + + if ( $is_inverted ) { + push @{ $opt->{idirs} }, $collection->invert(); + } + else { + push @{ $opt->{idirs} }, $collection; + } + } + + $collection->add($filter); + + if ( $filter_type eq 'is' ) { + $collection->add(App::Ack::Filter::IsPath->new($args)); + } + }; +} + +sub process_filter_spec { + my ( $spec ) = @_; + + if ( $spec =~ /^(\w+):(\w+):(.*)/ ) { + my ( $type_name, $ext_type, $arguments ) = ( $1, $2, $3 ); + + return ( $type_name, + App::Ack::Filter->create_filter($ext_type, split(/,/, $arguments)) ); + } + elsif ( $spec =~ /^(\w+)=(.*)/ ) { # Check to see if we have ack1-style argument specification. + my ( $type_name, $extensions ) = ( $1, $2 ); + + my @extensions = split(/,/, $extensions); + foreach my $extension ( @extensions ) { + $extension =~ s/^[.]//; + } + + return ( $type_name, App::Ack::Filter->create_filter('ext', @extensions) ); + } + else { + Carp::croak "invalid filter specification '$spec'"; + } +} + + +sub uninvert_filter { + my ( $opt, @filters ) = @_; + + return unless defined $opt->{filters} && @filters; + + # Loop through all the registered filters. If we hit one that + # matches this extension and it's inverted, we need to delete it from + # the options. + for ( my $i = 0; $i < @{ $opt->{filters} }; $i++ ) { + my $opt_filter = @{ $opt->{filters} }[$i]; + + # XXX Do a real list comparison? This just checks string equivalence. + if ( $opt_filter->is_inverted() && "$opt_filter->{filter}" eq "@filters" ) { + splice @{ $opt->{filters} }, $i, 1; + $i--; + } + } + + return; +} + + +sub process_filetypes { + my ( $opt, $arg_sources ) = @_; + + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); # start with default options, minus some annoying ones + Getopt::Long::Configure( + 'no_ignore_case', + 'no_auto_abbrev', + 'pass_through', + ); + my %additional_specs; + + my $add_spec = sub { + my ( undef, $spec ) = @_; + + my ( $name, $filter ) = process_filter_spec($spec); + + push @{ $App::Ack::mappings{$name} }, $filter; + + $additional_specs{$name . '!'} = sub { + my ( undef, $value ) = @_; + + my @filters = @{ $App::Ack::mappings{$name} }; + if ( not $value ) { + @filters = map { $_->invert() } @filters; + } + else { + uninvert_filter( $opt, @filters ); + } + + push @{ $opt->{'filters'} }, @filters; + }; + }; + + my $set_spec = sub { + my ( undef, $spec ) = @_; + + my ( $name, $filter ) = process_filter_spec($spec); + + $App::Ack::mappings{$name} = [ $filter ]; + + $additional_specs{$name . '!'} = sub { + my ( undef, $value ) = @_; + + my @filters = @{ $App::Ack::mappings{$name} }; + if ( not $value ) { + @filters = map { $_->invert() } @filters; + } + + push @{ $opt->{'filters'} }, @filters; + }; + }; + + my $delete_spec = sub { + my ( undef, $name ) = @_; + + delete $App::Ack::mappings{$name}; + delete $additional_specs{$name . '!'}; + }; + + my %type_arg_specs = ( + 'type-add=s' => $add_spec, + 'type-set=s' => $set_spec, + 'type-del=s' => $delete_spec, + ); + + foreach my $source (@{$arg_sources}) { + my ( $source_name, $args ) = @{$source}{qw/name contents/}; + + if ( ref($args) ) { + # $args are modified in place, so no need to munge $arg_sources + local @ARGV = @{$args}; + Getopt::Long::GetOptions(%type_arg_specs); + @{$args} = @ARGV; + } + else { + ( undef, $source->{contents} ) = + Getopt::Long::GetOptionsFromString($args, %type_arg_specs); + } + } + + $additional_specs{'k|known-types'} = sub { + my ( undef, $value ) = @_; + + my @filters = map { @{$_} } values(%App::Ack::mappings); + + push @{ $opt->{'filters'} }, @filters; + }; + + return \%additional_specs; +} + + +sub removed_option { + my ( $option, $explanation ) = @_; + + $explanation ||= ''; + return sub { + warn "Option '$option' is not valid in ack 2.\n$explanation"; + exit 1; + }; +} + + +sub get_arg_spec { + my ( $opt, $extra_specs ) = @_; + + my $dash_a_explanation = <<'EOT'; +You don't need -a, ack 1.x users. This is because ack 2.x has +-k/--known-types which makes it only select files of known types, rather +than any text file (which is the behavior of ack 1.x). + +If you're surprised to see this message because you didn't put -a on the +command line, you may have options in an .ackrc, or in the ACKRC_OPTIONS +environment variable. Try using the --dump flag to help find it. +EOT + + + sub _context_value { + my $val = shift; + + # Contexts default to 2. + return (!defined($val) || ($val < 0)) ? 2 : $val; + } + + return { + 1 => sub { $opt->{1} = $opt->{m} = 1 }, + 'A|after-context:-1' => sub { shift; $opt->{after_context} = _context_value(shift) }, + 'B|before-context:-1' => sub { shift; $opt->{before_context} = _context_value(shift) }, + 'C|context:-1' => sub { shift; $opt->{before_context} = $opt->{after_context} = _context_value(shift) }, + 'a' => removed_option('-a', $dash_a_explanation), + 'all' => removed_option('--all', $dash_a_explanation), + 'break!' => \$opt->{break}, + 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}, + 'create-ackrc' => sub { print "$_\n" for ( '--ignore-ack-defaults', App::Ack::ConfigDefault::options() ); exit; }, + 'env!' => sub { + my ( undef, $value ) = @_; + + if ( !$value ) { + $opt->{noenv_seen} = 1; + } + }, + f => \$opt->{f}, + 'files-from=s' => \$opt->{files_from}, + 'filter!' => \$App::Ack::is_filter_mode, + flush => \$opt->{flush}, + 'follow!' => \$opt->{follow}, + g => \$opt->{g}, + G => removed_option('-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}, + 'ignore-directory|ignore-dir=s' => _generate_ignore_dir('--ignore-dir', $opt), + 'ignore-file=s' => sub { + my ( undef, $file ) = @_; + + my ( $filter_type, $args ) = split /:/, $file, 2; + + my $filter = App::Ack::Filter->create_filter($filter_type, split(/,/, $args)); + + if ( !$opt->{ifiles} ) { + $opt->{ifiles} = App::Ack::Filter::Collection->new(); + } + $opt->{ifiles}->add($filter); + }, + 'lines=s' => sub { shift; my $val = shift; push @{$opt->{lines}}, $val }, + 'l|files-with-matches' + => \$opt->{l}, + 'L|files-without-matches' + => \$opt->{L}, + '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' => sub { + my ( undef, $value ) = @_; + + $opt->{pager} = $value || $ENV{PAGER}; + }, + 'noignore-directory|noignore-dir=s' => _generate_ignore_dir('--noignore-dir', $opt), + 'nopager' => sub { $opt->{pager} = undef }, + 'passthru' => \$opt->{passthru}, + 'print0' => \$opt->{print0}, + 'Q|literal' => \$opt->{Q}, + 'r|R|recurse' => sub { $opt->{n} = 0 }, + 's' => \$opt->{dont_report_bad_filenames}, + 'show-types' => \$opt->{show_types}, + 'smart-case!' => \$opt->{smart_case}, + 'sort-files' => \$opt->{sort_files}, + 'type=s' => sub { + my ( $getopt, $value ) = @_; + + my $cb_value = 1; + if ( $value =~ s/^no// ) { + $cb_value = 0; + } + + my $callback = $extra_specs->{ $value . '!' }; + + if ( $callback ) { + $callback->( $getopt, $cb_value ); + } + else { + Carp::croak( "Unknown type '$value'" ); + } + }, + 'u' => removed_option('-u'), + 'unrestricted' => removed_option('--unrestricted'), + 'v|invert-match' => \$opt->{v}, + 'w|word-regexp' => \$opt->{w}, + 'x' => sub { $opt->{files_from} = '-' }, + + 'version' => sub { App::Ack::print_version_statement(); exit; }, + 'help|?:s' => sub { shift; App::Ack::show_help(@_); exit; }, + 'help-types' => sub { App::Ack::show_help_types(); exit; }, + 'man' => sub { App::Ack::show_man(); exit; }, + $extra_specs ? %{$extra_specs} : (), + }; # arg_specs +} + + +sub process_other { + my ( $opt, $extra_specs, $arg_sources ) = @_; + + # Start with default options, minus some annoying ones. + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); + Getopt::Long::Configure( + 'bundling', + 'no_ignore_case', + ); + + my $argv_source; + my $is_help_types_active; + + foreach my $source (@{$arg_sources}) { + my ( $source_name, $args ) = @{$source}{qw/name contents/}; + + if ( $source_name eq 'ARGV' ) { + $argv_source = $args; + last; + } + } + + if ( $argv_source ) { # This *should* always be true, but you never know... + my @copy = @{$argv_source}; + local @ARGV = @copy; + + Getopt::Long::Configure('pass_through'); + + Getopt::Long::GetOptions( + 'help-types' => \$is_help_types_active, + ); + + Getopt::Long::Configure('no_pass_through'); + } + + my $arg_specs = get_arg_spec($opt, $extra_specs); + + foreach my $source (@{$arg_sources}) { + my ( $source_name, $args ) = @{$source}{qw/name contents/}; + + my $args_for_source = $arg_specs; + + if ( $source->{project} ) { + my $illegal = sub { + die "Options --output, --pager and --match are forbidden in project .ackrc files.\n"; + }; + + $args_for_source = { + %{$args_for_source}, + 'output=s' => $illegal, + 'pager:s' => $illegal, + 'match=s' => $illegal, + }; + } + + my $ret; + if ( ref($args) ) { + local @ARGV = @{$args}; + $ret = Getopt::Long::GetOptions( %{$args_for_source} ); + @{$args} = @ARGV; + } + else { + ( $ret, $source->{contents} ) = + Getopt::Long::GetOptionsFromString( $args, %{$args_for_source} ); + } + if ( !$ret ) { + if ( !$is_help_types_active ) { + my $where = $source_name eq 'ARGV' ? 'on command line' : "in $source_name"; + App::Ack::die( "Invalid option $where" ); + } + } + if ( $opt->{noenv_seen} ) { + App::Ack::die( "--noenv found in $source_name" ); + } + } + + # XXX We need to check on a -- in the middle of a non-ARGV source + + return; +} + + +sub should_dump_options { + my ( $sources ) = @_; + + foreach my $source (@{$sources}) { + my ( $name, $options ) = @{$source}{qw/name contents/}; + + if($name eq 'ARGV') { + my $dump; + local @ARGV = @{$options}; + Getopt::Long::Configure('default', 'pass_through', 'no_auto_help', 'no_auto_version'); + Getopt::Long::GetOptions( + 'dump' => \$dump, + ); + @{$options} = @ARGV; + return $dump; + } + } + return; +} + + +sub explode_sources { + my ( $sources ) = @_; + + my @new_sources; + + Getopt::Long::Configure('default', 'pass_through', 'no_auto_help', 'no_auto_version'); + + my %opt; + my $arg_spec = get_arg_spec(\%opt); + + my $add_type = sub { + my ( undef, $arg ) = @_; + + # XXX refactor? + if ( $arg =~ /(\w+)=/) { + $arg_spec->{$1} = sub {}; + } + else { + ( $arg ) = split /:/, $arg; + $arg_spec->{$arg} = sub {}; + } + }; + + my $del_type = sub { + my ( undef, $arg ) = @_; + + delete $arg_spec->{$arg}; + }; + + foreach my $source (@{$sources}) { + my ( $name, $options ) = @{$source}{qw/name contents/}; + if ( ref($options) ne 'ARRAY' ) { + $source->{contents} = $options = + [ Text::ParseWords::shellwords($options) ]; + } + + for my $j ( 0 .. @{$options}-1 ) { + next unless $options->[$j] =~ /^-/; + my @chunk = ( $options->[$j] ); + push @chunk, $options->[$j] while ++$j < @{$options} && $options->[$j] !~ /^-/; + $j--; + + my @copy = @chunk; + local @ARGV = @chunk; + Getopt::Long::GetOptions( + 'type-add=s' => $add_type, + 'type-set=s' => $add_type, + 'type-del=s' => $del_type, + ); + Getopt::Long::GetOptions( %{$arg_spec} ); + + push @new_sources, { + name => $name, + contents => \@copy, + }; + } + } + + return \@new_sources; +} + + +sub compare_opts { + my ( $a, $b ) = @_; + + my $first_a = $a->[0]; + my $first_b = $b->[0]; + + $first_a =~ s/^--?//; + $first_b =~ s/^--?//; + + return $first_a cmp $first_b; +} + + +sub dump_options { + my ( $sources ) = @_; + + $sources = explode_sources($sources); + + my %opts_by_source; + my @source_names; + + foreach my $source (@{$sources}) { + my ( $name, $contents ) = @{$source}{qw/name contents/}; + if ( not $opts_by_source{$name} ) { + $opts_by_source{$name} = []; + push @source_names, $name; + } + push @{$opts_by_source{$name}}, $contents; + } + + foreach my $name (@source_names) { + my $contents = $opts_by_source{$name}; + + print $name, "\n"; + print '=' x length($name), "\n"; + print ' ', join(' ', @{$_}), "\n" foreach sort { compare_opts($a, $b) } @{$contents}; + } + + return; +} + + +sub remove_default_options_if_needed { + my ( $sources ) = @_; + + my $default_index; + + foreach my $index ( 0 .. $#{$sources} ) { + if ( $sources->[$index]{'name'} eq 'Defaults' ) { + $default_index = $index; + last; + } + } + + return $sources unless defined $default_index; + + my $should_remove = 0; + + # Start with default options, minus some annoying ones. + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); + Getopt::Long::Configure( + 'no_ignore_case', + 'no_auto_abbrev', + 'pass_through', + ); + + foreach my $index ( $default_index + 1 .. $#{$sources} ) { + my ( $name, $args ) = @{$sources->[$index]}{qw/name contents/}; + + if (ref($args)) { + local @ARGV = @{$args}; + Getopt::Long::GetOptions( + 'ignore-ack-defaults' => \$should_remove, + ); + @{$args} = @ARGV; + } + else { + ( undef, $sources->[$index]{contents} ) = Getopt::Long::GetOptionsFromString($args, + 'ignore-ack-defaults' => \$should_remove, + ); + } + } + + Getopt::Long::Configure('default'); + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); + + return $sources unless $should_remove; + + my @copy = @{$sources}; + splice @copy, $default_index, 1; + return \@copy; +} + + +sub check_for_mutually_exclusive_options { + my ( $arg_sources ) = @_; + + my %mutually_exclusive_with; + my @copy = @{$arg_sources}; + + for(my $i = 0; $i < @INVALID_COMBINATIONS; $i += 2) { + my ( $lhs, $rhs ) = @INVALID_COMBINATIONS[ $i, $i + 1 ]; + + foreach my $l_opt ( @{$lhs} ) { + foreach my $r_opt ( @{$rhs} ) { + push @{ $mutually_exclusive_with{ $l_opt } }, $r_opt; + push @{ $mutually_exclusive_with{ $r_opt } }, $l_opt; + } + } + } + + while( @copy ) { + my %set_opts; + + my $source = shift @copy; + my ( $source_name, $args ) = @{$source}{qw/name contents/}; + $args = ref($args) ? [ @{$args} ] : [ Text::ParseWords::shellwords($args) ]; + + foreach my $opt ( @{$args} ) { + next unless $opt =~ /^[-+]/; + last if $opt eq '--'; + + if( $opt =~ /^(.*)=/ ) { + $opt = $1; + } + elsif ( $opt =~ /^(-[^-]).+/ ) { + $opt = $1; + } + + $set_opts{ $opt } = 1; + + my $mutex_opts = $mutually_exclusive_with{ $opt }; + + next unless $mutex_opts; + + foreach my $mutex_opt ( @{$mutex_opts} ) { + if($set_opts{ $mutex_opt }) { + die "Options '$mutex_opt' and '$opt' are mutually exclusive\n"; + } + } + } + } + + return; +} + + +sub process_args { + my $arg_sources = \@_; + + my %opt = ( + pager => $ENV{ACK_PAGER_COLOR} || $ENV{ACK_PAGER}, + ); + + check_for_mutually_exclusive_options($arg_sources); + + $arg_sources = remove_default_options_if_needed($arg_sources); + + if ( should_dump_options($arg_sources) ) { + dump_options($arg_sources); + exit(0); + } + + my $type_specs = process_filetypes(\%opt, $arg_sources); + process_other(\%opt, $type_specs, $arg_sources); + while ( @{$arg_sources} ) { + my $source = shift @{$arg_sources}; + my ( $source_name, $args ) = @{$source}{qw/name contents/}; + + # All of our sources should be transformed into an array ref + if ( ref($args) ) { + if ( $source_name eq 'ARGV' ) { + @ARGV = @{$args}; + } + elsif (@{$args}) { + Carp::croak "source '$source_name' has extra arguments!"; + } + } + else { + Carp::croak 'The impossible has occurred!'; + } + } + my $filters = ($opt{filters} ||= []); + + # Throw the default filter in if no others are selected. + if ( not grep { !$_->is_inverted() } @{$filters} ) { + push @{$filters}, App::Ack::Filter::Default->new(); + } + return \%opt; +} + + +sub retrieve_arg_sources { + my @arg_sources; + + my $noenv; + my $ackrc; + + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); + Getopt::Long::Configure('pass_through'); + Getopt::Long::Configure('no_auto_abbrev'); + + Getopt::Long::GetOptions( + 'noenv' => \$noenv, + 'ackrc=s' => \$ackrc, + ); + + Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version'); + + my @files; + + if ( !$noenv ) { + my $finder = App::Ack::ConfigFinder->new; + @files = $finder->find_config_files; + } + if ( $ackrc ) { + # We explicitly use open so we get a nice error message. + # XXX This is a potential race condition!. + if(open my $fh, '<', $ackrc) { + close $fh; + } + else { + die "Unable to load ackrc '$ackrc': $!" + } + push( @files, { path => $ackrc } ); + } + + push @arg_sources, { + name => 'Defaults', + contents => [ App::Ack::ConfigDefault::options_clean() ], + }; + + foreach my $file ( @files) { + my @lines = App::Ack::ConfigFinder::read_rcfile($file->{path}); + + if(@lines) { + push @arg_sources, { + name => $file->{path}, + contents => \@lines, + project => $file->{project}, + }; + } + } + + if ( $ENV{ACK_OPTIONS} && !$noenv ) { + push @arg_sources, { + name => 'ACK_OPTIONS', + contents => $ENV{ACK_OPTIONS}, + }; + } + + push @arg_sources, { + name => 'ARGV', + contents => [ @ARGV ], + }; + + return @arg_sources; +} + +1; # End of App::Ack::ConfigLoader +package App::Ack::Filter; + + +use strict; +use warnings; + +use Carp 1.04 (); + +my %filter_types; + + +sub create_filter { + my ( undef, $type, @args ) = @_; + + if ( my $package = $filter_types{$type} ) { + return $package->new(@args); + } + Carp::croak "Unknown filter type '$type'"; +} + + +sub register_filter { + my ( undef, $type, $package ) = @_; + + $filter_types{$type} = $package; + + return; +} + + +sub invert { + my ( $self ) = @_; + + return App::Ack::Filter::Inverse->new( $self ); +} + + +sub is_inverted { + return 0; +} + + +sub to_string { + my ( $self ) = @_; + + return '(unimplemented to_string)'; +} + + +sub inspect { + my ( $self ) = @_; + + return ref($self); +} + +1; +package App::Ack::Filter::Extension; + + +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} + + +sub new { + my ( $class, @extensions ) = @_; + + my $exts = join('|', map { "\Q$_\E"} @extensions); + my $re = qr/[.](?:$exts)$/i; + + return bless { + extensions => \@extensions, + regex => $re, + groupname => 'ExtensionGroup', + }, $class; +} + +sub create_group { + return App::Ack::Filter::ExtensionGroup->new(); +} + +sub filter { + my ( $self, $resource ) = @_; + + my $re = $self->{'regex'}; + + return $resource->name =~ /$re/; +} + +sub inspect { + my ( $self ) = @_; + + my $re = $self->{'regex'}; + + return ref($self) . " - $re"; +} + +sub to_string { + my ( $self ) = @_; + + my $exts = $self->{'extensions'}; + + return join(' ', map { ".$_" } @{$exts}); +} + +BEGIN { + App::Ack::Filter->register_filter(ext => __PACKAGE__); +} + +1; +package App::Ack::Filter::FirstLineMatch; + + + +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} + +sub new { + my ( $class, $re ) = @_; + + $re =~ s{^/|/$}{}g; # XXX validate? + $re = qr{$re}i; + + return bless { + regex => $re, + }, $class; +} + +# This test reads the first 250 characters of a file, then just uses the +# first line found in that. This prevents reading something like an entire +# .min.js file (which might be only one "line" long) into memory. + +sub filter { + my ( $self, $resource ) = @_; + + my $re = $self->{'regex'}; + + my $line = $resource->firstliney; + + return $line =~ /$re/; +} + +sub inspect { + my ( $self ) = @_; + + my $re = $self->{'regex'}; + + return ref($self) . " - $re"; +} + +sub to_string { + my ( $self ) = @_; + + (my $re = $self->{regex}) =~ s{\([^:]*:(.*)\)$}{$1}; + + return "first line matches /$re/"; +} + +BEGIN { + App::Ack::Filter->register_filter(firstlinematch => __PACKAGE__); +} + +1; +package App::Ack::Filter::Is; + + +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} + +use File::Spec 3.00 (); + +sub new { + my ( $class, $filename ) = @_; + + return bless { + filename => $filename, + groupname => 'IsGroup', + }, $class; +} + +sub create_group { + return App::Ack::Filter::IsGroup->new(); +} + +sub filter { + my ( $self, $resource ) = @_; + + my $filename = $self->{'filename'}; + my $base = (File::Spec->splitpath($resource->name))[2]; + + return $base eq $filename; +} + +sub inspect { + my ( $self ) = @_; + + my $filename = $self->{'filename'}; + + return ref($self) . " - $filename"; +} + +sub to_string { + my ( $self ) = @_; + + my $filename = $self->{'filename'}; + + return $filename; +} + +BEGIN { + App::Ack::Filter->register_filter(is => __PACKAGE__); +} + +1; +package App::Ack::Filter::Match; + +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} + +use File::Spec 3.00; + + +sub new { + my ( $class, $re ) = @_; + + $re =~ s{^/|/$}{}g; # XXX validate? + $re = qr/$re/i; + + return bless { + regex => $re, + groupname => 'MatchGroup', + }, $class; +} + +sub create_group { + return App::Ack::Filter::MatchGroup->new; +} + +sub filter { + my ( $self, $resource ) = @_; + + my $re = $self->{'regex'}; + + return $resource->basename =~ /$re/; +} + +sub inspect { + my ( $self ) = @_; + + my $re = $self->{'regex'}; + + print ref($self) . " - $re"; + + return; +} + +sub to_string { + my ( $self ) = @_; + + my $re = $self->{'regex'}; + + return "filename matches $re"; +} + +BEGIN { + App::Ack::Filter->register_filter(match => __PACKAGE__); +} + +1; +package App::Ack::Filter::Default; + + +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} + +sub new { + my ( $class ) = @_; + + return bless {}, $class; +} + +sub filter { + my ( $self, $resource ) = @_; + + return -T $resource->name; +} + +1; +package App::Ack::Filter::Inverse; + + + +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} + +sub new { + my ( $class, $filter ) = @_; + + return bless { + filter => $filter, + }, $class; +} + +sub filter { + my ( $self, $resource ) = @_; + + my $filter = $self->{'filter'}; + return !$filter->filter( $resource ); +} + +sub invert { + my $self = shift; + + return $self->{'filter'}; +} + +sub is_inverted { + return 1; +} + +sub inspect { + my ( $self ) = @_; + + my $filter = $self->{'filter'}; + + return "!$filter"; +} + +1; +package App::Ack::Filter::Collection; + + +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} + +sub new { + my ( $class ) = @_; + + return bless { + groups => {}, + ungrouped => [], + }, $class; +} + +sub filter { + my ( $self, $resource ) = @_; + + for my $group (values %{$self->{'groups'}}) { + if ($group->filter($resource)) { + return 1; + } + } + + for my $filter (@{$self->{'ungrouped'}}) { + if ($filter->filter($resource)) { + return 1; + } + } + + return 0; +} + +sub add { + my ( $self, $filter ) = @_; + + if (exists $filter->{'groupname'}) { + my $group = ($self->{groups}->{$filter->{groupname}} ||= $filter->create_group()); + $group->add($filter); + } + else { + push @{$self->{'ungrouped'}}, $filter; + } + + return; +} + +sub inspect { + my ( $self ) = @_; + + return ref($self) . " - $self"; +} + +sub to_string { + my ( $self ) = @_; + + my $ungrouped = $self->{'ungrouped'}; + + return join(', ', map { "($_)" } @{$ungrouped}); +} + +1; +package App::Ack::Filter::IsGroup; + + +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} + +use File::Spec 3.00 (); + +sub new { + my ( $class ) = @_; + + return bless { + data => {}, + }, $class; +} + +sub add { + my ( $self, $filter ) = @_; + + $self->{data}->{ $filter->{filename} } = 1; + + return; +} + +sub filter { + my ( $self, $resource ) = @_; + + my $data = $self->{'data'}; + my $base = $resource->basename; + + return exists $data->{$base}; +} + +sub inspect { + my ( $self ) = @_; + + return ref($self) . " - $self"; +} + +sub to_string { + my ( $self ) = @_; + + return join(' ', keys %{$self->{data}}); +} + +1; +package App::Ack::Filter::ExtensionGroup; + + +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} + +sub new { + my ( $class ) = @_; + + return bless { + data => {}, + }, $class; +} + +sub add { + my ( $self, $filter ) = @_; + + foreach my $ext (@{$filter->{extensions}}) { + $self->{data}->{lc $ext} = 1; + } + + return; +} + +sub filter { + my ( $self, $resource ) = @_; + + if ($resource->name =~ /[.]([^.]*)$/) { + return exists $self->{'data'}->{lc $1}; + } + + return 0; +} + +sub inspect { + my ( $self ) = @_; + + return ref($self) . " - $self"; +} + +sub to_string { + my ( $self ) = @_; + + return join(' ', map { ".$_" } sort keys %{$self->{data}}); +} + +1; +package App::Ack::Filter::MatchGroup; + + +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} + +sub new { + my ( $class ) = @_; + + return bless { + matches => [], + big_re => undef, + }, $class; +} + +sub add { + my ( $self, $filter ) = @_; + + push @{ $self->{matches} }, $filter->{regex}; + + my $re = join('|', map { "(?:$_)" } @{ $self->{matches} }); + $self->{big_re} = qr/$re/; + + return; +} + +sub filter { + my ( $self, $resource ) = @_; + + my $re = $self->{big_re}; + + return $resource->basename =~ /$re/; +} + +sub inspect { + my ( $self ) = @_; + + # XXX Needs an explicit return. +} + +sub to_string { + my ( $self ) = @_; + + # XXX Needs an explicit return. +} + +1; +package App::Ack::Filter::IsPath; + + +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} + + +sub new { + my ( $class, $filename ) = @_; + + return bless { + filename => $filename, + groupname => 'IsPathGroup', + }, $class; +} + +sub create_group { + return App::Ack::Filter::IsPathGroup->new(); +} + +sub filter { + my ( $self, $resource ) = @_; + + return $resource->name eq $self->{'filename'}; +} + +sub inspect { + my ( $self ) = @_; + + my $filename = $self->{'filename'}; + + return ref($self) . " - $filename"; +} + +sub to_string { + my ( $self ) = @_; + + my $filename = $self->{'filename'}; + + return $filename; +} + +1; +package App::Ack::Filter::IsPathGroup; + + + + +use strict; +use warnings; +BEGIN { + our @ISA = 'App::Ack::Filter'; +} + +sub new { + my ( $class ) = @_; + + return bless { + data => {}, + }, $class; +} + +sub add { + my ( $self, $filter ) = @_; + + $self->{data}->{ $filter->{filename} } = 1; + + return; +} + +sub filter { + my ( $self, $resource ) = @_; + + my $data = $self->{'data'}; + + return exists $data->{$resource->name}; +} + +sub inspect { + my ( $self ) = @_; + + return ref($self) . " - $self"; +} + +sub to_string { + my ( $self ) = @_; + + return join(' ', keys %{$self->{data}}); +} + +1; +package File::Next; + +use strict; +use warnings; + + +our $VERSION = '1.16'; + + + +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 $_[0] }, + warning_handler => sub { CORE::warn @_ }, + sort_files => undef, + follow_symlinks => 1, + nul_separated => 0, + ); + %skip_dirs = map {($_,1)} (File::Spec->curdir, File::Spec->updir); +} + + +sub files { + die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__); + + my ($parms,@queue) = _setup( \%files_defaults, @_ ); + + return sub { + my $filter = $parms->{file_filter}; + while (@queue) { + my ($dirname,$file,$fullpath) = splice( @queue, 0, 3 ); + if ( -f $fullpath || -p _ || $fullpath =~ m{^/dev/fd} ) { + if ( $filter ) { + local $_ = $file; + local $File::Next::dir = $dirname; + local $File::Next::name = $fullpath; + next if not $filter->(); + } + return wantarray ? ($dirname,$file,$fullpath) : $fullpath; + } + if ( -d _ ) { + unshift( @queue, _candidate_files( $parms, $fullpath ) ); + } + } # while + + return; + }; # iterator +} + + + + + + +sub from_file { + die _bad_invocation() if @_ && defined($_[0]) && ($_[0] eq __PACKAGE__); + + my ($parms,@queue) = _setup( \%files_defaults, @_ ); + my $err = $parms->{error_handler}; + my $warn = $parms->{warning_handler}; + + my $filename = $queue[1]; + + if ( !defined($filename) ) { + $err->( 'Must pass a filename to from_file()' ); + return undef; + } + + my $fh; + if ( $filename eq '-' ) { + $fh = \*STDIN; + } + else { + if ( !open( $fh, '<', $filename ) ) { + $err->( "Unable to open $filename: $!", $! + 0 ); + return undef; + } + } + + return sub { + my $filter = $parms->{file_filter}; + local $/ = $parms->{nul_separated} ? "\x00" : $/; + while ( my $fullpath = <$fh> ) { + chomp $fullpath; + next unless $fullpath =~ /./; + if ( not ( -f $fullpath || -p _ ) ) { + $warn->( "$fullpath: No such file" ); + next; + } + + my ($volume,$dirname,$file) = File::Spec->splitpath( $fullpath ); + if ( $filter ) { + local $_ = $file; + local $File::Next::dir = $dirname; + local $File::Next::name = $fullpath; + next if not $filter->(); + } + return wantarray ? ($dirname,$file,$fullpath) : $fullpath; + } # while + close $fh; + + return; + }; # iterator +} + +sub _bad_invocation { + my $good = (caller(1))[3]; + my $bad = $good; + $bad =~ s/(.+)::/$1->/; + return "$good must not be invoked as $bad"; +} + +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 $dirname = shift; + + my $dh; + if ( !opendir $dh, $dirname ) { + $parms->{error_handler}->( "$dirname: $!", $! + 0 ); + 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; + + my $fullpath = File::Spec->catdir( $dirname, $file ); + if ( !$follow_symlinks ) { + next if -l $fullpath; + $has_stat = 1; + } + + # Only do directory checking if we have a descend_filter + if ( $descend_filter ) { + if ( $has_stat ? (-d _) : (-d $fullpath) ) { + local $File::Next::dir = $fullpath; + local $_ = $file; + next if not $descend_filter->(); + } + } + if ( $sort_sub ) { + push( @newfiles, [ $dirname, $file, $fullpath ] ); + } + else { + push( @newfiles, $dirname, $file, $fullpath ); + } + } + closedir $dh; + + if ( $sort_sub ) { + return map { @{$_} } sort $sort_sub @newfiles; + } + + return @newfiles; +} + + +1; # End of File::Next diff --git a/imap-pass b/imap-pass new file mode 100755 index 0000000..f4cd46e --- /dev/null +++ b/imap-pass @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +import argparse +import keyring +import getpass + +if __name__ == '__main__': + SERVICE = 'mbsync' + + parser = argparse.ArgumentParser() + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('--set', '-s', type=str, help='Account to save password') + group.add_argument('--get', '-g', type=str, help='Account to get password') + + args = parser.parse_args() + + if args.set: + password = getpass.getpass() + keyring.set_password(SERVICE, args.set, password) + else: + print(keyring.get_password(SERVICE, args.get)) diff --git a/md b/md new file mode 100755 index 0000000..27434a9 --- /dev/null +++ b/md @@ -0,0 +1,5 @@ +#!/bin/sh + +# Preview Markdown in w3m + +pandoc $1 |w3m -T text/html -- cgit v1.2.3