[ack] 01/05: New upstream version 2.999.01

Axel Beckert abe at deuxchevaux.org
Wed Jan 3 02:39:41 UTC 2018


This is an automated email from the git hooks/post-receive script.

abe pushed a commit to branch ack3
in repository ack.

commit caf92dccd025eb6c71aacf12e7dd8da23fca7d30
Merge: 7a0f3f9 4bd6612
Author: Axel Beckert <abe at deuxchevaux.org>
Date:   Wed Jan 3 03:15:12 2018 +0100

    New upstream version 2.999.01

 CONTRIBUTING.md                                    |   39 -
 Changes                                            |  517 +----
 MANIFEST                                           |   90 +-
 META.json                                          |   13 +-
 META.yml                                           |   11 +-
 Makefile.PL                                        |  117 +-
 README.md                                          |   50 +-
 ack                                                | 2249 +++++---------------
 Ack.pm => lib/App/Ack.pm                           |  189 +-
 ConfigDefault.pm => lib/App/Ack/ConfigDefault.pm   |   42 +-
 ConfigFinder.pm => lib/App/Ack/ConfigFinder.pm     |   17 +-
 ConfigLoader.pm => lib/App/Ack/ConfigLoader.pm     |  140 +-
 lib/App/Ack/Docs/Cookbook.pm                       |  599 ++++++
 lib/App/Ack/Docs/FAQ.pm                            |  112 +
 lib/App/Ack/Docs/Manual.pm                         | 1118 ++++++++++
 Resource.pm => lib/App/Ack/File.pm                 |   73 +-
 Resources.pm => lib/App/Ack/Files.pm               |   75 +-
 Filter.pm => lib/App/Ack/Filter.pm                 |   33 +-
 Collection.pm => lib/App/Ack/Filter/Collection.pm  |   20 +-
 Default.pm => lib/App/Ack/Filter/Default.pm        |    6 +-
 Extension.pm => lib/App/Ack/Filter/Extension.pm    |   16 +-
 .../App/Ack/Filter/ExtensionGroup.pm               |    6 +-
 .../App/Ack/Filter/FirstLineMatch.pm               |   17 +-
 Inverse.pm => lib/App/Ack/Filter/Inverse.pm        |    7 +-
 Is.pm => lib/App/Ack/Filter/Is.pm                  |   17 +-
 IsGroup.pm => lib/App/Ack/Filter/IsGroup.pm        |    9 +-
 IsPath.pm => lib/App/Ack/Filter/IsPath.pm          |   14 +-
 .../App/Ack/Filter/IsPathGroup.pm                  |   10 +-
 Match.pm => lib/App/Ack/Filter/Match.pm            |   20 +-
 MatchGroup.pm => lib/App/Ack/Filter/MatchGroup.pm  |   21 +-
 squash                                             |   17 +-
 t/00-load.t                                        |   30 +-
 t/Barfly.pm                                        |  162 ++
 t/FilterTest.pm                                    |    6 +-
 t/Util.pm                                          |  357 +++-
 t/ack-1.t                                          |    3 +-
 t/ack-color.t                                      |   63 +-
 t/ack-column.t                                     |    4 +
 t/ack-files-from.t                                 |   19 +-
 t/ack-g.t                                          |   58 +-
 t/ack-h.t                                          |    6 +-
 t/ack-help-types.t                                 |    4 +
 t/ack-help.t                                       |    2 +-
 t/ack-i.barfly                                     |  140 ++
 t/ack-i.t                                          |   38 +-
 t/ack-ignore-file.t                                |  101 +-
 t/ack-known-types.t                                |    2 +
 t/ack-line.t                                       |   63 +-
 t/ack-m.t                                          |   29 +-
 t/ack-man.t                                        |   75 +
 t/ack-match.t                                      |   74 +-
 t/ack-n.t                                          |   46 +-
 t/ack-named-pipes.t                                |   62 -
 t/ack-o.t                                          |   73 +-
 t/ack-output.t                                     |  141 +-
 t/ack-pager.t                                      |   85 +-
 t/ack-passthru.t                                   |    3 +
 t/ack-proximate.t                                  |  166 ++
 t/ack-removed-options.t                            |   34 -
 t/ack-s.t                                          |    7 +-
 t/ack-type-del.t                                   |    2 +-
 t/ack-type.t                                       |   17 +-
 t/ack-u.barfly                                     |   47 +
 t/ack-u.t                                          |   70 +
 t/ack-underline.t                                  |  107 +
 t/ack-v.t                                          |    2 +-
 t/ack-version.t                                    |   22 +
 t/ack-w.barfly                                     |  200 ++
 t/ack-w.t                                          |  280 ++-
 t/ack-x.t                                          |    3 +
 t/anchored.t                                       |    4 +-
 t/bad-ackrc-opt.t                                  |    4 +
 t/config-backwards-compat.t                        |    7 +-
 t/config-finder.t                                  |  262 ++-
 t/config-loader.t                                  |  153 +-
 t/context.t                                        |   20 +-
 t/default-filter.t                                 |    1 +
 t/{resource-iterator.t => file-iterator.t}         |    3 +-
 t/file-permission.t                                |    4 +-
 t/filetype-detection.t                             |   36 +
 t/filetypes.t                                      |   20 +-
 t/forbidden-options.t                              |  144 ++
 t/illegal-regex.t                                  |   11 +-
 t/incomplete-last-line.t                           |    6 +-
 t/{ack-interactive.t => interactive.t}             |    2 +-
 t/{ack-invalid-ackrc.t => invalid-ackrc.t}         |    8 +-
 t/issue276.t                                       |   33 -
 t/issue491.t                                       |    6 +-
 t/issue522.t                                       |    6 +-
 t/issue562.t                                       |   12 +-
 t/issue571.t                                       |    8 +-
 t/lib/00-coverage.t                                |   24 -
 t/lib/Ack.t                                        |   12 -
 t/lib/Collection.t                                 |   12 -
 t/lib/ConfigDefault.t                              |   12 -
 t/lib/ConfigFinder.t                               |   12 -
 t/lib/ConfigLoader.t                               |   12 -
 t/lib/Default.t                                    |   12 -
 t/lib/Extension.t                                  |   12 -
 t/lib/ExtensionGroup.t                             |   12 -
 t/lib/Filter.t                                     |   12 -
 t/lib/FirstLineMatch.t                             |   12 -
 t/lib/Inverse.t                                    |   12 -
 t/lib/Is.t                                         |   12 -
 t/lib/IsGroup.t                                    |   12 -
 t/lib/IsPath.t                                     |   12 -
 t/lib/IsPathGroup.t                                |   12 -
 t/lib/Match.t                                      |   12 -
 t/lib/MatchGroup.t                                 |   12 -
 t/lib/Resource.t                                   |   12 -
 t/lib/Resources.t                                  |   12 -
 t/longopts.t                                       |   38 +-
 t/lua-shebang.t                                    |   12 -
 t/mutex-options.t                                  |  132 +-
 t/named-pipes.t                                    |   55 +
 t/noenv.t                                          |   13 +-
 t/process-substitution.t                           |    3 +-
 t/r-lang-ext.t                                     |   19 -
 t/swamp/fresh.css                                  |    2 +-
 t/swamp/fresh.css.min                              |    2 +-
 t/swamp/fresh.min.css                              |    2 +-
 t/trailing-whitespace.t                            |   94 +
 xt/coding-standards.t                              |   56 +
 xt/coresubs.t                                      |   53 +
 xt/man.t                                           |   33 +-
 xt/pod.t                                           |   10 +-
 126 files changed, 5682 insertions(+), 4111 deletions(-)

diff --cc CONTRIBUTING.md
index 8715a92,8715a92..0000000
deleted file mode 100644,100644
--- a/CONTRIBUTING.md
+++ /dev/null
@@@ -1,39 -1,39 +1,0 @@@
--# Before You Report an Issue...
--
--Before you report an issue, please consult the [FAQ](https://beyondgrep.com/documentation/ack-2.16-man.html#faq).
--
--# Asking to add a new filetype, or any enhancement
--
--From the man page:
--
--> All enhancement requests MUST first be posted to the ack-users mailing list at <http://groups.google.com/group/ack-users>.  I will not consider a request without it first getting seen by other ack users.  This includes
--> requests for new filetypes.
-->
--> There is a list of enhancements I want to make to ack in the ack issues list at Github: <https://github.com/beyondgrep/ack2/issues>
-->
--> Patches are always welcome, but patches with tests get the most attention.
--
--# Reporting an Issue
--
--If you have an issue with ack, please add the following to your ticket:
--
--  - What OS you're on
--  - What version of ack you're using
--
--Please try to reproduce your issue against the latest ack from git.  If you are not able to
--reproduce the issue with ack built from git, you should probably file the issue anyway, as
--that probably means someone needs to make a release. =)
--
--Also appreciated with an issue are the following:
--
--  - Example invocations along with expected vs received output (see [#439](https://github.com/beyondgrep/ack2/issues/439), great job!)
--  - A `.t` test file that tests and verifies the behavior you expect
--  - A patch that fixes your issue
--
--# Ack 1.x
--
--Keep in mind that if you're using ack 1.x, you should probably upgrade, as ack 1.x is no longer supported.
--
--# Getting Help
--
--Also, feel free to discuss your issues on the [ack mailing list](http://groups.google.com/group/ack-users)!
diff --cc Changes
index de7355c,de7355c..6e2b601
--- a/Changes
+++ b/Changes
@@@ -1,497 -1,497 +1,92 @@@
  History file for ack.  https://beyondgrep.com/
  
--2.22    Fri Dec 22 16:42:43 CST 2017
--====================================
--No changes since 2.21_01.
--
--
--2.21_01 Mon Dec 18 23:40:45 CST 2017
--====================================
--[FIXES]
--Avoid a fatal error that sometimes happened if a file was unreadable.
--(GH#520)
--
--[ENHANCEMENTS]
--Added support for the Kotlin language.
--
--Sped up file type detection for certain files.
--
--
--2.20    Sun Dec 10 21:54:55 CST 2017
--====================================
--No changes since 2.19_01.
--
--
--2.19_01 Thu Dec  7 23:35:53 CST 2017
--====================================
--[FIXES]
--Changed a construction in the docs that Ubuntu flagged as a misspelling.
--
--[ENHANCEMENTS]
--When using submodules, the .git directory will be a file.  Make ack
--ignore this by default.  Thanks, Michele Campeotto. (GH#653)
--
--[INTERNALS]
--Replaced all the test data for the test suite with public domain documents.
--
--
--2.18    Fri Mar 24 14:53:19 CDT 2017
--====================================
--ack 2.18 will probably be the final release in the ack 2.x series.
--I'm going to be starting work on ack 3.000 in earnest.  Still,
--if you discover problems with ack 2, please report them to
--https://github.com/petdance/ack2/issues
--
--If you're interested in ack 3 development, please sign up
--for the ack-dev mailing list and/or join the ack Slack.  See
--https://beyondgrep.com/community/ for details.
--
--[INTERNALS]
--Removed the abstraction of App::Ack::Resource and its subclass
--App::Ack::Resource::Basic.  We are abandoning the idea that we'll have
--plugins.
--
--
--2.17_02 Thu Mar 23 22:25:13 CDT 2017
--====================================
--[FIXES]
--ack no longer throws an undefined variable warning if it's called
--from a directory that doesn't exist. (GH #634)
--
--[DOCUMENTATION]
--Explain that filetypes must be two characters or longer. (GH #389)
--
--[INTERNALS]
--Removed dependency on File::Glob which isn't used.
--
--
--2.17_01 Wed Mar 15 23:25:28 CDT 2017
--====================================
--[FIXES]
----context=0 (and its short counterpart -C 0) did not set to context
--of 0.  This means that a command-line --context=0 couldn't override
--a --context=5 in your ackrc.  Thanks, Ed Avis.  (GH #595)
--
--t/ack-s.t would fail in non-English locales.  Thanks, Olivier Mengué.
--(GH #485, GH #515)
--
--[ENHANCEMENTS]
----after-context and --before-context (and their short counterparts
---A and -B) no longer require a value to be passed.  If no value is
--set, they default to 2. (GH #351)
--
--Added .xhtml to the --html filetype.  Added .wsdl to the --xml filetype.
--Thanks, H.Merijn Brand.  (GH #456)
--
--[DOCUMENTATION]
--Updated incorrect docs about how ack works.  Thanks, Gerhard Poul.
--(GH #543)
--
--
--2.16    Fri Mar 10 13:32:39 CST 2017
--====================================
--No changes since 2.15_03.
--
--[CONFUSING BEHAVIOR & UPCOMING CHANGES]
--The -w has a confusing behavior that it's had since back to ack 1.x
--that will be changing in the future.  It's not changing in this
--version, but this is a heads-up that it's coming.
--
--ack -w is "match a whole word", and ack does this by putting turning
--your PATTERN into \bPATTERN\b.  So "ack -w foo" effectively becomes
--"ack \bfoo\b".  Handy.
--
--The problem is that ack doesn't put a \b before PATTERN if it begins
--with a non-word character, and won't put a \b after PATTERN if it
--ends with a non-word character.
--
--The problem is that if you're searching for "fool" or "foot", but
--only as a word, and you do "ack -w foo[lt]" or "ack -w (fool|foot)",
--you'll get matches for "football and foolish" which certainly should
--not match if you're using -w.
--
--
--2.15_03 Sun Feb 26 23:07:35 CST 2017
--====================================
--[ENHANCEMENTS]
--Include .cljs, .cljc and .edn files with the --clojure filetype.  Thanks,
--Austin Chamberlin.
--
--Added .xsd to the --xml filetype.  Thanks, Nick Morrott.
--
--Added support for Swift language.  Thanks, Nikolaj Schumacher. (GH #512)
--
--The MSYS2 project is now seen as Windows.  Thanks, Ray Donnelly. (GH #450)
--
--Expand the definition of OCaml files.  Thanks, Marek Kubica. (GH #511)
--
--Add support for Groovy Server Pages.  Thanks, Ethan Mallove. (GH #469)
--
--[INTERNALS]
--Added test to test --output. Thanks, Varadinsky! (GH #587, GH #590)
--
--[DOCUMENTATION]
--Expanded the explanation of how the -w flag works.  Thanks, Ed Avis.
--(GH #585)
--
--
--2.15_02 Thu Dec 17 15:51:15 CST 2015
--====================================
--This is the first dev version in nine months.  Many changes have gone
--into this, probably more than have been listed here.  We need to update
--this list, but for now these are fixes we know are in here.  My apologies
--for not having this changelog accurate.
--
--[FIXES]
--Reverted an optimization to make \s work properly again. (GH #572,
--GH #571, GH #562, GH #491, GH #498)
--
--Fixed an out-of-date FAQ entry.  Thanks, Jakub Wilk.  (GH #580)
  
++2.999_01 Mon Jan  1 22:11:17 CST 2018
++=====================================
  [ENHANCEMENTS]
--The JSP filetype (--jsp) now recognizes .jspf files.  Thanks, Sebastien
--Feugere.  (GH #586)
--
--[INTERNALS]
--Added test to make sure subdirs of target subdirs are ignored if
----ignore-dir applies to them.  Thanks, Pete Houston. (GH #570)
--
++Added --pod as a filetype, recognizing .pod as its extension.  This is
++Perl's POD (Plain Old Documentation) format.
  
--2.15_01 Fri Feb 13 16:23:54 CST 2015
--====================================
--[FIXES]
--The -l and -c flags would sometimes return inaccurate results due to
--a bug introduced in 2.14.  Thanks to Elliot Shank for the report! (GH #491)
--
--Behavior when using newlines in a search was inconsistent.  Thanks to Yves Chevallier
--for the report! (GH #522)
--
--Add minimal requirement of Getopt::Long 2.38, not 2.35, for GetOptionsFromString.
++Added --markdown as a filetype, recognizing .md and .markdown as
++extensions.
  
--Don't ignore directories that are specified as command line targets (GH #524)
++--pager is no longer allowed in a project .ackrc file.  --match and
++--output are not allowed in any .ackrc file.
  
--Fix a bug where a regular expression that matches the empty string could cause ack
--to go into an infinite loop (GH #542)
--
--[ENHANCEMENTS]
--Many optimizations and code cleanups.  Thanks, Stephan Hohe.
++ack 3's new features are listed below for now.
  
--Added --hpp option for C++ header files.  Thankis, Steffen Jaeckel.
--
--ack now supports --ignore-dir=match:....  Thanks, Ailin Nemui! (GitHub ticket #42)
--
--ack also supports --ignore-dir=ext:..., and --noignore-dir supports match/ext as well
--
--
--2.14    Wed Sep  3 22:48:00 CDT 2014
--====================================
  [FIXES]
--The -s flag would fail to suppress certain warnings.  Thanks, Kassio
--Borges. (GitHub ticket #439)
--
--The -w flag would fail to work properly with regex features such as
--alternation.  Thanks to Ed Avis for the report (GitHub ticket #443)
--
--The -g flag should now work faster on larger codebases.  Thanks to
--Manuel Meurer for the report (GitHub ticket #458)
--
--[ENHANCEMENTS]
--ack now ignores JavaScript and CSS source maps.  Thanks, Chris
--Rebert.
--
--ack now ships with customized shell completion scripts for bash and zsh.
--
--
--2.13_06 Sat Jan 25 23:36:09 CST 2014
--====================================
--[FIXES]
--More fixes for Windows tests.  Thanks to GitHub user @ispedals.
--
--[ENHANCEMENTS]
--
--Add docs for available colors in ack.
--
--2.13_05 Thu Jan  9 22:41:33 CST 2014
--====================================
--[FIXES]
--More whack-a-mole with Windows failures.  This time, it's POSIX::mkfifo
--dying on Windows instead of returning undef like the docs implied,
--or at least that I inferred.
--
--[ENHANCEMENTS]
----create-ackrc keeps the comments that describe each of the options,
--and it shows the ack version number.
++--lines had some mutex options that were not getting checked.  Now,
++--lines is mutex with --passthru, --match and all context options.
  
  
--2.13_04 Sat Jan  4 11:17:28 CST 2014
--====================================
--[FIXES]
--Fixed incorrect deduping of config files under Windows.  Thanks,
--Denis Howe.
--
--
--2.13_03 Thu Jan  2 22:23:03 CST 2014
--====================================
--[FIXES]
--More build fixes for Windows.  Windows config finder fixes from
--James McCoy.
--
--t/ack-named-pipes.t uses POSIX::mkfifo instead of the external
--command, which should be more portable.  Thanks, Pete Krawczyk.
--
--[ENHANCEMENTS]
--Now ignores Cabal (Haskell) sandboxes.  Thanks, Fraser Tweedale.
--
--Added filetypes for Jade, Smarty and Stylus.  Thanks, Raúl Gundín.
--
--
--2.13_02 Fri Dec 27 12:54:27 CST 2013
--====================================
--[FIXES]
--The building of ack-standalone relied on the output of `perldoc
---l`, which I apparently can't rely on having been installed.  I've
--changed the way that the squash program finds File::Next.
--
--
--2.13_01 Wed Dec 25 13:36:08 CST 2013
--====================================
--So this is Christmas
--And what have you done
--I'm sitting here hacking
--On ack 2.13_01
--   -- via Pete Krawczyk
--
--[FIXES]
--Issue #313: ack would fail when trying to check files for readability
--on some networked filesystems, or on Mac OS X with ACLs.  Now it
--uses the filetest pragma.  Thanks, Jonathan Perret.
--
--
--[INTERNALS]
--ack's entire test suite now runs under Perl's -T taint flag.  We'll
--build more security tests on top of this.
--
--Added some checks to the squash program that I hope will turn up
--errors in the Windows builds.
--
--
--2.12    Tue Dec  3 07:05:02 CST 2013
--====================================
--[SECURITY FIXES]
--This version of ack prevents the --pager, --regex and --output
--options from being used from project-level ackrc files.  It is
--possible to execute malicious code with these options, and we want
--to prevent the security risk of acking through a potentially malicious
--codebase, such as one downloaded from an Internet site or checked
--out from a code repository.
--
--The --pager, --regex and --output options may still be used from
--the global /etc/ackrc, your own private ~/.ackrc, the ACK_OPTIONS
--environment variable, and of course from the command line.
--
--[ENHANCEMENTS]
--Now ignores Eclipse .metadata directory.  Thanks, Steffen Jaeckel.
--
--[INTERNALS]
--Removed the Git revision tracking in the --version.
--
--
--2.11_02 Sun Oct  6 12:39:21 CDT 2013
--====================================
--[FIXES]
--2.11_01 was mispackaged.  This fixes that.
--
--
--2.11_01 Sun Sep 29 13:15:41 CDT 2013
--====================================
--[FIXES]
--Fixed a race condition in t/file-permission.t that was causing
--failures if tests were run in parallel.
--
--
--2.10    Tue Sep 24 16:24:11 CDT 2013
--====================================
--[ENHANCEMENTS]
--Add --perltest for *.t files.
--
--Added Matlab support.  Thanks, Zertrin.
--
--[FIXES]
--Fix the test suite for Win32.  Many thanks to Christian Walde for
--bringing the severity of this issue to our attention, as well as
--providing a Win32 development environment for us to work with.
--
--Fixed Win32-detection in the Makefile.PL.  Thanks, Michael Beijen
--and Alexandr Ciornii.
--
--More compatibility fixes for Perl 5.8.8.
--
--
--2.08    Thu Aug 22 23:11:45 CDT 2013
--====================================
--[ENHANCEMENTS]
--ack now ignores CMake's build/cache directories by default.  Thanks,
--Volodymyr Medvid.
--
--Add shebang matching for --lua files.
--
--Add documentation for --ackrc.
--
--Add Elixir filetype.
--
--Add --cathy option.  Thanks to Joe McMahon.
--
--Add some helpful debugging tips when an invalid option is found.
--Thanks to Charles Lee.
--
--Ignore PDF files by default, because Perl will detect them as text.
--
--Ignore .gif, .jpg, .jpeg and .png files.  They won't normally be
--selected, but this is an optimization so that ack doesn't have to
--open them to know.
--
--[FIXES]
--Ack's colorizing of output would get confused with multiple sets
--of parentheses.  This has been fixed.  (Issue #276)
--
--Ack would get confused when trying to colorize the output in
--DOS-format files.  This has been fixed.  (Issue #145)
--
--
--2.06
--====================================
--This mistake of an upload lived for only about 15 minutes.
--
--
--
--2.05_01 Tue May 28 10:12:04 CDT 2013
--====================================
--[ENHANCEMENTS]
--We now ignore the node_modules directories created by npm.  Thanks,
--Konrad Borowski.
--
----pager without an argument implies --pager=$PAGER.
--
----perl now recognizes Plack-style .psgi files.  Thanks, Ron Savage.
--
--Added filetypes for Coffescript, JSON, LESS, and Sass.
--
--[FIXES]
--Command-line options now override options set in ackrc files.
--
--ACK_PAGER and ACK_PAGER_COLOR now work as advertised.
--
--Fix a bug resulting in uninitialized variable warnings when more
--than one capture group was specified in the search pattern.
--
--Make sure ack is happy to build and test under cron and other
--console-less environments.
--
--Colored output is now supported and on by default on Windows.
--
--2.04    Fri Apr 26 22:47:55 CDT 2013
--====================================
--ack now runs on a standard Perl 5.8.8 install with no module updates.
--The minimum Perl requirement for ack is now explicitly 5.8.8.  Anything
--before 5.8.8 will not work, and we've added checks.  Thanks, Michael
--McClimon.
--
--[FIXES]
--ack was colorizing captured groups even if --nocolor was given.
--Thanks, Dale Sedivic.
--
--[ENHANCEMENTS]
--The --shell file type now recognizes the fish shell.
--
--We now ignore minified CSS or Javascript, in the form of either *.css.min
--or *.min.css, or *.js.min or *.min.js.
--
--Added support for the Dart language.
--
--ack 2.02 was much slower than ack 1.96, up to 8x slower in some cases.
--These slowdowns have been mostly eliminated, and in some cases ack 2.04
--is now faster than 1.96.
--
--
--2.02    Thu Apr 18 23:51:52 CDT 2013
--====================================
--[ENHANCEMENTS]
--The firstlinematch file type detection option now only searches the
--first 250 characters of the first line of the file.  Otherwise, ack
--would read entire text files that were only one line long, such as
--minified JavaScript, and that would be slow.  Thanks, Michael
--McClimon.
++=============================
++# Release notes for ack 3.000
++=============================
  
--[DOCUMENTATION]
--Many clarifications and cleanups.  Thanks, Michael McClimon.
++# New features
  
++ack 3 is a greplike tool optimized for searching large code trees.
  
--2.00    Wed Apr 17 22:49:41 CDT 2013
--====================================
++Improvements over ack 2 include:
  
--The first version of ack 2.0.
++* Improved `-w` option.
  
++* `-w` option will warn if your pattern does not lend itself to
++word matching.
  
--# Incompatibilities with ack 1.x
++* `-i`, `-I` and `--smart-case`
  
--ack 2 makes some big changes in its behaviors that could trip up
--users who are used to the idiosyncracies of ack 1.x.  These changes
--could affect your searching happiness, so please read them.
++* `--proximate=N` option
  
--* ack's default behavior is now to search all files that it identifies
--as being text.  ack 1.x would only search files that were of a file
--type that it recognized.
++* Added `--pod` and `--markdown`.
  
--* Removed the `-a` and `-u` options since the default is to search
--all text files.
++* Added `GNUmakefile` to the list of makefile specs.
  
--* Removed the `--binary` option.  ack 2.0 will not find and search
--through binary files.
++* Added `-S` as a synonym for `--smart-case`.
  
--* Removed the `--skipped` option.
++# Bug fixes
  
--* Removed the `--invert-file-match` option.  `-v` now works with
--`-g`.  To list files that do not match `/foo/`
++* Column numbers were not getting colorized in the output.  Added
++`--color-colno` option and `ACK_COLOR_COLNO` environment variable.
  
--    ack -g foo -v
++* A pattern that wanted whitespace at the end could match the
++linefeed at the end of a line.  This is no longer possible.
  
--* `-g` now obeys all regex options: `-i`, `-w`, `-Q`, `-v`
++# Incompatibilities with ack 2
  
--* Removed the `-G` switch, because it was too confusing to have two
--regexes specified on the command line.  Now you use the `-x` switch
--to pipe filenames from one `ack` invocation into another.
++## ack 3 requires Perl 5.10.1
  
--To search files with filename matching "sales" for the string "foo":
++ack 2 only needed Perl 5.8.8.  This shouldn't be a problem since 5.10.1
++has been out since 2009.
  
--    ack -g sales | ack -x foo
++## ack 3 no longer highlights capture groups.
  
--# New features in ack 2.0
++ack 2 would highlight your capture groups.  For example,
  
--ack 2.0 will:
++    ack '(set|get)_foo_(name|id)'
  
--* By default searches all text files, as identified by Perl's `-T` operator
--    * We will no longer have a `-a` switch.
++would highlight the `set` or `get`, and the `name` or `id`, but not the
++full `set_user_id` that was matched.
  
--* improved flexibility in defining filetype selectors
--    * name equality ($filename eq 'Makefile')
--    * glob-style matching (`*.pl` identifies a Perl file)
--    * regex-style matching (`/\.pl$/i` identifies a Perl file)
--    * shebang-line matching (shebang line matching `/usr/bin/perl/`
--    identifies a Perl file)
++This feature was too confusing and has been removed.  Now, the entire
++matching string is highlighted.
  
--* support for multiple ackrc files
--    * global ackrc (/etc/ackrc)
--        * https://github.com/petdance/ack/issues/#issue/79
--    * user-specific ackrc (~/.ackrc)
--    * per-project ackrc files (~/myproject/.ackrc)
++## ack 3's --output allows fewer special variables
  
--* you can use --dump to figure which options are set where
++In ack 2, you could put any kind of Perl code in the `--output`
++option and it would get `eval`uated at run time, which would let
++you do tricky stuff like this gem from Mark Fowler
++(http://www.perladvent.org/2014/2014-12-21.html):
  
--* all inclusion/exclusion rules will be in the ackrc files
--    * ack 2.0 has a set of definitions for filetypes, directories to
--      include or exclude, etc, *but* these are only included so you don't
--      need to ship an ackrc file to a new machine.  You may tell ack to
--      disregard these defaults if you like.
++    ack --output='$&: @{[ eval "use LWP::Simple; 1" && length LWP::Simple::get($&) ]} bytes' \
++                    'https?://\S+' list.txt
++    http://google.com/: 19529 bytes
++    http://metacpan.org/: 7560 bytes
++    http://www.perladvent.org/: 5562 bytes
  
--* In addition to the classic `--thpppt` option to draw Bill the
--Cat, `ack --bar` will draw (of course) Admiral Ackbar.
++This has been a security problem in the past, and so in ack 3 we
++no longer `eval` the contents of `--output`.  You're now restricted
++to the following variables: `$1` thru `$9`, `$_`, `$.`, `$&`, ``$` ``,
++`$'` and `$+`.  You can also embed `\t`, `\n` and `\r` ,
++and `$f` as stand-in for `$filename` in `ack2 --output` .
diff --cc MANIFEST
index a52aa00,d07113a..41d77a9
--- a/MANIFEST
+++ b/MANIFEST
@@@ -1,35 -1,35 +1,39 @@@
  Changes
--CONTRIBUTING.md
  LICENSE.md
  MANIFEST
  README.md
  Makefile.PL
  
  ack
--Ack.pm
--Collection.pm
--ConfigDefault.pm
--ConfigFinder.pm
--ConfigLoader.pm
--Default.pm
--Extension.pm
--ExtensionGroup.pm
--Filter.pm
--FirstLineMatch.pm
--Inverse.pm
--Is.pm
--IsGroup.pm
--IsPath.pm
--IsPathGroup.pm
--Match.pm
--MatchGroup.pm
--Resource.pm
--Resources.pm
++lib/App/Ack.pm
++lib/App/Ack/ConfigDefault.pm
++lib/App/Ack/ConfigFinder.pm
++lib/App/Ack/ConfigLoader.pm
++lib/App/Ack/File.pm
++lib/App/Ack/Files.pm
++lib/App/Ack/Filter.pm
++lib/App/Ack/Filter/Collection.pm
++lib/App/Ack/Filter/Default.pm
++lib/App/Ack/Filter/Extension.pm
++lib/App/Ack/Filter/ExtensionGroup.pm
++lib/App/Ack/Filter/FirstLineMatch.pm
++lib/App/Ack/Filter/Inverse.pm
++lib/App/Ack/Filter/Is.pm
++lib/App/Ack/Filter/IsGroup.pm
++lib/App/Ack/Filter/IsPath.pm
++lib/App/Ack/Filter/IsPathGroup.pm
++lib/App/Ack/Filter/Match.pm
++lib/App/Ack/Filter/MatchGroup.pm
++
++lib/App/Ack/Docs/Manual.pm
++lib/App/Ack/Docs/FAQ.pm
++lib/App/Ack/Docs/Cookbook.pm
  
  record-options
  squash
  test-pager
  
++t/Barfly.pm
  t/FilterTest.pm
  t/Util.pm
  
@@@ -50,28 -50,28 +54,32 @@@ t/ack-help-types.
  t/ack-h.t
  t/ack-ignore-dir.t
  t/ack-ignore-file.t
--t/ack-interactive.t
--t/ack-invalid-ackrc.t
  t/ack-i.t
++t/ack-i.barfly
  t/ack-known-types.t
  t/ack-k.t
  t/ack-line.t
  t/ack-match.t
  t/ack-m.t
--t/ack-named-pipes.t
++t/ack-man.t
  t/ack-n.t
  t/ack-o.t
  t/ack-output.t
  t/ack-pager.t
  t/ack-passthru.t
  t/ack-print0.t
--t/ack-removed-options.t
++t/ack-proximate.t
  t/ack-show-types.t
  t/ack-s.t
  t/ack-type-del.t
  t/ack-type.t
++t/ack-u.t
++t/ack-u.barfly
++t/ack-underline.t
  t/ack-v.t
++t/ack-version.t
  t/ack-w.t
++t/ack-w.barfly
  t/ack-x.t
  t/anchored.t
  t/asp-net-ext.t
@@@ -85,31 -85,31 +93,34 @@@ t/context.
  t/default-filter.t
  t/exit-code.t
  t/ext-filter.t
++t/file-iterator.t
  t/file-permission.t
++t/filetype-detection.t
  t/filetypes.t
  t/filter.t
  t/firstlinematch-filter.t
++t/forbidden-options.t
  t/highlighting.t
  t/illegal-regex.t
  t/incomplete-last-line.t
++t/interactive.t
++t/invalid-ackrc.t
  t/inverted-file-filter.t
  t/is-filter.t
  t/issue244.t
--t/issue276.t
  t/issue491.t
  t/issue522.t
  t/issue562.t
  t/issue571.t
  t/longopts.t
--t/lua-shebang.t
  t/match-filter.t
  t/mutex-options.t
++t/named-pipes.t
  t/noackrc.t
  t/noenv.t
  t/process-substitution.t
--t/resource-iterator.t
--t/r-lang-ext.t
  t/runtests.pl
++t/trailing-whitespace.t
  t/zero.t
  
  t/etc/buttonhook.xml.xxx
@@@ -121,27 -121,27 +132,6 @@@ t/etc/shebang.py.xx
  t/etc/shebang.rb.xxx
  t/etc/shebang.sh.xxx
  
--t/lib/00-coverage.t
--t/lib/Ack.t
--t/lib/Collection.t
--t/lib/ConfigDefault.t
--t/lib/ConfigFinder.t
--t/lib/ConfigLoader.t
--t/lib/Default.t
--t/lib/Extension.t
--t/lib/ExtensionGroup.t
--t/lib/Filter.t
--t/lib/FirstLineMatch.t
--t/lib/Inverse.t
--t/lib/Is.t
--t/lib/IsGroup.t
--t/lib/IsPath.t
--t/lib/IsPathGroup.t
--t/lib/Match.t
--t/lib/MatchGroup.t
--t/lib/Resource.t
--t/lib/Resources.t
--
  t/home/.ackrc
  t/swamp/#emacs-workfile.pl#
  t/swamp/0
@@@ -236,7 -236,5 +226,9 @@@ t/text/numbered-text.tx
  t/text/ozymandias.txt
  t/text/raven.txt
  
++xt/coding-standards.t
++xt/coresubs.t
  xt/man.t
  xt/pod.t
 +META.yml                                 Module YAML meta-data (added by MakeMaker)
 +META.json                                Module JSON meta-data (added by MakeMaker)
diff --cc META.json
index 805f8e8,0000000..c7fd639
mode 100644,000000..100644
--- a/META.json
+++ b/META.json
@@@ -1,69 -1,0 +1,72 @@@
 +{
 +   "abstract" : "A grep-like program for searching source code",
 +   "author" : [
 +      "Andy Lester <andy at petdance.com>"
 +   ],
 +   "dynamic_config" : 1,
 +   "generated_by" : "ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.150010",
 +   "license" : [
 +      "artistic_2"
 +   ],
 +   "meta-spec" : {
 +      "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
 +      "version" : "2"
 +   },
 +   "name" : "ack",
 +   "no_index" : {
 +      "directory" : [
 +         "t",
 +         "inc"
 +      ]
 +   },
 +   "prereqs" : {
 +      "build" : {
 +         "requires" : {
 +            "ExtUtils::MakeMaker" : "0"
 +         }
 +      },
 +      "configure" : {
 +         "requires" : {
 +            "ExtUtils::MakeMaker" : "0"
 +         }
 +      },
 +      "runtime" : {
 +         "requires" : {
 +            "Carp" : "1.04",
 +            "Cwd" : "3.00",
 +            "Errno" : "0",
 +            "File::Basename" : "1.00015",
 +            "File::Next" : "1.16",
 +            "File::Spec" : "3.00",
 +            "File::Temp" : "0.19",
 +            "Getopt::Long" : "2.38",
 +            "Pod::Usage" : "1.26",
++            "Scalar::Util" : "0",
 +            "Term::ANSIColor" : "1.10",
 +            "Test::Harness" : "2.50",
 +            "Test::More" : "0.98",
 +            "Text::ParseWords" : "3.1",
-             "perl" : "5.008008"
++            "if" : "0",
++            "parent" : "0",
++            "perl" : "5.010001"
 +         }
 +      }
 +   },
-    "release_status" : "stable",
++   "release_status" : "unstable",
 +   "resources" : {
 +      "bugtracker" : {
-          "web" : "https://github.com/beyondgrep/ack2"
++         "web" : "https://github.com/beyondgrep/ack3"
 +      },
 +      "homepage" : "https://beyondgrep.com/",
 +      "license" : [
 +         "http://www.perlfoundation.org/artistic_license_2_0"
 +      ],
 +      "repository" : {
 +         "type" : "git",
-          "url" : "git://github.com/beyondgrep/ack2.git"
++         "url" : "git://github.com/beyondgrep/ack3.git"
 +      },
 +      "x_MailingList" : "https://groups.google.com/group/ack-users"
 +   },
-    "version" : "2.22",
++   "version" : "2.999_01",
 +   "x_serialization_backend" : "JSON::PP version 2.27400_02"
 +}
diff --cc META.yml
index f90ca67,0000000..45c604e
mode 100644,000000..100644
--- a/META.yml
+++ b/META.yml
@@@ -1,42 -1,0 +1,45 @@@
 +---
 +abstract: 'A grep-like program for searching source code'
 +author:
 +  - 'Andy Lester <andy at petdance.com>'
 +build_requires:
 +  ExtUtils::MakeMaker: '0'
 +configure_requires:
 +  ExtUtils::MakeMaker: '0'
 +dynamic_config: 1
 +generated_by: 'ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.150010'
 +license: artistic_2
 +meta-spec:
 +  url: http://module-build.sourceforge.net/META-spec-v1.4.html
 +  version: '1.4'
 +name: ack
 +no_index:
 +  directory:
 +    - t
 +    - inc
 +requires:
 +  Carp: '1.04'
 +  Cwd: '3.00'
 +  Errno: '0'
 +  File::Basename: '1.00015'
 +  File::Next: '1.16'
 +  File::Spec: '3.00'
 +  File::Temp: '0.19'
 +  Getopt::Long: '2.38'
 +  Pod::Usage: '1.26'
++  Scalar::Util: '0'
 +  Term::ANSIColor: '1.10'
 +  Test::Harness: '2.50'
 +  Test::More: '0.98'
 +  Text::ParseWords: '3.1'
-   perl: '5.008008'
++  if: '0'
++  parent: '0'
++  perl: '5.010001'
 +resources:
 +  MailingList: https://groups.google.com/group/ack-users
-   bugtracker: https://github.com/beyondgrep/ack2
++  bugtracker: https://github.com/beyondgrep/ack3
 +  homepage: https://beyondgrep.com/
 +  license: http://www.perlfoundation.org/artistic_license_2_0
-   repository: git://github.com/beyondgrep/ack2.git
- version: '2.22'
++  repository: git://github.com/beyondgrep/ack3.git
++version: 2.999_01
 +x_serialization_backend: 'CPAN::Meta::YAML version 0.018'
diff --cc Makefile.PL
index b5b05f4,b5b05f4..f5ff863
--- a/Makefile.PL
+++ b/Makefile.PL
@@@ -1,6 -1,6 +1,6 @@@
  package main;
  
--require 5.008008;
++require 5.010001;
  
  use strict;
  use warnings;
@@@ -12,29 -12,29 +12,19 @@@ my %parms = 
      NAME                => 'ack',
      AUTHOR              => 'Andy Lester <andy at petdance.com>',
      ABSTRACT            => 'A grep-like program for searching source code',
--    VERSION_FROM        => 'Ack.pm',
--
--    PM                  => {
--        'Ack.pm'            => '$(INST_LIBDIR)/App/Ack.pm',
--        'Resource.pm'       => '$(INST_LIBDIR)/App/Ack/Resource.pm',
--        'Resources.pm'      => '$(INST_LIBDIR)/App/Ack/Resources.pm',
--        'ConfigDefault.pm'  => '$(INST_LIBDIR)/App/Ack/ConfigDefault.pm',
--        'ConfigFinder.pm'   => '$(INST_LIBDIR)/App/Ack/ConfigFinder.pm',
--        'ConfigLoader.pm'   => '$(INST_LIBDIR)/App/Ack/ConfigLoader.pm',
--        'Filter.pm'         => '$(INST_LIBDIR)/App/Ack/Filter.pm',
--        'Extension.pm'      => '$(INST_LIBDIR)/App/Ack/Filter/Extension.pm',
--        'FirstLineMatch.pm' => '$(INST_LIBDIR)/App/Ack/Filter/FirstLineMatch.pm',
--        'Is.pm'             => '$(INST_LIBDIR)/App/Ack/Filter/Is.pm',
--        'Match.pm'          => '$(INST_LIBDIR)/App/Ack/Filter/Match.pm',
--        'Default.pm'        => '$(INST_LIBDIR)/App/Ack/Filter/Default.pm',
--        'Inverse.pm'        => '$(INST_LIBDIR)/App/Ack/Filter/Inverse.pm',
--        'Collection.pm'     => '$(INST_LIBDIR)/App/Ack/Filter/Collection.pm',
--        'IsGroup.pm'        => '$(INST_LIBDIR)/App/Ack/Filter/IsGroup.pm',
--        'ExtensionGroup.pm' => '$(INST_LIBDIR)/App/Ack/Filter/ExtensionGroup.pm',
--        'MatchGroup.pm'     => '$(INST_LIBDIR)/App/Ack/Filter/MatchGroup.pm',
--        'IsPath.pm'         => '$(INST_LIBDIR)/App/Ack/Filter/IsPath.pm',
--        'IsPathGroup.pm'    => '$(INST_LIBDIR)/App/Ack/Filter/IsPathGroup.pm',
++    VERSION_FROM        => 'lib/App/Ack.pm',
++    LICENSE             => 'artistic_2',
++    MIN_PERL_VERSION    => 5.010001,
++    META_MERGE          => {
++        resources => {
++            homepage    => 'https://beyondgrep.com/',
++            bugtracker  => 'https://github.com/beyondgrep/ack3',
++            license     => 'http://www.perlfoundation.org/artistic_license_2_0',
++            repository  => 'git://github.com/beyondgrep/ack3.git',
++            MailingList => 'https://groups.google.com/group/ack-users',
++        },
      },
++
      EXE_FILES               => [ 'ack' ],
  
      PREREQ_PM => {
@@@ -45,8 -45,8 +35,11 @@@
          'File::Next'        => '1.16',
          'File::Spec'        => '3.00',
          'File::Temp'        => '0.19', # For newdir()
++        'if'                => 0,
          'Getopt::Long'      => '2.38',
++        'parent'            => 0,
          'Pod::Usage'        => '1.26',
++        'Scalar::Util'      => 0,
          'Term::ANSIColor'   => '1.10',
          'Test::Harness'     => '2.50', # Something reasonably newish
          'Test::More'        => '0.98', # For subtest()
@@@ -58,31 -58,31 +51,11 @@@
      clean               => { FILES => 'ack-2* nytprof* stderr.log stdout.log completion.*' },
  );
  
--if ( $ExtUtils::MakeMaker::VERSION =~ /^\d\.\d\d$/ and $ExtUtils::MakeMaker::VERSION > 6.30 ) {
--    $parms{LICENSE} = 'artistic_2';
--}
--
--if ( $ExtUtils::MakeMaker::VERSION ge '6.46' ) {
--    $parms{META_MERGE} = {
--        resources => {
--            homepage    => 'https://beyondgrep.com/',
--            bugtracker  => 'https://github.com/beyondgrep/ack2',
--            license     => 'http://www.perlfoundation.org/artistic_license_2_0',
--            repository  => 'git://github.com/beyondgrep/ack2.git',
--            MailingList => 'https://groups.google.com/group/ack-users',
--        }
--    };
--}
--
--if ( $ExtUtils::MakeMaker::VERSION ge '6.48' ) {
--    $parms{MIN_PERL_VERSION} = 5.008008;
--}
--
  WriteMakefile( %parms );
  
  package MY;
  
--# suppress EU::MM test rule
++# Suppress EU::MM test rule.
  sub MY::test {
      return '';
  }
@@@ -90,14 -90,14 +63,37 @@@
  sub MY::postamble {
      my $postamble = sprintf(<<'MAKE_FRAG', $debug_mode);
  ACK    = ack
--ALL_PM = \
--    Ack.pm \
--    Resource.pm Resources.pm \
--    ConfigDefault.pm ConfigFinder.pm ConfigLoader.pm \
--    Filter.pm Extension.pm FirstLineMatch.pm Is.pm Match.pm Default.pm Inverse.pm Collection.pm IsGroup.pm ExtensionGroup.pm MatchGroup.pm IsPath.pm IsPathGroup.pm
++CODE_PM = \
++    lib/App/Ack.pm \
++    \
++    lib/App/Ack/ConfigDefault.pm \
++    lib/App/Ack/ConfigFinder.pm \
++    lib/App/Ack/ConfigLoader.pm \
++    lib/App/Ack/File.pm \
++    lib/App/Ack/Files.pm \
++    lib/App/Ack/Filter.pm \
++    \
++    lib/App/Ack/Filter/Collection.pm \
++    lib/App/Ack/Filter/Default.pm \
++    lib/App/Ack/Filter/Extension.pm \
++    lib/App/Ack/Filter/ExtensionGroup.pm \
++    lib/App/Ack/Filter/FirstLineMatch.pm \
++    lib/App/Ack/Filter/Inverse.pm \
++    lib/App/Ack/Filter/Is.pm \
++    lib/App/Ack/Filter/IsGroup.pm \
++    lib/App/Ack/Filter/IsPath.pm \
++    lib/App/Ack/Filter/IsPathGroup.pm \
++    lib/App/Ack/Filter/Match.pm \
++    lib/App/Ack/Filter/MatchGroup.pm \
++
++DOCS_PM = \
++    lib/App/Ack/Docs/Manual.pm \
++    lib/App/Ack/Docs/FAQ.pm \
++    lib/App/Ack/Docs/Cookbook.pm \
  
  TEST_VERBOSE=0
--TEST_FILES=t/*.t t/lib/*.t
++TEST_UTILS=t/*.pm
++TEST_FILES=t/*.t
  TEST_XT_FILES=xt/*.t
  
  .PHONY: tags critic
@@@ -108,20 -108,20 +104,22 @@@ tags
  		--exclude=.git \
  		--exclude='*~' \
  		--exclude=ack-standalone \
++		--exclude=garage/* \
  		--languages=Perl --langmap=Perl:+.t \
  
++# Don't run critic on docs.
  critic:
--	perlcritic -1 -q -profile perlcriticrc $(ACK) $(ALL_PM) t/*.t t/lib/*.t xt/*.t
++	perlcritic -1 -q -profile perlcriticrc $(ACK) $(CODE_PM) $(TEST_UTILS) $(TEST_FILES) $(TEST_XT_FILES)
  
--ack-standalone : $(ACK) $(ALL_PM) squash Makefile
--	$(PERL) squash $(ACK) $(ALL_PM) File::Next %s > ack-standalone
++ack-standalone : $(ACK) $(CODE_PM) $(DOCS_PM) squash Makefile
++	$(PERL) squash $(ACK) $(CODE_PM) File::Next $(DOCS_PM) %s > ack-standalone
  	$(FIXIN) ack-standalone
  	-$(NOECHO) $(CHMOD) $(PERM_RWX) ack-standalone
  	$(PERL) -c ack-standalone
  
  bininst : $(ACK)
--	$(CP) $(ACK) ~/bin/ack2
--	$(CP) ackrc ~/.ack2rc
++	$(CP) $(ACK) ~/bin/ack3
++	$(CP) ackrc ~/.ack3rc
  
  test: test_classic test_standalone
  
@@@ -146,15 -146,15 +144,6 @@@ TIMER_ARGS=foo ~/parrot > /dev/nul
  time-ack196:
  	time $(PERL) ./garage/ack196 --noenv $(TIMER_ARGS)
  
--time-ack202:
--	time $(PERL) ./garage/ack202 --noenv $(TIMER_ARGS)
--
--time-ack20301:
--	time $(PERL) ./garage/ack20301 --noenv $(TIMER_ARGS)
--
--time-ack20302:
--	time $(PERL) ./garage/ack20302 --noenv $(TIMER_ARGS)
--
  time-head: ack-standalone
  	time $(PERL) ./ack-standalone --noenv $(TIMER_ARGS)
  
diff --cc README.md
index 6bf030c,6bf030c..a8c9a6b
--- a/README.md
+++ b/README.md
@@@ -1,39 -1,39 +1,53 @@@
  # Build status of dev branch
  
--* Linux [![Build Status](https://travis-ci.org/beyondgrep/ack2.png?branch=dev)](https://travis-ci.org/beyondgrep/ack2)
--* Windows [![Build Status](https://ci.appveyor.com/api/projects/status/github/beyondgrep/ack2)](https://ci.appveyor.com/project/petdance/ack2)
--* [CPAN Testers](http://cpantesters.org/distro/A/ack.html)
++* Linux [![Build Status](https://travis-ci.org/beyondgrep/ack3.png?branch=dev)](https://travis-ci.org/beyondgrep/ack3)
++* Windows [![Build Status](https://ci.appveyor.com/api/projects/status/github/beyondgrep/ack3)](https://ci.appveyor.com/project/petdance/ack3)
++<!-- * [CPAN Testers](http://cpantesters.org/distro/A/ack.html) -->
  
--# ack 2
++# ack 3
  
  ack is a code-searching tool, similar to grep but optimized for
--programmers searching large trees of source code.  It runs in pure
--Perl, is highly portable, and runs on any platform that runs Perl.
++programmers searching large trees of source code.  It is highly
++portable and runs on any platform that runs Perl.
  
--ack is written and maintained by Andy Lester (andy at petdance.com).
++ack is written and maintained by Andy Lester (andy at beyondgrep.com).
  
--* Project home page: https://beyondgrep.com/
--* Code home page: https://github.com/beyondgrep/ack2
--* Issue tracker: https://github.com/beyondgrep/ack2/issues
--* Mailing list for announcements: https://groups.google.com/d/forum/ack-announcements
--* Mailing list for users: https://groups.google.com/d/forum/ack-users
--* Mailing list for developers: https://groups.google.com/d/forum/ack-dev
++* [Project home page](https://beyondgrep.com/)
++* [Code home page](https://github.com/beyondgrep/ack3)
++* [Issue tracker](https://github.com/beyondgrep/ack3/issues)
++* Mailing lists
++    * [Announcements](https://groups.google.com/d/forum/ack-announcements)
++    * [Users](https://groups.google.com/d/forum/ack-users)
++    * [Developers](https://groups.google.com/d/forum/ack-dev)
  
  # Building
  
--ack requires Perl 5.8.8 or higher.  Perl 5.8.8 was released January 2006.
++ack requires Perl 5.10.1 or higher.  Perl 5.10.1 was released August 2009.
  
      # Required
      perl Makefile.PL
      make
      make test
--    sudo make install # for a system-wide installation (recommended)
++    sudo make install # For a system-wide installation
      # - or -
      make ack-standalone
--    cp ack-standalone ~/bin/ack2 # for a personal installation
++    cp ack-standalone ~/bin/ack3 # For a personal installation
  
  # Development
  
--[Developer's Guide](DEVELOPERS.md)
++* [How to contribute](CONTRIBUTING.md)
++* [Developer's Guide](DEVELOPERS.md)
++* [Design Guide](DESIGN.md)
  
--[Design Guide](DESIGN.md)
++# Community
++
++See the [Community](https://beyondgrep.com/community/) page.
++
++# License
++
++Copyright 2005-2018 Andy Lester.
++
++This program is free software; you can redistribute it and/or modify
++it under the terms of the
++[Artistic License v2.0](http://www.perlfoundation.org/artistic_license_2_0).
++See also the LICENSE.md file that comes with the ack distribution.
diff --cc ack
index 79f644a,79f644a..ad0b497
--- a/ack
+++ b/ack
@@@ -2,9 -2,9 +2,9 @@@
  
  use strict;
  use warnings;
--our $VERSION = '2.22'; # Check https://beyondgrep.com/ for updates
++our $VERSION = '2.999_01'; # Check https://beyondgrep.com/ for updates
  
--use 5.008008;
++use 5.010001;
  use Getopt::Long 2.38 ();
  use Carp 1.04 ();
  
@@@ -13,11 -13,11 +13,9 @@@ use File::Next ()
  
  use App::Ack ();
  use App::Ack::ConfigLoader ();
--use App::Ack::Resources;
--use App::Ack::Resource ();
++use App::Ack::File ();
++use App::Ack::Files ();
  
--# XXX Don't make this so brute force
--# See also: https://github.com/beyondgrep/ack2/issues/89
  use App::Ack::Filter ();
  use App::Ack::Filter::Default;
  use App::Ack::Filter::Extension;
@@@ -28,33 -28,33 +26,37 @@@ use App::Ack::Filter::IsPath
  use App::Ack::Filter::Match;
  use App::Ack::Filter::Collection;
  
++# Global command-line options
  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_color;
++our $opt_column;
  our $opt_count;
--our $opt_v;
--our $opt_m;
--our $opt_g;
  our $opt_f;
--our $opt_lines;
++our $opt_g;
++our $opt_heading;
  our $opt_L;
  our $opt_l;
++our $opt_lines;
++our $opt_m;
++our $opt_output;
  our $opt_passthru;
--our $opt_column;
++our $opt_print0;
++our $opt_proximate;
++our $opt_regex;
++our $opt_show_filename;
++our $opt_u;
++our $opt_v;
  
  # Flag if we need any context tracking.
  our $is_tracking_context;
  
--# These are all our globals.
++our @special_vars_used_by_opt_output;
++our $special_vars_used_by_opt_output;
  
  MAIN: {
--    $App::Ack::orig_program_name = $0;
++    $App::Ack::ORIGINAL_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" );
@@@ -79,14 -79,14 +81,27 @@@
          my @keys = ( 'ACKRC', grep { /^ACK_/ } keys %ENV );
          delete @ENV{@keys};
      }
--    load_colors();
++
++    # Load colors
++    my $modules_loaded_ok = eval 'use Term::ANSIColor 1.10 (); 1;';
++    if ( $modules_loaded_ok && $App::Ack::is_windows ) {
++        $modules_loaded_ok = eval 'use Win32::Console::ANSI; 1;';
++    }
++    if ( $modules_loaded_ok ) {
++        $ENV{ACK_COLOR_MATCH}    ||= 'black on_yellow';
++        $ENV{ACK_COLOR_FILENAME} ||= 'bold green';
++        $ENV{ACK_COLOR_LINENO}   ||= 'bold yellow';
++        $ENV{ACK_COLOR_COLNO}    ||= 'bold yellow';
++    }
  
      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; },
++        'version'    => sub { App::Ack::print( App::Ack::get_version_statement() ); exit; },
++        'man'        => sub { App::Ack::show_docs( 'Manual' ); }, # man/faq/cookbook all exit.
++        'faq'        => sub { App::Ack::show_docs( 'FAQ' ); },
++        'cookbook'   => sub { App::Ack::show_docs( 'Cookbook' ); },
      );
      Getopt::Long::Configure('default', 'no_auto_help', 'no_auto_version');
  
@@@ -95,9 -95,9 +110,238 @@@
          exit 1;
      }
  
--    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_break          = $opt->{break};
++    $opt_proximate      = $opt->{proximate};
++    $opt_color          = $opt->{color};
++    $opt_column         = $opt->{column};
++    $opt_count          = $opt->{count};
++    $opt_f              = $opt->{f};
++    $opt_g              = $opt->{g};
++    $opt_heading        = $opt->{heading};
++    $opt_L              = $opt->{L};
++    $opt_l              = $opt->{l};
++    $opt_lines          = $opt->{lines};
++    $opt_m              = $opt->{m};
++    $opt_output         = $opt->{output};
++    $opt_passthru       = $opt->{passthru};
++    $opt_print0         = $opt->{print0};
++    $opt_regex          = $opt->{regex};
++    $opt_show_filename  = $opt->{show_filename};
++    $opt_u              = $opt->{u};
++    $opt_v              = $opt->{v};
++
++    $App::Ack::report_bad_filenames = !$opt->{s};
++
++    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 ( defined $opt_output ) {
++        # Expand out \t, \n and \r.
++        $opt_output =~ s/\\n/\n/g;
++        $opt_output =~ s/\\r/\r/g;
++        $opt_output =~ s/\\t/\t/g;
++
++        my @supported_special_variables = ( 1..9, qw( _ . ` & ' +  f ) );
++        @special_vars_used_by_opt_output = grep { $opt_output =~ /\$$_/ } @supported_special_variables;
++        $special_vars_used_by_opt_output = join( '', @special_vars_used_by_opt_output );
++
++        # If the $opt_output contains $&, $` or $', those vars won't be
++        # captured until they're used at least once in the program.
++        # Do the eval to make this happen.
++        for my $i ( @special_vars_used_by_opt_output ) {
++            if ( $i eq q{&} || $i eq q{'} || $i eq q{`} ) {
++                no warnings;    # They will be undef, so don't warn.
++                eval qq{"\$$i"};    ## no critic ( ErrorHandling::RequireCheckingReturnValueOfEval )
++            }
++        }
++    }
++
++    # Set up file filters.
++    my $files;
++    if ( $App::Ack::is_filter_mode && !$opt->{files_from} ) { # probably -x
++        $files     = App::Ack::Files->from_stdin();
++        $opt_regex = shift @ARGV if not defined $opt_regex;
++        $opt_regex = $opt->{regex} = build_regex( $opt_regex, $opt );
++    }
++    else {
++        if ( $opt_f || $opt_lines ) {
++            # No need to check for regex, since mutex options are handled elsewhere.
++        }
++        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} ) {
++            $files = App::Ack::Files->from_file( $opt, $opt->{files_from} );
++            exit 1 unless $files;
++        }
++        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);
++
++            $files = App::Ack::Files->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();
++
++FILES:
++    while ( my $file = $files->next ) {
++        if ($is_tracking_context) {
++            set_up_line_context_for_file();
++        }
++
++        # ack -f
++        if ( $opt_f ) {
++            if ( $opt->{show_types} ) {
++                App::Ack::show_types( $file, $ors );
++            }
++            else {
++                App::Ack::print( $file->name, $ors );
++            }
++            ++$nmatches;
++            last FILES if defined($opt_m) && $nmatches >= $opt_m;
++        }
++        # ack -g
++        elsif ( $opt_g ) {
++            if ( $opt->{show_types} ) {
++                App::Ack::show_types( $file, $ors );
++            }
++            else {
++                local $opt_show_filename = 0; # XXX Why is this local?
++
++                print_line_with_options( '', $file->name, 0, $ors );
++            }
++            ++$nmatches;
++            last FILES if defined($opt_m) && $nmatches >= $opt_m;
++        }
++        # ack --lines
++        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 = $file->name;
++
++            local $opt_color = 0;
++
++            iterate( $file, 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;
++            });
++        }
++        # ack -c
++        elsif ( $opt_count ) {
++            my $matches_for_this_file = count_matches_in_file( $file );
++
++            if ( not $opt_show_filename ) {
++                $total_count += $matches_for_this_file;
++                next FILES;
++            }
++
++            if ( !$opt_l || $matches_for_this_file > 0) {
++                if ( $opt_show_filename ) {
++                    App::Ack::print( $file->name, ':', $matches_for_this_file, $ors );
++                }
++                else {
++                    App::Ack::print( $matches_for_this_file, $ors );
++                }
++            }
++        }
++        # ack -l, ack -L
++        elsif ( $opt_l || $opt_L ) {
++            my $is_match = file_has_match( $file );
++
++            if ( $opt_L ? !$is_match : $is_match ) {
++                App::Ack::print( $file->name, $ors );
++                ++$nmatches;
++
++                last FILES if $only_first;
++                last FILES if defined($opt_m) && $nmatches >= $opt_m;
++            }
++        }
++        # Normal match-showing ack
++        else {
++            $nmatches += print_matches_in_file( $file, $opt );
++            if ( $nmatches && $only_first ) {
++                last FILES;
++            }
++        }
++    }
++
++    if ( $opt_count && !$opt_show_filename ) {
++        App::Ack::print( $total_count, "\n" );
++    }
++
++    close $App::Ack::fh;
++
++    App::Ack::exit_from_ack( $nmatches );
  }
  
++# End of MAIN
++
  sub _compile_descend_filter {
      my ( $opt ) = @_;
  
@@@ -122,8 -122,8 +366,8 @@@
      $idirs = $opt->{idirs};
  
      return sub {
--        my $resource = App::Ack::Resource->new($File::Next::dir);
--        return !grep { $_->filter($resource) } @{$idirs};
++        my $file = App::Ack::File->new($File::Next::dir);
++        return !grep { $_->filter($file) } @{$idirs};
      };
  }
  
@@@ -182,7 -182,7 +426,7 @@@ sub _compile_file_filter 
                  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 $dir_rsrc = App::Ack::File->new(File::Spec->catfile(@dirs[0 .. $i]));
  
                      my $j = 0;
                      for my $filter (@ignore_dir_filter) {
@@@ -220,68 -220,68 +464,22 @@@
              }
          }
  
--        my $resource = App::Ack::Resource->new($File::Next::name);
++        my $file = App::Ack::File->new($File::Next::name);
  
--        if ( $ifiles_filters && $ifiles_filters->filter($resource) ) {
++        if ( $ifiles_filters && $ifiles_filters->filter($file) ) {
              return 0;
          }
  
--        my $match_found = $direct_filters->filter($resource);
++        my $match_found = $direct_filters->filter($file);
  
--        # Don't bother invoking inverse filters unless we consider the current resource a match
--        if ( $match_found && $inverse_filters->filter( $resource ) ) {
++        # Don't bother invoking inverse filters unless we consider the current file a match.
++        if ( $match_found && $inverse_filters->filter( $file ) ) {
              $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
@@@ -294,7 -294,7 +492,7 @@@ sub get_file_id 
      }
      else {
          # XXX Is this the best method? It always hits the FS.
--        if( my ( $dev, $inode ) = (stat($filename))[0, 1] ) {
++        if ( my ( $dev, $inode ) = (stat($filename))[0, 1] ) {
              return join(':', $dev, $inode);
          }
          else {
@@@ -313,30 -313,30 +511,63 @@@ sub build_regex 
  
      defined $str or App::Ack::die( 'No regular expression found.' );
  
++    # Check for lowercaseness before we do any modifications.
++    my $regex_is_lc = $str eq lc $str;
++
      $str = quotemeta( $str ) if $opt->{Q};
++
++    # Whole words only.
      if ( $opt->{w} ) {
--        my $pristine_str = $str;
++        my $ok = 1;
  
--        $str = "(?:$str)";
--        $str = "\\b$str" if $pristine_str =~ /^\w/;
--        $str = "$str\\b" if $pristine_str =~ /\w$/;
++        if ( $str =~ /^\\[wd]/ ) {
++            # Explicit \w is good.
++        }
++        else {
++            # Can start with \w, (, [ or dot.
++            if ( $str !~ /^[\w\(\[\.]/ ) {
++                $ok = 0;
++            }
++        }
++
++        # Can end with \w, }, ), ], +, *, or dot.
++        if ( $str !~ /[\w\}\)\]\+\*\?\.]$/ ) {
++            $ok = 0;
++        }
++        # ... unless it's escaped.
++        elsif ( $str =~ /\\[\}\)\]\+\*\?\.]$/ ) {
++            $ok = 0;
++        }
++
++        if ( !$ok ) {
++            App::Ack::die( '-w will not do the right thing if your regex does not begin and end with a word character.' );
++        }
++
++        if ( $str =~ /^\w+$/ ) {
++            # No need for fancy regex if it's a simple word.
++            $str = sprintf( '\b(?:%s)\b', $str );
++        }
++        else {
++            $str = sprintf( '(?:^|\b|\s)\K(?:%s)(?=\s|\b|$)', $str );
++        }
      }
  
--    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  $@";
++        my $err = $@;
++        chomp $err;
++        App::Ack::die( "Invalid regex '$str':\n  $err" );
      }
  
      return $re;
  
  }
  
--my $match_column_number;
++my $match_colno;
  
  {
  
@@@ -353,16 -353,16 +584,12 @@@ my $before_context_pos
  my $after_context_pending;
  
  # Number of latest line that got printed
--my $printed_line_no;
++my $printed_lineno;
  
  my $is_iterating;
  
  my $is_first_match;
--my $has_printed_something;
--
--BEGIN {
--    $has_printed_something = 0;
--}
++state $has_printed_something = 0;
  
  # Set up context tracking variables.
  sub set_up_line_context {
@@@ -381,7 -381,7 +608,7 @@@
  
  # Adjust context tracking variables when entering a new file.
  sub set_up_line_context_for_file {
--    $printed_line_no = 0;
++    $printed_lineno = 0;
      $after_context_pending = 0;
      if ( $opt_heading && !$opt_lines ) {
          $is_first_match = 1;
@@@ -406,19 -406,19 +633,19 @@@ must also happen there
  
  =cut
  
--sub print_matches_in_resource {
--    my ( $resource ) = @_;
++sub print_matches_in_file {
++    my ( $file ) = @_;
  
--    my $max_count      = $opt_m || -1;
--    my $nmatches       = 0;
--    my $filename       = $resource->name;
--    my $ors            = $opt_print0 ? "\0" : "\n";
++    my $max_count = $opt_m || -1;   # Go negative for no limit so it can never reduce to 0.
++    my $nmatches  = 0;
++    my $filename  = $file->name;
++    my $ors       = $opt_print0 ? "\0" : "\n";
  
--    my $has_printed_for_this_resource = 0;
++    my $has_printed_for_this_file = 0;
  
      $is_iterating = 1;
  
--    my $fh = $resource->open();
++    my $fh = $file->open;
      if ( !$fh ) {
          if ( $App::Ack::report_bad_filenames ) {
              App::Ack::warn( "$filename: $!" );
@@@ -435,8 -435,8 +662,9 @@@
      if ( $is_tracking_context ) {
          $after_context_pending = 0;
          while ( <$fh> ) {
++            chomp;
              if ( does_match( $_ ) && $max_count ) {
--                if ( !$has_printed_for_this_resource ) {
++                if ( !$has_printed_for_this_file ) {
                      if ( $opt_break && $has_printed_something ) {
                          App::Ack::print_blank_line();
                      }
@@@ -445,38 -445,38 +673,37 @@@
                      }
                  }
                  print_line_with_context( $filename, $_, $. );
--                $has_printed_for_this_resource = 1;
++                $has_printed_for_this_file = 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 ) {
++                if ( $opt_break && !$has_printed_for_this_file && $has_printed_something ) {
                      App::Ack::print_blank_line();
                  }
                  print_line_with_options( $filename, $_, $., ':' );
--                $has_printed_for_this_resource = 1;
++                $has_printed_for_this_file = 1;
              }
              else {
--                chomp; # XXX Proper newline handling?
                  print_line_if_context( $filename, $_, $., '-' );
              }
  
              last if ($max_count == 0) && ($after_context_pending == 0);
          }
      }
--    else {
++    else {  # Not tracking context
          if ( $opt_passthru ) {
--            local $_;
++            local $_ = undef;
  
              while ( <$fh> ) {
--                $match_column_number = undef;
++                chomp;
++                $match_colno = undef;
                  if ( $opt_v ? !/$opt_regex/o : /$opt_regex/o ) {
                      if ( !$opt_v ) {
--                        $match_column_number = $-[0] + 1;
++                        $match_colno = $-[0] + 1;
                      }
--                    if ( !$has_printed_for_this_resource ) {
++                    if ( !$has_printed_for_this_file ) {
                          if ( $opt_break && $has_printed_something ) {
                              App::Ack::print_blank_line();
                          }
@@@ -485,28 -485,28 +712,28 @@@
                          }
                      }
                      print_line_with_context( $filename, $_, $. );
--                    $has_printed_for_this_resource = 1;
++                    $has_printed_for_this_file = 1;
                      $nmatches++;
                      $max_count--;
                  }
                  else {
--                    chomp; # XXX proper newline handling?
--                    if ( $opt_break && !$has_printed_for_this_resource && $has_printed_something ) {
++                    if ( $opt_break && !$has_printed_for_this_file && $has_printed_something ) {
                          App::Ack::print_blank_line();
                      }
                      print_line_with_options( $filename, $_, $., ':' );
--                    $has_printed_for_this_resource = 1;
++                    $has_printed_for_this_file = 1;
                  }
--                last unless $max_count != 0;
++                last if $max_count == 0;
              }
          }
          elsif ( $opt_v ) {
--            local $_;
++            local $_ = undef;
  
--            $match_column_number = undef;
++            $match_colno = undef;
              while ( <$fh> ) {
++                chomp;
                  if ( !/$opt_regex/o ) {
--                    if ( !$has_printed_for_this_resource ) {
++                    if ( !$has_printed_for_this_file ) {
                          if ( $opt_break && $has_printed_something ) {
                              App::Ack::print_blank_line();
                          }
@@@ -515,21 -515,21 +742,23 @@@
                          }
                      }
                      print_line_with_context( $filename, $_, $. );
--                    $has_printed_for_this_resource = 1;
++                    $has_printed_for_this_file = 1;
                      $nmatches++;
                      $max_count--;
                  }
--                last unless $max_count != 0;
++                last if $max_count == 0;
              }
          }
          else {
--            local $_;
++            local $_ = undef;
  
++            my $last_match_lineno;
              while ( <$fh> ) {
--                $match_column_number = undef;
++                chomp;
++                $match_colno = undef;
                  if ( /$opt_regex/o ) {
--                    $match_column_number = $-[0] + 1;
--                    if ( !$has_printed_for_this_resource ) {
++                    $match_colno = $-[0] + 1;
++                    if ( !$has_printed_for_this_file ) {
                          if ( $opt_break && $has_printed_something ) {
                              App::Ack::print_blank_line();
                          }
@@@ -537,13 -537,13 +766,24 @@@
                              App::Ack::print_filename( $display_filename, $ors );
                          }
                      }
++                    if ( $opt_proximate ) {
++                        if ( $last_match_lineno ) {
++                            if ( $. > $last_match_lineno + $opt_proximate ) {
++                                App::Ack::print_blank_line();
++                            }
++                        }
++                        elsif ( !$opt_break && $has_printed_something ) {
++                            App::Ack::print_blank_line();
++                        }
++                    }
                      s/[\r\n]+$//g;
                      print_line_with_options( $filename, $_, $., ':' );
--                    $has_printed_for_this_resource = 1;
++                    $has_printed_for_this_file = 1;
                      $nmatches++;
                      $max_count--;
++                    $last_match_lineno = $.;
                  }
--                last unless $max_count != 0;
++                last if $max_count == 0;
              }
          }
  
@@@ -554,117 -554,117 +794,142 @@@
      return $nmatches;
  }
  
++
  sub print_line_with_options {
--    my ( $filename, $line, $line_no, $separator ) = @_;
++    my ( $filename, $line, $lineno, $separator ) = @_;
  
      $has_printed_something = 1;
--    $printed_line_no = $line_no;
++    $printed_lineno = $lineno;
  
      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});
++    # Figure out how many spaces are used per line for the ANSI coloring.
++    state $chars_used_by_coloring;
++    if ( !defined($chars_used_by_coloring) ) {
++        $chars_used_by_coloring = 0;
++        if ( $opt_color ) {
++            my $filename_uses = length( Term::ANSIColor::colored( 'x', $ENV{ACK_COLOR_FILENAME} ) ) - 1;
++            my $lineno_uses   = length( Term::ANSIColor::colored( 'x', $ENV{ACK_COLOR_LINENO} ) ) - 1;
++            if ( $opt_heading ) {
++                $chars_used_by_coloring = $lineno_uses;
++            }
++            else {
++                $chars_used_by_coloring = $filename_uses + $lineno_uses;
++            }
++            if ( $opt_column ) {
++                $chars_used_by_coloring += length( Term::ANSIColor::colored( 'x', $ENV{ACK_COLOR_LINENO} ) ) - 1;
++            }
++        }
      }
  
--    if($opt_show_filename) {
--        if( $opt_heading ) {
--            push @line_parts, $line_no;
++    if ( $opt_show_filename ) {
++        my $colno;
++        $colno = get_match_colno() if $opt_column;
++        if ( $opt_color ) {
++            $filename = Term::ANSIColor::colored( $filename, $ENV{ACK_COLOR_FILENAME} );
++            $lineno   = Term::ANSIColor::colored( $lineno,   $ENV{ACK_COLOR_LINENO} );
++            $colno    = Term::ANSIColor::colored( $colno,    $ENV{ACK_COLOR_COLNO} ) if $opt_column;
          }
--        else {
--            push @line_parts, $filename, $line_no;
++        if ( $opt_heading ) {
++            push @line_parts, $lineno;
++            push @line_parts, $colno if $opt_column;
          }
--
--        if( $opt_column ) {
--            push @line_parts, get_match_column();
++        else {
++            push @line_parts, $filename, $lineno;
++            push @line_parts, $colno if $opt_column;
          }
      }
--    if( $opt_output ) {
++
++    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;
++            no strict;
++
++            my $output = $opt_output;
++
++            # Stash copies of the special variables because we can't rely
++            # on them not changing in the process of doing the s///.
++            my %keep = map { ($_ => ${$_} // '') } @special_vars_used_by_opt_output;
++            $keep{_} = $line if exists $keep{_}; # Manually set it because $_ gets reset in a map.
++            $keep{f} = $filename if exists $keep{f};
++            $output =~ s/\$([$special_vars_used_by_opt_output])/$keep{$1}/ego;
              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;
++        my $underline = '';
  
--                    last if $-[0] == $+[0];
++        # We have to do underlining before any highlighting because highlighting modifies string length.
++        if ( $opt_u ) {
++            while ( $line =~ /$opt_regex/og ) {
++                my $match_start = $-[0];
++                next unless defined($match_start);
  
--                    for ( my $i = 1; $i < @+; $i++ ) {
--                        my ( $match_start, $match_end ) = ( $-[$i], $+[$i] );
++                my $match_end = $+[0];
++                my $match_length = $match_end - $match_start;
++                last if $match_length <= 0;
  
--                        next unless defined($match_start);
--                        next if $match_start < $previous_match_end;
++                my $spaces_needed = $match_start - length $underline;
  
--                        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;
--                }
++                $underline .= (' ' x $spaces_needed);
++                $underline .= ('^' x $match_length);
              }
--            else {
--                my $matched = 0; # If matched, need to escape afterwards.
++        }
++        if ( $opt_color ) {
++            my $highlighted = 0; # If highlighted, need to escape afterwards.
  
--                while ( $line =~ /$opt_regex/og ) {
++            while ( $line =~ /$opt_regex/og ) {
++                my $match_start = $-[0];
++                next unless defined($match_start);
  
--                    $matched = 1;
--                    my ( $match_start, $match_end ) = ($-[0], $+[0]);
--                    next unless defined($match_start);
--                    last if $match_start == $match_end;
++                my $match_end = $+[0];
++                my $match_length = $match_end - $match_start;
++                last if $match_length <= 0;
  
--                    my $substring = substr( $line, $match_start,
--                        $match_end - $match_start );
--                    my $substitution = Term::ANSIColor::colored( $substring,
--                        $ENV{ACK_COLOR_MATCH} );
++                if ( $opt_color ) {
++                    my $substring    = substr( $line, $match_start, $match_length );
++                    my $substitution = Term::ANSIColor::colored( $substring, $ENV{ACK_COLOR_MATCH} );
  
--                    substr( $line, $match_start, $match_end - $match_start,
--                        $substitution );
++                    # Fourth argument replaces the string specified by the first three.
++                    substr( $line, $match_start, $match_length, $substitution );
  
--                    pos($line) = $match_end +
--                    (length( $substitution ) - length( $substring ));
++                    # Move the offset of where /g left off forward the number of spaces of highlighting.
++                    pos($line) = $match_end + (length( $substitution ) - length( $substring ));
++                    $highlighted = 1;
                  }
--                # XXX Why do we do this?
--                $line .= "\033[0m\033[K" if $matched;
              }
++            # Reset formatting and delete everything to the end of the line.
++            $line .= "\e[0m\e[K" if $highlighted;
          }
  
          push @line_parts, $line;
          App::Ack::print( join( $separator, @line_parts ), $ors );
++
++        if ( $underline ne '' ) {
++            pop @line_parts; # Leave only the stuff on the left.
++            if ( @line_parts ) {
++                my $stuff_on_the_left = join( $separator, @line_parts );
++                my $spaces_needed = length($stuff_on_the_left) - $chars_used_by_coloring + 1;
++
++                App::Ack::print( ' ' x $spaces_needed );
++            }
++            App::Ack::print( $underline, $ors );
++        }
      }
  
      return;
  }
  
  sub iterate {
--    my ( $resource, $cb ) = @_;
++    my ( $file, $cb ) = @_;
  
      $is_iterating = 1;
  
--    my $fh = $resource->open();
++    my $fh = $file->open;
      if ( !$fh ) {
          if ( $App::Ack::report_bad_filenames ) {
--            App::Ack::warn( $resource->name . ': ' . $! );
++            App::Ack::warn( $file->name . ': ' . $! );
          }
          return;
      }
@@@ -678,7 -678,7 +943,7 @@@
          }
      }
      else {
--        local $_;
++        local $_ = undef;
  
          while ( <$fh> ) {
              last unless $cb->();
@@@ -691,17 -691,17 +956,16 @@@
  }
  
  sub print_line_with_context {
--    my ( $filename, $matching_line, $line_no ) = @_;
++    my ( $filename, $matching_line, $lineno ) = @_;
  
--    my $ors                 = $opt_print0 ? "\0" : "\n";
--    my $is_tracking_context = $opt_after_context || $opt_before_context;
++    my $ors = $opt_print0 ? "\0" : "\n";
  
      $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 ) ) {
++    if ( $opt_after_context || $opt_before_context ) {
++        my $before_unprinted = $lineno - $printed_lineno - 1;
++        if ( !$is_first_match && ( !$printed_lineno || $before_unprinted > $n_before_ctx_lines ) ) {
              App::Ack::print('--', $ors);
          }
  
@@@ -718,12 -718,12 +982,12 @@@
              # 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, '-' );
++            print_line_with_options( $filename, $line, $lineno-$before_unprinted, '-' );
              $before_unprinted--;
          }
      }
  
--    print_line_with_options( $filename, $matching_line, $line_no, ':' );
++    print_line_with_options( $filename, $matching_line, $lineno, ':' );
  
      # We want to get the next $n_after_ctx_lines printed.
      $after_context_pending = $n_after_ctx_lines;
@@@ -735,12 -735,12 +999,12 @@@
  
  # 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 ) = @_;
++    my ( $filename, $line, $lineno, $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 );
++        print_line_with_options( $filename, $line, $lineno, $separator );
          --$after_context_pending;
      }
      elsif ( $n_before_ctx_lines ) {
@@@ -758,7 -758,7 +1022,7 @@@
  
  =begin Developers
  
--This subroutine is inlined a few places in print_matches_in_resource
++This subroutine is inlined a few places in C<print_matches_in_file>
  for performance reasons, so any changes here must be copied there as
  well.
  
@@@ -769,7 -769,7 +1033,7 @@@
  sub does_match {
      my ( $line ) = @_;
  
--    $match_column_number = undef;
++    $match_colno = undef;
  
      if ( $opt_v ) {
          return ( $line !~ /$opt_regex/o );
@@@ -778,7 -778,7 +1042,7 @@@
          if ( $line =~ /$opt_regex/o ) {
              # @- = @LAST_MATCH_START
              # @+ = @LAST_MATCH_END
--            $match_column_number = $-[0] + 1;
++            $match_colno = $-[0] + 1;
              return 1;
          }
          else {
@@@ -787,22 -787,22 +1051,23 @@@
      }
  }
  
--sub get_match_column {
--    return $match_column_number;
++sub get_match_colno {
++    return $match_colno;
  }
  
--sub resource_has_match {
--    my ( $resource ) = @_;
++sub file_has_match {
++    my ( $file ) = @_;
  
      my $has_match = 0;
--    my $fh = $resource->open();
++    my $fh = $file->open();
      if ( !$fh ) {
          if ( $App::Ack::report_bad_filenames ) {
--            App::Ack::warn( $resource->name . ': ' . $! );
++            App::Ack::warn( $file->name . ': ' . $! );
          }
      }
      else {
          while ( <$fh> ) {
++            chomp;
              if (/$opt_regex/o xor $opt_v) {
                  $has_match = 1;
                  last;
@@@ -814,14 -814,14 +1079,14 @@@
      return $has_match;
  }
  
--sub count_matches_in_resource {
--    my ( $resource ) = @_;
++sub count_matches_in_file {
++    my ( $file ) = @_;
  
      my $nmatches = 0;
--    my $fh = $resource->open();
++    my $fh = $file->open;
      if ( !$fh ) {
          if ( $App::Ack::report_bad_filenames ) {
--            App::Ack::warn( $resource->name . ': ' . $! );
++            App::Ack::warn( $file->name . ': ' . $! );
          }
      }
      else {
@@@ -833,1555 -833,1555 +1098,3 @@@
  
      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<grep> 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<http://perldoc.perl.org/perlreref.html|perlreref>.  If you don't know
--how to use regular expression but are interested in learning, you may
--consult L<http://perldoc.perl.org/perlretut.html|perlretut>.  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<ack> delves into
--subdirectories selecting files for searching.
--
--I<ack> is intelligent about the files it searches.  It knows about
--certain file types, based on both the extension on the file and,
--in some cases, the contents of the file.  These selections can be
--made with the B<--type> option.
--
--With no file selection, I<ack> searches through regular files that
--are not explicitly excluded by B<--ignore-dir> and B<--ignore-file>
--options, either present in F<ackrc> files or on the command line.
--
--The default options for I<ack> ignore certain files and directories.  These
--include:
--
--=over 4
--
--=item * Backup files: Files matching F<#*#> or ending with F<~>.
--
--=item * Coredumps: Files matching F<core.\d+>
--
--=item * Version control directories like F<.svn> and F<.git>.
--
--=back
--
--Run I<ack> with the C<--dump> option to see what settings are set.
--
--However, I<ack> always searches the files given on the command line,
--no matter what type.  If you tell I<ack> to search in a coredump,
--it will search in a coredump.
--
--=head1 DIRECTORY SELECTION
--
--I<ack> 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<ack --dump>.
--
--=head1 WHEN TO USE GREP
--
--I<ack> trumps I<grep> as an everyday tool 99% of the time, but don't
--throw I<grep> away, because there are times you'll still need it.
--
--E.g., searching through huge files looking for regexes that can be
--expressed with I<grep> syntax should be quicker with I<grep>.
--
--If your script or parent program uses I<grep> C<--quiet> or C<--silent>
--or needs exit 2 on IO error, use I<grep>.
--
--=head1 OPTIONS
--
--=over 4
--
--=item B<--ackrc>
--
--Specifies an ackrc file to load after all others; see L</"ACKRC LOCATION SEMANTICS">.
--
--=item B<-A I<NUM>>, B<--after-context=I<NUM>>
--
--Print I<NUM> lines of trailing context after matching lines.
--
--=item B<-B I<NUM>>, B<--before-context=I<NUM>>
--
--Print I<NUM> lines of leading context before matching lines.
--
--=item B<--[no]break>
--
--Print a break between results from different files. On by default
--when used interactively.
--
--=item B<-C [I<NUM>]>, B<--context[=I<NUM>]>
--
--Print I<NUM> 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<Win32::Console::ANSI> module is installed or the C<ACK_PAGER_COLOR>
--environment variable is used.
--
--=item B<--color-filename=I<color>>
--
--Sets the color to be used for filenames.
--
--=item B<--color-match=I<color>>
--
--Sets the color to be used for matches.
--
--=item B<--color-lineno=I<color>>
--
--Sets the color to be used for line numbers.
--
--=item B<--[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<ack>
--considers F<.ackrc> and settings in the environment.
--
--=item B<--flush>
--
--B<--flush> flushes output immediately.  This is off by default
--unless ack is running interactively (when output goes to a pipe or
--file).
--
--=item B<-f>
--
--Only print the files that would be searched, without actually doing
--any searching.  PATTERN must not be specified, or it will be taken
--as a path to search.
--
--=item B<--files-from=I<FILE>>
--
--The list of files to be searched is specified in I<FILE>.  The list of
--files are separated by newlines.  If I<FILE> 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<PATTERN>>
--
--Print searchable files where the relative path + filename matches
--I<PATTERN>.
--
--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<really> want
--to customize ack.
--
--=item B<--[no]ignore-dir=I<DIRNAME>>, B<--[no]ignore-directory=I<DIRNAME>>
--
--Ignore directory (as CVS, .svn, etc are ignored). May be used
--multiple times to ignore multiple directories. For example, mason
--users may wish to include B<--ignore-dir=data>. The B<--noignore-dir>
--option allows users to search directories which would normally be
--ignored (perhaps to research the contents of F<.svn/props> directories).
--
--The I<DIRNAME> must always be a simple directory name. Nested
--directories like F<foo/bar> are NOT supported. You would need to
--specify B<--ignore-dir=foo> and then no files from any foo directory
--are taken into account by ack unless given explicitly on the command
--line.
--
--=item B<--ignore-file=I<FILTERTYPE:FILTERARGS>>
--
--Ignore files matching I<FILTERTYPE:FILTERARGS>.  The filters are specified
--identically to file type filters as seen in L</"Defining your own types">.
--
--=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<NUM>>
--
--Only print line I<NUM> 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<NOT> match.
--
--=item B<--match I<PATTERN>>
--
--Specify the I<PATTERN> explicitly. This is helpful if you don't want to put the
--regex as your first argument, e.g. when executing multiple searches over the
--same set of files.
--
--    # search for foo and bar in given files
--    ack file1 t/file* --match foo
--    ack file1 t/file* --match bar
--
--=item B<-m=I<NUM>>, B<--max-count=I<NUM>>
--
--Stop reading a file after I<NUM> matches.
--
--=item B<--man>
--
--Print this manual page.
--
--=item B<-n>, B<--no-recurse>
--
--No descending into subdirectories.
--
--=item B<-o>
--
--Show only the part of each line matching PATTERN (turns off text
--highlighting)
--
--=item B<--output=I<expr>>
--
--Output the evaluation of I<expr> for each line (turns off text
--highlighting)
--If PATTERN matches more than once then a line is output for each non-overlapping match.
--For more information please see the section L</"Examples of F<--output>">.
--
--=item B<--pager=I<program>>, B<--nopager>
--
--B<--pager> directs ack's output through I<program>.  This can also be specified
--via the C<ACK_PAGER> and C<ACK_PAGER_COLOR> environment variables.
--
--Using --pager does not suppress grouping and coloring like piping
--output on the command-line does.
--
--B<--nopager> cancels any setting in ~/.ackrc, C<ACK_PAGER> or C<ACK_PAGER_COLOR>.
--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<smartcase> 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<ack>.
--
--=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<perl> or I<xml>.  B<--type=perl> can
--also be specified as B<--perl>, and B<--type=noperl> can be done
--as B<--noperl>.
--
--If a file is of both type "foo" and "bar", specifying --foo and
----nobar will exclude the file, because an exclusion takes precedence
--over an inclusion.
--
--Type specifications can be repeated and are ORed together.
--
--See I<ack --help=types> for a list of valid types.
--
--=item B<--type-add I<TYPE>:I<FILTER>:I<FILTERARGS>>
--
--Files with the given FILTERARGS applied to the given FILTER
--are recognized as being of (the existing) type TYPE.
--See also L</"Defining your own types">.
--
--
--=item B<--type-set I<TYPE>:I<FILTER>:I<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.  See
--also L</"Defining your own types">.
--
--=item B<--type-del I<TYPE>>
--
--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<ox> will not
--match the strings C<box> or C<oxen>.  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<line>
--in the F<.ackrc> file is interpreted as one element of C<@ARGV>.
--
--F<ack> looks in several locations for F<.ackrc> files; the searching
--process is detailed in L</"ACKRC LOCATION SEMANTICS">.  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<ack --perl foo> searches for foo in all perl files. I<ack --help=types>
--tells you, that perl files are files ending
--in .pl, .pm, .pod or .t. So what if you would like to include .xs
--files as well when searching for --perl files? I<ack --type-add perl:ext:xs --perl foo>
--does this for you. B<--type-add> appends
--additional extensions to an existing type.
--
--If you want to define a new type, or completely redefine an existing
--type, then use B<--type-set>. I<ack --type-set eiffel:ext:e,eiffel> defines
--the type I<eiffel> to include files with
--the extensions .e or .eiffel. So to search for all eiffel files
--containing the word Bertrand use I<ack --type-set eiffel:ext:e,eiffel --eiffel Bertrand>.
--As usual, you can also write B<--type=eiffel>
--instead of B<--eiffel>. Negation also works, so B<--noeiffel> excludes
--all eiffel files from a search. Redefining also works: I<ack --type-set cc:ext:c,h>
--and I<.xs> files no longer belong to the type I<cc>.
--
--When defining your own types in the F<.ackrc> file you have to use
--the following:
--
--  --type-set=eiffel:ext:e,eiffel
--
--or writing on separate lines
--
--  --type-set
--  eiffel:ext:e,eiffel
--
--The following does B<NOT> 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<ack --type-set backup:ext:bak --type-add perl:ext:perl --help-types>
--
--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<FILTERARGS> depends on the value
--of I<FILTER>.
--
--=over 4
--
--=item is:I<FILENAME>
--
--I<is> 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<EXTENSION>[,I<EXTENSION2>[,...]]
--
--I<ext> 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<PATTERN>
--
--I<match> 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<PATTERN>
--
--I<firstlinematch> matches the first line of the target file against a
--regular expression.  Like I<match>, 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<ack> looks in the default location.
--
--=item ACK_OPTIONS
--
--This variable specifies default options to be placed in front of
--any explicit options on the command line.
--
--=item ACK_COLOR_FILENAME
--
--Specifies the color of the filename when it's printed in B<--group>
--mode.  By default, it's "bold green".
--
--The recognized attributes are clear, reset, dark, bold, underline,
--underscore, blink, reverse, concealed black, red, green, yellow,
--blue, magenta, on_black, on_red, on_green, on_yellow, on_blue,
--on_magenta, on_cyan, and on_white.  Case is not significant.
--Underline and underscore are equivalent, as are clear and reset.
--The color alone sets the foreground color, and on_color sets the
--background color.
--
--This option can also be set with B<--color-filename>.
--
--=item ACK_COLOR_MATCH
--
--Specifies the color of the matching text when printed in B<--color>
--mode.  By default, it's "black on_yellow".
--
--This option can also be set with B<--color-match>.
--
--See B<ACK_COLOR_FILENAME> for the color specifications.
--
--=item ACK_COLOR_LINENO
--
--Specifies the color of the line number when printed in B<--color>
--mode.  By default, it's "bold yellow".
--
--This option can also be set with B<--color-lineno>.
--
--See B<ACK_COLOR_FILENAME> for the color specifications.
--
--=item ACK_PAGER
--
--Specifies a pager program, such as C<more>, C<less> or C<most>, to which
--ack will send its output.
--
--Using C<ACK_PAGER> does not suppress grouping and coloring like
--piping output on the command-line does, except that on Windows
--ack will assume that C<ACK_PAGER> does not support color.
--
--C<ACK_PAGER_COLOR> overrides C<ACK_PAGER> if both are specified.
--
--=item ACK_PAGER_COLOR
--
--Specifies a pager program that understands ANSI color sequences.
--Using C<ACK_PAGER_COLOR> does not suppress grouping and coloring
--like piping output on the command-line does.
--
--If you are not on Windows, you never need to use C<ACK_PAGER_COLOR>.
--
--=back
--
--=head1 AVAILABLE COLORS
--
--F<ack> uses the colors available in Perl's L<Term::ANSIColor> 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<ack> integrates easily with the Vim text editor. Set this in your
--F<.vimrc> to use F<ack> instead of F<grep>:
--
--    set grepprg=ack\ -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<ack> 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<https://beyondgrep.com/more-tools/>.
--
--=head2 Shell and Return Code
--
--For greater compatibility with I<grep>, I<ack> in normal use returns
--shell return or exit code of 0 only if something is found and 1 if
--no match is found.
--
--(Shell exit code 1 is C<$?=256> in perl with C<system> or backticks.)
--
--The I<grep> code 2 for errors is not used.
--
--If C<-f> or C<-g> are specified, then 0 is returned if at least one
--file is found.  If no files are found, then 1 is returned.
--
--=cut
--
--=head1 DEBUGGING ACK PROBLEMS
--
--If ack gives you output you're not expecting, start with a few simple steps.
--
--=head2 Use B<--noenv>
--
--Your environment variables and F<.ackrc> may be doing things you're
--not expecting, or forgotten you specified.  Use B<--noenv> to ignore
--your environment and F<.ackrc>.
--
--=head2 Use B<-f> to see what files 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<ack -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<ack -f --perl> will create a
--list of all the Perl files in a tree, ideal for sending into F<xargs>.
--For example:
--
--    # Change all "this" to "that" in all Perl files in a tree.
--    ack -f --perl | xargs perl -p -i -e's/this/that/g'
--
--or if you prefer:
--
--    perl -p -i -e's/this/that/g' $(ack -f --perl)
--
--=head2 Use F<-Q> when in doubt about metacharacters
--
--If you're searching for something with a regular expression
--metacharacter, most often a period in a filename or IP address, add
--the -Q to avoid false positives without all the backslashing.  See
--the following example for more...
--
--=head2 Use ack to watch log files
--
--Here's one I used the other day to find trouble spots for a website
--visitor.  The user had a problem loading F<troublesome.gif>, so I
--took the access log and scanned it with ack twice.
--
--    ack -Q aa.bb.cc.dd /path/to/access.log | ack -Q -B5 troublesome.gif
--
--The first ack finds only the lines in the Apache log for the given
--IP.  The second finds the match on my troublesome GIF, and shows
--the previous five lines from the log in each case.
--
--=head2 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<http://perldoc.perl.org/perlvar.html#Variables-related-to-regular-expressions|perlvar>.
--
--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<https://beyondgrep.com/community/> 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<ack -f> 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<If ack 1.x doesn't know what kind of file it is,
--ack ignores the file.>  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<ack> did search & replace?
--
--No, ack will always be read-only.  Perl has a perfectly good way
--to do search & replace in files, using the C<-i>, C<-p> and C<-n>
--switches.
--
--You can certainly use ack to select your files to update.  For
--example, to change all "foo" to "bar" in all PHP files, you can do
--this from the Unix shell:
--
--    $ perl -i -p -e's/foo/bar/g' $(ack -f --php)
--
--=head2 Can I make ack recognize F<.xyz> files?
--
--Yes!  Please see L</"Defining your own types">.  If you think
--that F<ack> should recognize a type by default, please see
--L</"ENHANCEMENTS">.
--
--=head2 There's already a program/package called ack.
--
--Yes, I know.
--
--=head2 Why is it called ack if it's called ack-grep?
--
--The name of the program is "ack".  Some packagers have called it
--"ack-grep" when creating packages because there's already a package
--out there called "ack" that has nothing to do with this ack.
--
--I suggest you make a symlink named F<ack> that points to F<ack-grep>
--because one of the crucial benefits of ack is having a name that's
--so short and simple to type.
--
--To do that, run this with F<sudo> or as root:
--
--   ln -s /usr/bin/ack-grep /usr/bin/ack
--
--Alternatively, you could use a shell alias:
--
--    # bash/zsh
--    alias ack=ack-grep
--
--    # csh
--    alias ack ack-grep
--
--=head2 What does F<ack> mean?
--
--Nothing.  I wanted a name that was easy to type and that you could
--pronounce as a single syllable.
--
--=head2 Can I do multi-line regexes?
--
--No, ack does not support regexes that match multiple lines.  Doing
--so would require reading in the entire file at a time.
--
--If you want to see lines near your match, use the C<--A>, C<--B>
--and C<--C> switches for displaying context.
--
--=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</etc/ackrc> on Unix-like systems.
--
--Under Windows XP and earlier, the global ackrc is at
--C<C:\Documents and Settings\All Users\Application Data\ackrc>
--
--Under Windows Vista/7, the global ackrc is at
--C<C:\ProgramData\ackrc>
--
--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<C:\Documents and Settings\$USER\Application Data\ackrc>.
--
--Under Windows Vista/7, the user's ackrc is at
--C<C:\Users\$USER\AppData\Roaming\ackrc>.
--
--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<ACK_OPTIONS>.  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</"Defining your own types">.
--
--=item *
--
--ack now loads multiple ackrc files; see L</"ACKRC LOCATION SEMANTICS"> 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<< <andy at petdance.com> >>
--
--=head1 BUGS
--
--Please report any bugs or feature requests to the issues list at
--Github: L<https://github.com/beyondgrep/ack2/issues>
--
--=head1 ENHANCEMENTS
--
--All enhancement requests MUST first be posted to the ack-users
--mailing list at L<http://groups.google.com/group/ack-users>.  I
--will not consider a request without it first getting seen by other
--ack users.  This includes requests for new filetypes.
--
--There is a list of enhancements I want to make to F<ack> in the ack
--issues list at Github: L<https://github.com/beyondgrep/ack2/issues>
--
--Patches are always welcome, but patches with tests get the most
--attention.
--
--=head1 SUPPORT
--
--Support for and information about F<ack> can be found at:
--
--=over 4
--
--=item * The ack homepage
--
--L<https://beyondgrep.com/>
--
--=item * The ack-users mailing list
--
--L<http://groups.google.com/group/ack-users>
--
--=item * The ack issues list at Github
--
--L<https://github.com/beyondgrep/ack2/issues>
--
--=item * AnnoCPAN: Annotated CPAN documentation
--
--L<http://annocpan.org/dist/ack>
--
--=item * CPAN Ratings
--
--L<http://cpanratings.perl.org/d/ack>
--
--=item * Search CPAN
--
--L<http://search.cpan.org/dist/ack>
--
--=item * MetaCPAN
--
--L<http://metacpan.org/release/ack>
--
--=item * Git source repository
--
--L<https://github.com/beyondgrep/ack2>
--
--=back
--
--=head1 ACKNOWLEDGEMENTS
--
--How appropriate to have I<ack>nowledgements!
--
--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,
--SE<eacute>bastien FeugE<egrave>re,
--Jakub Wilk,
--Pete Houston,
--Stephen Thirlwall,
--Jonah Bishop,
--Chris Rebert,
--Denis Howe,
--RaE<uacute>l GundE<iacute>n,
--James McCoy,
--Daniel Perrett,
--Steven Lee,
--Jonathan Perret,
--Fraser Tweedale,
--RaE<aacute>l GundE<aacute>n,
--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,
--GE<aacute>bor SzabE<oacute>,
--Tod Hagan,
--Michael Hendricks,
--E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason,
--Piers Cawley,
--Stephen Steneker,
--Elias Lutfallah,
--Mark Leighton Fisher,
--Matt Diephouse,
--Christian Jaeger,
--Bill Sully,
--Bill Ricker,
--David Golden,
--Nilson Santos F. Jr,
--Elliot Shank,
--Merijn Broeren,
--Uwe Voelker,
--Rick Scott,
--Ask BjE<oslash>rn Hansen,
--Jerry Gay,
--Will Coleda,
--Mike O'Regan,
--Slaven ReziE<0x107>,
--Mark Stosberg,
--David Alan Pisoni,
--Adriano Ferreira,
--James Keenan,
--Leland Johnson,
--Ricardo Signes,
--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
diff --cc lib/App/Ack.pm
index e34f667,e34f667..179104f
--- a/lib/App/Ack.pm
+++ b/lib/App/Ack.pm
@@@ -13,16 -13,16 +13,18 @@@ A container for functions for the ack p
  
  =head1 VERSION
  
--Version 2.22
++Version 2.999_01
  
  =cut
  
  our $VERSION;
  our $COPYRIGHT;
  BEGIN {
--    $VERSION = '2.22';
--    $COPYRIGHT = 'Copyright 2005-2017 Andy Lester.';
++    $VERSION = '2.999_01';
++    $COPYRIGHT = 'Copyright 2005-2018 Andy Lester.';
  }
++our $STANDALONE = 0;
++our $ORIGINAL_PROGRAM_NAME;
  
  our $fh;
  
@@@ -89,7 -89,7 +91,7 @@@ sub warn 
      return CORE::warn( _my_program(), ': ', @_, "\n" );
  }
  
--=head2 die( @_ )
++=head2 die( @msgs )
  
  Die in an ack-specific way.
  
@@@ -105,16 -105,16 +107,6 @@@ sub _my_program 
  }
  
  
--=head2 filetypes_supported()
--
--Returns a list of all the types that we can detect.
--
--=cut
--
--sub filetypes_supported {
--    return keys %mappings;
--}
--
  sub thpppt {
      my $y = q{_   /|,\\'!.x',=(www)=,   U   };
      $y =~ tr/,x!w/\nOo_/;
@@@ -260,15 -260,15 +252,19 @@@ Example: ack -i selec
  
  Searching:
    -i, --ignore-case             Ignore case distinctions in PATTERN
--  --[no]smart-case              Ignore case distinctions in PATTERN,
++  -S, --[no]smart-case          Ignore case distinctions in PATTERN,
                                  only if PATTERN contains no upper case.
--                                Ignored if -i is specified
++                                Ignored if -i or -I are specified.
++  -I                            Turns on case-sensitivity in PATTERN.
++                                Negates -i and --smart-case.
    -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
++  --lines=NUM                   Only print line(s) NUM of each file.  No
++                                pattern is matched.
++  --match PATTERN               Specify PATTERN explicitly. Typically omitted.
  
  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
@@@ -276,7 -276,7 +272,6 @@@
    -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:
@@@ -311,13 -311,13 +306,16 @@@ File presentation
                                  files.  (default: on when used interactively)
    --group                       Same as --heading --break
    --nogroup                     Same as --noheading --nobreak
++  --[no]proximate               Separate match output with blank lines unless
++                                they are on adjacent lines.
    --[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.
++  --color-colno=COLOR
++  --color-lineno=COLOR          Set the color for filenames, matches, line and
++                                column numbers.
    --flush                       Flush output immediately, even when ack is used
                                  non-interactively (when output goes to a pipe or
                                  file).
@@@ -336,48 -336,48 +334,55 @@@ File finding
  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
++  --ignore-file=FILTER:ARGS     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.
  
++File type inclusion/exclusion:
    --type=X                      Include only X files, where X is a recognized
--                                filetype.
--  --type=noX                    Exclude X files.
--                                See "ack --help-types" for supported filetypes.
++                                filetype, e.g. --php, --ruby
++  --type=noX                    Exclude X files, e.g. --nophp, --no-ruby.
++  -k, --known-types             Include only files of types that ack recognizes.
++  --help-types                  Display all known types, and how they're defined.
  
  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.
--
++  --type-set=TYPE:FILTER:ARGS   Files with the given ARGS applied to the given
++                                FILTER are recognized as being of type TYPE.
++                                This replaces an existing definition for TYPE.
++  --type-add=TYPE:FILTER:ARGS   Files with the given ARGS applied to the given
++                                FILTER are recognized as being type TYPE.
++  --type-del=TYPE               Removes all filters associated with TYPE.
  
  Miscellaneous:
++  --version                     Display version & copyright
    --[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
++                                and where they're defined.
    --[no]filter                  Force ack to treat standard input as a pipe
                                  (--filter) or tty (--nofilter)
--  --man                         Man page
--  --version                     Display version & copyright
++  --help, -?                    This help
++  --man                         Print the manual
++  --faq                         Print the frequently asked questions
++  --cookbook                    Print a list of tips and tricks for using ack
    --thpppt                      Bill the Cat
    --bar                         The warning admiral
    --cathy                       Chocolate! Chocolate! Chocolate!
  
++Filter specifications:
++    If FILTER is "ext", ARGS is a list of extensions checked against the
++        file's extension.
++    If FILTER is "is", ARGS must match the file's name exactly.
++    If FILTER is "match", ARGS is matched as a case-insensitive regex
++        against the filename.
++    If FILTER is "firstlinematch", ARGS is matched as a regex the first
++        line of the file's contents.
++
  Exit status is 0 if match, 1 if no match.
  
  ack's home page is at https://beyondgrep.com/
@@@ -410,7 -410,7 +415,7 @@@ Note that some extensions may appear i
  
  END_OF_HELP
  
--    my @types = filetypes_supported();
++    my @types = keys %App::Ack::mappings;
      my $maxlen = 0;
      for ( @types ) {
          $maxlen = length if $maxlen < length;
@@@ -428,18 -428,18 +433,35 @@@
      return;
  }
  
--sub show_man {
++
++sub show_docs {
++    my $section = shift;
++
      require Pod::Usage;
  
--    Pod::Usage::pod2usage({
--        -input   => $App::Ack::orig_program_name,
--        -verbose => 2,
--        -exitval => 0,
--    });
++    if ( $App::Ack::STANDALONE ) {
++        # Right now we just show all POD for the standalone.
++        Pod::Usage::pod2usage({
++            -input     => $App::Ack::ORIGINAL_PROGRAM_NAME,
++            -verbose   => 2,
++            -exitval   => 0,
++        });
++    }
++    else {
++        my $module = "App::Ack::Docs::$section";
++        eval "require $module" or App::Ack::die( "Can't load $module" );
++
++        Pod::Usage::pod2usage({
++            -input     => $INC{ "App/Ack/Docs/$section.pm" },
++            -verbose   => 2,
++            -exitval   => 0,
++        });
++    }
  
      return;
  }
  
++
  =head2 get_version_statement
  
  Returns the version information for ack.
@@@ -449,7 -449,7 +471,7 @@@
  sub get_version_statement {
      require Config;
  
--    my $copyright = get_copyright();
++    my $copyright = $App::Ack::COPYRIGHT;
      my $this_perl = $Config::Config{perlpath};
      if ($^O ne 'VMS') {
          my $ext = $Config::Config{_exe};
@@@ -457,8 -457,8 +479,10 @@@
      }
      my $ver = sprintf( '%vd', $^V );
  
++    my $build_type = $App::Ack::STANDALONE ? 'standalone version' : 'standard build';
++
      return <<"END_OF_VERSION";
--ack ${VERSION}
++ack ${VERSION} ($build_type)
  Running under Perl $ver at $this_perl
  
  $copyright
@@@ -468,28 -468,28 +492,6 @@@ under the terms of the Artistic Licens
  END_OF_VERSION
  }
  
--=head2 print_version_statement
--
--Prints the version information for ack.
--
--=cut
--
--sub print_version_statement {
--    App::Ack::print( get_version_statement() );
--
--    return;
--}
--
--=head2 get_copyright
--
--Return the copyright for ack.
--
--=cut
--
--sub get_copyright {
--    return $COPYRIGHT;
--}
--
  
  sub print                   { print {$fh} @_; return; }
  sub print_blank_line        { App::Ack::print( "\n" ); return; }
@@@ -519,7 -519,7 +521,7 @@@ sub output_to_pipe 
      return $output_to_pipe;
  }
  
--=head2 exit_from_ack
++=head2 exit_from_ack( $nmatches )
  
  Exit from the application with the correct exit code.
  
@@@ -535,14 -535,14 +537,47 @@@ sub exit_from_ack 
      exit $rc;
  }
  
++=head2 show_types( $file, $ors )
  
--=head1 COPYRIGHT & LICENSE
++Shows the filetypes associated with a given file.
  
--Copyright 2005-2017 Andy Lester.
++=cut
  
--This program is free software; you can redistribute it and/or modify
--it under the terms of the Artistic License v2.0.
++sub show_types {
++    my $file = shift;
++    my $ors  = shift;
++
++    my @types = filetypes( $file );
++    my $types = join( ',', @types );
++    my $arrow = @types ? ' => ' : ' =>';
++    App::Ack::print( $file->name, $arrow, join( ',', @types ), $ors );
++
++    return;
++}
++
++
++sub filetypes {
++    my ( $file ) = @_;
++
++    my @matches;
++
++    foreach my $k (keys %App::Ack::mappings) {
++        my $filters = $App::Ack::mappings{$k};
++
++        foreach my $filter (@{$filters}) {
++            # Clone the file.
++            my $clone = $file->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;
++}
  
--=cut
  
  1; # End of App::Ack
diff --cc lib/App/Ack/ConfigDefault.pm
index 26cfc79,26cfc79..c25d57c
--- a/lib/App/Ack/ConfigDefault.pm
+++ b/lib/App/Ack/ConfigDefault.pm
@@@ -63,7 -63,7 +63,7 @@@ sub _options_block 
  # Git
  # http://git-scm.com/
  --ignore-directory=is:.git
--# When using submodules, .git is a file.
++# When submodules are used, .git is a file.
  --ignore-file=is:.git
  
  # Mercurial
@@@ -161,16 -161,16 +161,11 @@@
  # Common graphics, just as an optimization
  --ignore-file=ext:gif,jpg,jpeg,png
  
++# Common archives, as an optimization
++--ignore-file=ext:gz,tar,tgz,zip
  
--### 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
++### Filetypes defined
  
  # Makefiles
  # http://www.gnu.org/s/make/
@@@ -180,6 -180,6 +175,7 @@@
  --type-add=make:is:Makefile
  --type-add=make:is:Makefile.Debug
  --type-add=make:is:Makefile.Release
++--type-add=make:is:GNUmakefile
  
  # Rakefiles
  # http://rake.rubyforge.org/
@@@ -208,7 -208,7 +204,7 @@@
  # Assembly
  --type-add=asm:ext:asm,s
  
--# Batch
++# DOS/Windows batch
  --type-add=batch:ext:bat,cmd
  
  # ColdFusion
@@@ -322,6 -322,6 +318,17 @@@
  --type-add=lua:ext:lua
  --type-add=lua:firstlinematch:/^#!.*\blua(jit)?/
  
++# Markdown
++# https://en.wikipedia.org/wiki/Markdown
++--type-add=markdown:ext:md,markdown
++# We understand that there are many ad hoc extensions for markdown
++# that people use.  .md and .markdown are the two that ack recognizes.
++# You are free to add your own in your ackrc file.
++
++# Matlab
++# http://en.wikipedia.org/wiki/MATLAB
++--type-add=matlab:ext:m
++
  # Objective-C
  --type-add=objc:ext:m,h
  
@@@ -332,14 -332,14 +339,21 @@@
  # 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
  
++# 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
++
++# Perl's Plain Old Documentation format, POD
++--type-add=pod:ext:pod
++
  # PHP
  # http://www.php.net/
  --type-add=php:ext:php,phpt,php3,php4,php5,phtml
diff --cc lib/App/Ack/ConfigFinder.pm
index 0d54206,0d54206..986c674
--- a/lib/App/Ack/ConfigFinder.pm
+++ b/lib/App/Ack/ConfigFinder.pm
@@@ -69,16 -69,16 +69,18 @@@ sub _remove_redundancies 
      my @configs = @_;
  
      my %seen;
++    my @uniq;
      foreach my $config (@configs) {
--        my $key = $config->{path};
++        my $path = $config->{path};
++        my $key = -e $path ? Cwd::realpath( $path ) : $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}++;
++        push( @uniq, $config ) unless $seen{$key}++;
      }
--    return grep { defined } @configs;
++    return @uniq;
  }
  
  
@@@ -89,9 -89,9 +91,8 @@@ sub _check_for_ackrc 
                  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;
++    App::Ack::die( File::Spec->catdir(@_) . ' contains both .ackrc and _ackrc. Please remove one of those files.' )
++        if @files > 1;
  
      return wantarray ? @files : $files[0];
  } # end _check_for_ackrc
@@@ -131,9 -131,9 +132,9 @@@ sub find_config_files 
      $cwd =~ /(.+)/;
      $cwd = $1;
      my @dirs = File::Spec->splitdir( $cwd );
--    while(@dirs) {
++    while ( @dirs ) {
          my $ackrc = _check_for_ackrc(@dirs);
--        if(defined $ackrc) {
++        if ( defined $ackrc ) {
              push @config_files, { project => 1, path => $ackrc };
              last;
          }
diff --cc lib/App/Ack/ConfigLoader.pm
index ff2adf5,ff2adf5..65d766f
--- a/lib/App/Ack/ConfigLoader.pm
+++ b/lib/App/Ack/ConfigLoader.pm
@@@ -2,6 -2,6 +2,7 @@@ package App::Ack::ConfigLoader
  
  use strict;
  use warnings;
++use feature 'say';
  
  use App::Ack ();
  use App::Ack::ConfigDefault ();
@@@ -30,15 -30,15 +31,15 @@@ Logic for loading configuration files
  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 );
++    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 );
++    my @file_lists = qw( -f -g -l -L );
  
      @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(--lines)]            => [@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)],
@@@ -47,10 -47,10 +48,11 @@@
          [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(--column)]           => [@file_lists],
++        [@context]               => [@file_lists],
          [qw(-f)]                 => [qw(-g), @pretty],
          [qw(-g)]                 => [qw(-f), @pretty],
++        [qw(--proximate)]        => [@context, @file_lists, qw( --passthru --lines -c )],
      );
  }
  
@@@ -82,9 -82,9 +84,8 @@@ sub _generate_ignore_dir 
              $collection = $opt->{idirs}[-1];
  
              if ( $is_inverted ) {
--                # XXX this relies on invert of an inverted filter
--                #     to return the original
--                $collection = $collection->invert()
++                # This relies on invert of an inverted filter to return the original.
++                $collection = $collection->invert();
              }
          }
          else {
@@@ -245,30 -245,30 +246,9 @@@ sub process_filetypes 
  }
  
  
--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
--
  =begin Adding-Options
  
      *** IF YOU ARE MODIFYING ACK PLEASE READ THIS ***
@@@ -282,7 -282,7 +262,7 @@@
      * Your new option is explained when a user invokes ack --man.
        (See the POD at the end of ./ack)
      * Add your option to t/config-loader.t
--    * Add your option to t/Util.pm#get_options
++    * Add your option to t/Util.pm#get_expected_options
      * Add your option's description and aliases to dev/generate-completion-scripts.pl
      * Go through the list of options already available, and consider
        whether your new option can be considered mutually exclusive
@@@ -292,29 -292,29 +272,21 @@@
  
  =cut
  
--    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-colno=s'     => \$ENV{ACK_COLOR_COLNO},
          '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; },
++        'create-ackrc'      => sub { say for ( '--ignore-ack-defaults', App::Ack::ConfigDefault::options() ); exit; },
          'env!'              => sub {
              my ( undef, $value ) = @_;
  
@@@ -325,28 -325,28 +297,28 @@@
          f                   => \$opt->{f},
          'files-from=s'      => \$opt->{files_from},
          'filter!'           => \$App::Ack::is_filter_mode,
--        flush               => \$opt->{flush},
++        flush               => sub { $| = 1 },
          '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},
++        'i|ignore-case'     => sub { $opt->{i} = 1; $opt->{smart_case} = 0; },
++        'I'                 => sub { $opt->{i} = 0; $opt->{smart_case} = 0; },
          'ignore-directory|ignore-dir=s' => _generate_ignore_dir('--ignore-dir', $opt),
          'ignore-file=s'     => sub {
--                                    my ( undef, $file ) = @_;
++            my ( undef, $file ) = @_;
  
--                                    my ( $filter_type, $args ) = split /:/, $file, 2;
++            my ( $filter_type, $args ) = split /:/, $file, 2;
  
--                                    my $filter = App::Ack::Filter->create_filter($filter_type, split(/,/, $args));
++            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);
--                               },
++            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},
@@@ -366,11 -366,11 +338,12 @@@
          'nopager'           => sub { $opt->{pager} = undef },
          'passthru'          => \$opt->{passthru},
          'print0'            => \$opt->{print0},
++        'proximate:1'       => \$opt->{proximate},
          'Q|literal'         => \$opt->{Q},
          'r|R|recurse'       => sub { $opt->{n} = 0 },
--        's'                 => \$opt->{dont_report_bad_filenames},
++        's'                 => \$opt->{s},
          'show-types'        => \$opt->{show_types},
--        'smart-case!'       => \$opt->{smart_case},
++        'S|smart-case!'     => sub { my (undef,$value) = @_; $opt->{smart_case} = $value; $opt->{i} = 0 if $value; },
          'sort-files'        => \$opt->{sort_files},
          'type=s'            => sub {
              my ( $getopt, $value ) = @_;
@@@ -389,20 -389,20 +362,24 @@@
                  Carp::croak( "Unknown type '$value'" );
              }
          },
--        'u'                 => removed_option('-u'),
--        'unrestricted'      => removed_option('--unrestricted'),
++        'u|underline!'      => \$opt->{u},
          '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 _context_value {
++    my $val = shift;
++
++    # Contexts default to 2.
++    return (!defined($val) || ($val < 0)) ? 2 : $val;
++}
++
  
  sub process_other {
      my ( $opt, $extra_specs, $arg_sources ) = @_;
@@@ -444,20 -444,20 +421,31 @@@
      foreach my $source (@{$arg_sources}) {
          my ( $source_name, $args ) = @{$source}{qw/name contents/};
  
--        my $args_for_source = $arg_specs;
++        my $args_for_source = { %{$arg_specs} };
  
--        if ( $source->{project} ) {
++        if ( $source->{is_ackrc} ) {
              my $illegal = sub {
--                die "Options --output, --pager and --match are forbidden in project .ackrc files.\n";
++                my $name = shift;
++                App::Ack::die( "Option --$name is forbidden in .ackrc files." );
              };
  
              $args_for_source = {
                  %{$args_for_source},
                  'output=s' => $illegal,
--                'pager:s'  => $illegal,
                  'match=s'  => $illegal,
              };
          }
++        if ( $source->{project} ) {
++            my $illegal = sub {
++                my $name = shift;
++                App::Ack::die( "Option --$name is forbidden in project .ackrc files." );
++            };
++
++            $args_for_source = {
++                %{$args_for_source},
++                'pager:s' => $illegal,
++            };
++        }
  
          my $ret;
          if ( ref($args) ) {
@@@ -492,7 -492,7 +480,7 @@@ sub should_dump_options 
      foreach my $source (@{$sources}) {
          my ( $name, $options ) = @{$source}{qw/name contents/};
  
--        if($name eq 'ARGV') {
++        if ( $name eq 'ARGV' ) {
              my $dump;
              local @ARGV = @{$options};
              Getopt::Long::Configure('default', 'pass_through', 'no_auto_help', 'no_auto_version');
@@@ -520,7 -520,7 +508,6 @@@ sub explode_sources 
      my $add_type = sub {
          my ( undef, $arg ) = @_;
  
--        # XXX refactor?
          if ( $arg =~ /(\w+)=/) {
              $arg_spec->{$1} = sub {};
          }
@@@ -602,9 -602,9 +589,9 @@@ sub dump_options 
      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};
++        say $name;
++        say '=' x length($name);
++        say '  ', join(' ', @{$_}) for sort { compare_opts($a, $b) } @{$contents};
      }
  
      return;
@@@ -669,7 -669,7 +656,7 @@@ sub check_for_mutually_exclusive_option
      my %mutually_exclusive_with;
      my @copy = @{$arg_sources};
  
--    for(my $i = 0; $i < @INVALID_COMBINATIONS; $i += 2) {
++    for ( my $i = 0; $i < @INVALID_COMBINATIONS; $i += 2 ) {
          my ( $lhs, $rhs ) = @INVALID_COMBINATIONS[ $i, $i + 1 ];
  
          foreach my $l_opt ( @{$lhs} ) {
@@@ -680,7 -680,7 +667,7 @@@
          }
      }
  
--    while( @copy ) {
++    while ( @copy ) {
          my %set_opts;
  
          my $source = shift @copy;
@@@ -691,7 -691,7 +678,7 @@@
              next unless $opt =~ /^[-+]/;
              last if $opt eq '--';
  
--            if( $opt =~ /^(.*)=/ ) {
++            if ( $opt =~ /^(.*)=/ ) {
                  $opt = $1;
              }
              elsif ( $opt =~ /^(-[^-]).+/ ) {
@@@ -705,8 -705,8 +692,8 @@@
              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";
++                if ( $set_opts{ $mutex_opt } ) {
++                    App::Ack::die( "Options '$mutex_opt' and '$opt' are mutually exclusive" );
                  }
              }
          }
@@@ -787,11 -787,11 +774,11 @@@ sub retrieve_arg_sources 
      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) {
++        if ( open my $fh, '<', $ackrc ) {
              close $fh;
          }
          else {
--            die "Unable to load ackrc '$ackrc': $!"
++            App::Ack::die( "Unable to load ackrc '$ackrc': $!" );
          }
          push( @files, { path => $ackrc } );
      }
@@@ -804,11 -804,11 +791,12 @@@
      foreach my $file ( @files) {
          my @lines = App::Ack::ConfigFinder::read_rcfile($file->{path});
  
--        if(@lines) {
++        if ( @lines ) {
              push @arg_sources, {
                  name     => $file->{path},
                  contents => \@lines,
                  project  => $file->{project},
++                is_ackrc => 1,
              };
          }
      }
diff --cc lib/App/Ack/Docs/Cookbook.pm
index 0000000,0000000..2100b2d
new file mode 100644
--- /dev/null
+++ b/lib/App/Ack/Docs/Cookbook.pm
@@@ -1,0 -1,0 +1,599 @@@
++package App::Ack::Docs::Cookbook;
++
++=head1 COOKBOOK
++
++Here are examples of how to effectively use ack.
++
++Note: on Windows CMD prompt, the quoting may need to switch C<"> for C<'> and/or vice-versa.
++
++
++=head1 COMPOUND QUERIES
++
++Some compound queries can be done in a single RE pattern but most will require a pipeline command.
++
++=head2 Find "goat(s)" or "cow(s)" or both
++
++    ack '(goats?|cows?)'
++
++=head2 Find "goats" in every file that also contains "cows"
++
++Use command substitution to pass a list of cow files to ack:
++
++    ack goats $(ack -l cows)
++
++Or you can use the program F<xargs> to feed the filenames to the
++goat search.
++
++    ack -l cows | xargs ack goats
++
++Or you can use ack's C<-x> option to do the same thing without
++having to get F<xargs> involved.
++
++    ack -l cows | ack -x goats
++
++=head2 Find goats in files that do not contain cows
++
++    ack -L cows | ack -x -l goats
++
++=head2 Find "goats" in every farmish file
++
++One of the usual FAQ is how do I use C<< -f pattern1 >> for files
++and C< pattern2 > within the files in one command?
++
++The answer is don't, use two C<ack>s, either via a Pipe, or nest them.
++
++   ack -ig 'farm|(?:ei)+o$' | ack -x goats
++
++   ack goats $(ack -ig 'farm|(?:ei)+o$' )
++
++=head2 Find "goats" and "cows" in the same line, either order, as words
++
++Following the  compound is a pipeline trope,
++if we assume filenames don't also have goats -
++
++   ack  cows | ack goats
++
++This obviously could scale to N terms, at the cost of N processes.
++
++But for just two, we can easily do it in on go, too:
++
++    ack '\bgoats\b.*\bcows\b|\bcows\b.*\bgoats\b'
++
++C<|> says "OR".
++
++C<< \b >> says word boundary here, letters one side and space or punctuation on the other,
++so half of a Whole-words limitation.
++
++C<.*> says anything or nothing, but wrapped in C<\b.*\b> with words on outsides,
++will have space or punctuation first and last, unless nothing -- and nothing isn't
++allowed, need at least a single space or comma, not C<goatscows>.
++
++=head2 Search for all the files that mention Sys::Hostname but don't match hostname.
++
++    ack -l Sys::Hostname | ack -x -L -I hostname
++
++(We added C<-I> (the reverse of C<-i> or C<< --ignore-case >>)
++because we have C<< --smart-case >> on in our C<.ackrc>.
++The first C<ack> doesn't need it because smart-case smartly goes case-sensitive when pattern has some UpperCase.
++We could have typed C<< --no-smart-case >> but that's too much to type,
++so C<-I> on commandline, longform in scripts!)
++
++
++=head2 Find all goat(s) in files without cows
++
++Search all the files that don't have cow(s) and show their goat(s).
++
++    ack -L 'cows?' | ack -x -w 'goats?'
++
++=head2  Search and highlight a specific pattern 'goat' but exclude lines that match also another pattern 'cow'.
++
++    ack '^(?!.*?cow).*?\Kgoat'
++
++The C<\K> is called KEEP; aside from preventing backtracking, it resets the C<$` $&> boundary, the start of match.
++C<(?!.*cow)> is a negative lookahead.
++C<.*?> is non-greedy so the search is left to right.
++
++=head2 Simulate a 'to within 5 lines' adverb
++
++     ack -w "$1" -C$n  $dirs | ack -C$n -w "$2" | ack -C$n "$1|$2" --pager='less -r'
++
++Add a C<-h> etc to taste.
++
++=head1 USING ACK EFFECTIVELY
++
++=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 C<--type-add>
++and C<--type-set> definitions in it.  If you like C<--smart-case> and
++C<--sort-files>, set them there, too.
++
++
++=head2 Use F<-f> for working with big codesets
++
++Ack does more than search files.  C<ack -f --perl> will create a
++list of all the Perl files in a tree, ideal for sending into F<xargs>.
++For example:
++
++    # Change all "this" to "that" in all Perl files in a tree.
++    ack -f --perl | xargs perl -p -i -e's/this/that/g'
++
++or if you prefer:
++
++    perl -p -i -e's/this/that/g' $(ack -f --perl)
++
++=head2 Use C<-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 C<-Q> to avoid false positives without all the backslashing.  See
++the following example for more...
++
++=head2 Use ack to watch log files
++
++Here's one I used the other day to find trouble spots for a website
++visitor.  The user had a problem loading F<troublesome.gif>, so I
++took the access log and scanned it with ack twice.
++
++    ack -Q aa.bb.cc.dd /path/to/access.log | ack -Q -B5 troublesome.gif
++
++The first ack finds only the lines in the Apache log for the given
++IP.  The second finds the match on my troublesome GIF, and shows
++the previous five lines from the log in each case.
++
++=head2 Use ack instead of find
++
++    ack -f --html --print0 | xargs -0 wc -l
++
++(You may omit the C<< --print0 >> and C<-0> if none of your files or directories contain a space in the name.)
++
++=head2 Searching for a method call
++
++    ack -- '->method'
++
++or
++
++    ack '[-]>method'
++
++(Optionally followed by "word boundary" marker C<\b>
++as in  C<< [-]>method\b >> to not find C<< ->methodically >> .)
++
++=head2 Use C<-w> only for words
++
++C<ack -w pattern> will restrict the match to C<pattern> as a word, surrounded by whitespace or punctuation
++(and both ends of lines count as whitespace).
++(Word means alphabetic or '_' in this context.
++If your OS tells Perl that certain accented characters are alphabetic for you, they may be included, try it!)
++
++If your desired pattern begins with one word and ends with another, C<-w> is still safe.
++
++If your desired pattern starts or ends (or both) with punctuation, using C<-w> may be erroneous.  ack will warn you in this case.
++If you get a warning about C<-w>, Say What You Mean (SWYM).
++If you mean the start of your pattern with punctuation should be
++at the beginning of line B<OR> preceded by a word OR by a space (or tab etc), before the needed punctuation,
++SWYM as C<(?:^|\b|\s)#+\s+(.*)>
++which will find and capture a comment.
++(Try it plain, with C<-o>, and with C<--output='$1'>, which has its own section below)
++
++So, don't look for C<ack -w '$identifier'>, as it won't match
++C<+$identifier> or C<func($identifier)> and won't even find it in column
++1.  Instead, do:
++
++   ack '(?:^|\s|\b)\$identifier\b'
++
++   ack '(?x)(?: ^ | \b | (?<=  \W | \s  )) [#]  DEBUG (?= \b | \s | $) '
++
++SWYMingly finds C<< #DEBUG >> if at beginning of line, after a word break, a non-word char, or a space char,
++and followed by a word-break, or space, or end-of-line.
++This uses look-behind and look-ahead so that only B<#DEBUG> is highlighted (or saved to C<$&>),
++and also C<(?x)> the B<extended> syntax, in which whitespace is only match if explicitly C<\s> or C<\0x20> or C<[ ]>,
++the blanks are for readability.
++
++(This is over-specified, since C<G> followed by space would be C<\b>, and C<\s> is C<\W>, but if you're not sure, it's OK to over-specify!)
++
++=head2 See all the C<.vim> files in your hidden C<~/.vim> directory, except in the C<bundle/> directory.
++
++    ack -f --vim ~/.vim --ignore-dir=bundle
++
++=head2 Find all the Ruby files that match /tax/.
++
++   ack -g tax --ruby
++
++Open all the files that have taxes in them.
++
++   vim $(ack -l taxes)
++
++=head2 Find all the Perl test files and test them
++
++Use C<ack>'s file-type inference to find things of C<--type=perltest>
++and feed them to C<xargs> so it will run the C<prove> command on them.
++
++     ack -f --perltest | xargs prove
++
++=head2 Find places where two methods with the same name are being called on the same line.
++
++    ack -- '->(\w+).*->\1\b'
++
++The C<< (\w+) >> captures a method name (introduced by C<< -> >> ),
++which is then sought a second time as C<\1>, a backreference.
++The backreference needs a trailing C<\b> so C<< ->method ... ->methodically >> does not match.
++The C<< (\w+) >> needs neither left nor right side C<\b> because the C<+> modifier is greedy, will take all the word chars available.
++
++=head2 Find all the places in code there's a call like C<< sort { lc $a cmp lc $b } >>.
++
++This is a rough heuristic.  It gets false positives, but it's pretty useful.
++
++    ack '\bsort\b.+(\w+).+\bcmp\b.+\b(\1)\b'
++
++The word and backreference are matching the canonicalizing use of C<lc> to lowercase both comparands.
++
++=head2 Show a range of lines in a file
++
++     ack --lines=830-850 filename
++
++And use C<-H> to show what line numbers are.
++
++=head2 Find files that match one string but do not match another.
++
++The module Test::Warn has a number of functions of the form
++C<warnings_xxx>.  Look in all the files that find Test::Warn but don't
++find the C<warnings_xxx> functions.
++
++    ack -L 'warnings?_' $(ack -l Test::Warn)
++
++=head2 Search only recently changed or 'dirty' files
++
++Most version control systems provide a query to list files changed since checkout, often referred to as 'dirty' files. E.g.,
++
++    alias dirty="git diff --name-only"
++
++    dirty | ack -x pattern
++
++or
++
++    ack pattern $(dirty)
++
++=head2 See just outline of POD in Perl files
++
++    perldoc -o markdown lib/App/Ack/Docs/Cookbook.pm | ack -h '^#+'
++
++Note, this requires L<Pod::Markdown|https://metacpan.org/pod/Pod::Markdown> plugin installed.
++
++=head2 TBD Do we need more C<-f> and C<-g> examples?
++
++#TODO
++
++=head1 EXAMPLES OF C<< --output >>
++
++The C<-o> and C<< --output expr >> options allow for specifying and formating the output.
++
++With look-behind and look-ahead, one "match without matching" for highlighting or C<-o>) purposes.
++The  regex C<< abc\K(def)(?=ghi) >>  will highlight ONLY C<def> in the text, \
++but only if that string is preceeded by C<abc> and C<ghi> follows.
++With C<-o>, it will output C<def> but only when found in context of C<abc>B<<C<def>>>C<ghi>.
++
++HT to L<HN|https://news.ycombinator.com/item?id=15433310>
++
++=head2 Inventory all PHP sqldo functions
++
++Simple C<-o> requests output only what is matched.
++
++    ack 'sqldo_\w+' --php -o -h | sort -u
++
++=head2 Look for a method you're not sure of the name of.
++
++I was looking for a method that I knew was called "something_follows",
++so I looked for method invocations like that:
++
++     ack -- '->.+_follows\b'
++
++
++=head2 Variables for C<< --output >>
++
++Following variables are useful in the expansion string:
++
++=over 4
++
++=item C<$&>
++
++The whole string matched by PATTERN.
++
++=item C<$1>, C<$2>, ... C<$9>
++
++The contents of the 1st, 2nd ... bracketed group in PATTERN.
++
++=item C<$`>
++
++The string before (to the left of) the match.
++
++=item C<$'>
++
++The string after (to the right of) the match.
++
++=back
++
++For more details and other variables see
++L<http://perldoc.perl.org/perlvar.html#Variables-related-to-regular-expressions|perlvar>.
++
++This example shows how to add text around a particular pattern
++(in this case adding _ around word with "e")
++
++    ack3 "\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"
++
++=head2 Find all the headers used in your C programs.
++
++    ack '#include\s+<(.+)>' --cc --output='$1' | sort -u
++
++=head2 Find the most-used modules in your codebase.
++
++    ack '^use ([\w+:]+)' --output='$1' -h --nogroup | sort | uniq -c | sort -n
++
++=head2 Find all the subroutines in Perl tests and then give a count of how many of each there are
++
++     ack '^sub (\w+)' --perltest --output='$1' -h --nogroup | sort | uniq -c | sort -n
++
++=head2 In COBOL source code, match only lines with blank in column 7, ignore
++
++    ack '^.{6}[ ].*?\Kpattern'
++    ack '(?x) ^ .{6} [ ] .*? \K pattern'  # same but readable
++
++Again using the C<\K> Keep to reset start of matching.
++
++(Legacy COBOL put C<'*'> in Col 7 for comments, back in punch-card days.
++FORTRAN in the day similarly used 'C' or '*' in Col 1.
++Early FORTRAN wrapped lines with C<&> in Col 73 and in Col 6 of next line.)
++
++(Hat-tip for Question to Pierre)
++
++=head2 Extract part of a line from a logfile
++
++    ack '>>ip(\S+).+rq"/help' --output='$1' -h
++
++## Fake parsing long JSON
++
++Having a very long line of JSON consisting of bits like
++
++    {"city":"london","first name":"peter","last name":"hansen","age":"40"},
++    {"city":"new york","first name":"celine","last name":"parker","age":"36"]
++
++wanting output like
++
++     peter (40) celine (36)
++
++the right way would be to do this is the L<C<jq>|https://stedolan.github.io/jq/> utility - which is sed or ack for JSON. Or write a program. However ... this example, ack can do it.
++
++The sneaky and potentially unreliable way to do it is:
++
++     ack '"first name":"([^"]+)".+"age":"(\d+)"' input.txt --output='$1 $2'
++
++Why unreliable? JSON like Perl makes no guarantee hash keys are in any particular order.
++
++For only two fields, we can use 'alternation' to make it safe:
++
++     ack --output '$1$4($2$3)' '{.*?"first name":"([^"]*)".*?age":"(\d+)|{.*?"age":"(\d+)".*?first name":"([^"]*?)"'
++
++This won't scale well to 3! or greater possible field orders to extract.
++At which point, plain Perl with any real JSON module is required.
++
++HT L<SO|https://stackoverflow.com/questions/45538755/bash-text-extracting>
++
++=head1 VERY ELEGANT ACK
++
++=head2 Open list of matching files in Vim, searching for your search term
++
++     $ ack my_search_term
++     <results>
++     $ vim $(!! -l) +/!$
++
++=over 4
++
++=item C<!!> expands to the previous command - C<ack my_search_term> in the example
++
++=item C<$(...)> expands to the output of the command in the parens, so the output of C<ack my_search_term -l> in the example
++
++=item C<<  +/<term> >> tells Vim to start searching for C<< <term> >> once it opens
++
++=item C<< !$ >> expands to the last argument of the previous command - my_search_term in the example
++
++=back
++
++Small caveat: Vim patterns and Perl regexes have some overlap, but they are different, so this doesn't work
++so well when you have a more complex regex as your search term.
++Vim command C<< :help perl-patterns >> will report what Vim thinks the differences are.
++
++=head2 Extending ack your way
++
++A user who really wants the working directory reported makes a C<bash function> (which will look like an alias) to make it so. Hat tip to B<teika-kazura> !
++
++    function ack(){
++        local ackLogDir=/tmp/mylogs/Ack
++        mkdir -p "$ackLogDir"
++        chmod 777 "$ackLogDir" &> /dev/null
++        if [[ $# == 0 ]]; then
++            find "$ackLogDir" -type f | xargs ls -t |xargs less
++            return
++        fi
++        local f="$( mktemp --tmpdir=$ackLogDir )"
++        echo "# Pwd: `pwd`" > $f
++        echo "# ack $@" >> $f
++        command ack "$@" >> "$f" 2>&1
++        less $f
++    }
++
++(We can't really recommend C<chmod 777>.  You're better off with per-user temp sub-dirs for security safety,
++whether under C<$HOME> or under C</tmp/$USER>.)
++
++=head2 Find log lines with 4 nulls and sort by IP address
++
++Via the L<@clmagic|https://twitter.com/clmagic> "Command Line Magic" Twitter account:
++
++    egrep -- "\t-\t-\t-\t-\t" entries.txt |sort -k3V
++    # Get the entries with 4+ null fields and sort the entries by IPv4 (-V) in the 3rd column.
++
++(It's 4+ B<adjacent> null tab-separated fields, null represented as dashes.)
++
++C<ack> will happily do likewise, no changes:
++
++    ack -- "\t-\t-\t-\t-\t" entries.txt |sort -k3V
++
++The difference with C<ack> being, you can use the larger C<perldoc perlre> pattern language,
++larger than even C<egrep>'s, to better SWYM DRY (Say What You Mean, and Don't Repeat Yourself):
++
++    ack -- "(?x: \t  (?: - \t ){4} )" entries.txt |sort -k3V
++
++to explicitly count (C<< (?:  ){4} >>) the tab-separated dashes.
++The C<< (?x: ) >>  says spaces don't count, are used for readability.
++If 'null' was optional spaces between the tabs not a single dash,
++we'd use a character class of just space C<< [ ] >>:
++
++    ack -- "(?x: \t  (?: [ ]* \t ){4} )" entries.txt |sort -k3V
++
++(We could use a C<\ > escaped space, but that's hard to read, especially hard to tell if wrong.
++Is that one space or two there?)
++
++(L<regex cheatsheat comparing ack's perlRe with (e)grep, sed, ...|https://remram44.github.io/regex-cheatsheet/regex.html>
++
++=head2 Summarize the file-types in your project
++
++    $ ack --noenv --show-type -f | perl -MData::Dumper -naE'++$n{$F[-1]}; END {print Dumper \%n}'
++    $VAR1 = {
++          'xml' => 32,
++          'sql' => 2,
++          'shell' => 4,
++          'php,shell' => 8,
++          'yaml' => 1809,
++          'php' => 7122,
++          'css' => 360,
++          'markdown' => 7,
++          'html' => 7,
++          '=>' => 1180,
++          'json' => 69,
++          'js' => 582
++        };
++
++=head2 Fetching URLs with ack
++
++In old C<ack2>, Mark Fowler demonstrated the reason that ack3 no longer allows C<--output>
++in project-scoped F<.ackrc> files.
++
++In the PerlAdvent calendar
++(L<http://www.perladvent.org/2014/2014-12-21.html>), Mark wrote an ack
++expression to annotate the URLs found in a file with their download
++weights:
++
++    ack2 --output='$&: @{[ eval "use LWP::Simple; 1" && length LWP::Simple::get($&) ]} bytes' \
++           'https?://\S+' list.txt
++    http://google.com/: 19529 bytes
++    http://metacpan.org/: 7560 bytes
++    http://www.perladvent.org/: 5562 bytes
++
++C<ack3> restricts C<--output> to using only the safe and sensible variables documented,
++and emphatically not code execution via array interpolation.
++
++But you can sill do this, it just requires a pipe --
++
++    ack -o 'https?://\S+' DEVELOPERS.md  \
++    |  perl -nl -MLWP::Simple \
++                -E 'say "$_ :  @{[ length LWP::Simple::get($_) ]}  bytes";'
++    https://github.com/beyondgrep/website :  50784  bytes
++    https://github.com/beyondgrep/ack3/issues :  111627  bytes
++
++=head2 KWIC: KeyWord in Context index
++
++A Keyword In Context (KWIC) index was more useful in the days of offline
++computing and line-printer reports but is still sometimes relevant to
++see the matches not (just) highlighted but lined up for easy scanning.
++
++The traditional distinction between KWIC and KWOC is whether the Keyword
++is at start of line with it's left context wrapped (KWOC= Out of), or
++tab-separated in the middle. KWIC works best with two word-processor
++fixed tabsets, not with 8-char tabs, alas.
++
++    ack  --output '$&^I$'"'"'^I|| $`' pattern files | sort     # KWOC
++    ack  --output '$&^I$\'^I|| $`'    pattern files | sort     # KWOC
++
++    ack  --output '$`^I$&^I$'"'" pattern files | sort -df -t^I -k F2,F2 # pseudo KWIC
++    ack  --output '$`^I$&^I$\''  pattern files | sort -df -t^I -k F2,F2 # pseudo KWIC
++
++(On the KWOC, the C<||> shows where right and left margin are wrapped.)
++(To make the KWIC output look right, load into OpenOffice or Word to spread the tab stops !)
++
++
++=head2 TBD Add Elegant nearly- and not-ugly-and- exact solutions that  require neither hypothetical, C<\n> as OR nor C<--fgrep-f> .
++
++Ack doesn't have C<--fgrep-f> nor does it accept newlines as OR otherwise, as newer Grep does.  But Grep has no C<--passthru>. 
++L<Requestor|> would like to view the whole files but highlight any of several words in each, which needs both.
++Workaround is ugly:
++
++  ack /etc --match "`/bin/ls /home/ | tr '\n' '|' | sed -e 's/|$//'`" 
++
++Longer but more readable, use C<< $() >> instead of C<``> and Perl instead of tr, sed, 
++which allows us to insert C<< | >> between as needed without an extra to be removed:
++
++  ack /etc --match $(/bin/ls /home/ | perl  -E '@u=<>; chomp for @u; say join q(|), @u' )
++
++or invert the C<ls>,
++
++  ack /etc --match $( perl -E '@u=`ls /home/`; chomp for @u; say join q(|), @u' )
++
++or keep it in one process, 
++
++  ack /etc --match $( perl -E 'chdir q(/home/); @u=<*>; chomp for @u; say join q(|), @u' )
++
++# TODO https://github.com/beyondgrep/ack2/pull/646
++
++=head2 TBD look-ahead and look-behind
++
++#TODO There are a couple examples above - do we need more ?
++
++=head1 WHEN TO DO SOMETHING ELSE
++
++Sometimes tools in the B<BeyondGrep> family aren't the right tool.
++
++=head2 Json Query C<jq>)
++
++For commandline access to JSON data, L<C<jq>|https://stedolan.github.io/jq/> is utility,
++it's like C<sed> or C<ack> for JSON.
++
++=head2 C<comm>: Lines (words) in file1 but not in file2
++
++(Commonly the lines are single words per line.)
++
++While grep can do this
++
++    grep -F -x -v -f file1 file2 > file3
++
++it's rather slow for large files!
++
++The standard Unix/Linux tool for this is C<comm>.
++In C<comm> terms, the request is the C<<comm -23 file1 file2>> option.
++Wit no args, Column 1 is words only in file1, Columnn 2 is words only in file2,
++and Column 3 is words in both files 1 and 2.
++
++The Mnemonic such as it is: C<-23> is C<minus 2,3>,
++i.e. omit columns 2 (file 2 words) and 3 (both files words).
++
++One requirement for C<comm> is that files must be sorted by natural sort order.
++If the files aren't in nor wanted in sorted order, the shell command or alias needed is
++
++     comm -23 <(sort $file1) <(sort $file2)
++
++with modern C<bash>'s C<< <() >> command substitution as file-pipes.
++
++(That C<< <(fileter $f1) <(filter $f2) >> idiom is also good for pre-filtering input to C<diff> etc.)
++
++Note for Windows users: MicroSoft and CygWin both provide Linux/GNU commandline utilities for Windows.
++They may have come with the Perl you're using for Ack.
++
++=cut;
++
++1;
diff --cc lib/App/Ack/Docs/FAQ.pm
index 0000000,0000000..70c6260
new file mode 100644
--- /dev/null
+++ b/lib/App/Ack/Docs/FAQ.pm
@@@ -1,0 -1,0 +1,112 @@@
++package App::Ack::Docs::FAQ;
++
++=pod
++
++=head1 FAQ
++
++This is the Frequently Asked Questions list for ack.  You can also see the
++manual in the Perl module App::Ack::Docs::Manual, or running F<ack --man>.
++
++=head2 Can I stop using grep now?
++
++Many people find I<ack> to be better than I<grep> as an everyday tool
++99% of the time, but don't throw I<grep> away, because there are times
++you'll still need it.  For example, you might be looking through huge
++log files and not using regular expressions.  In that case, I<grep>
++will probably perform better.
++
++=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<ack -f> shows, then ack never looks in it.
++
++=head2 Wouldn't it be great if F<ack> did search & replace?
++
++No, ack will always be read-only.  Perl has a perfectly good way
++to do search & replace in files, using the C<-i>, C<-p> and C<-n>
++switches.
++
++You can certainly use ack to select your files to update.  For
++example, to change all "foo" to "bar" in all PHP files, you can do
++this from the Unix shell:
++
++    $ perl -i -p -e's/foo/bar/g' $(ack -f --php)
++
++=head2 Can I make ack recognize F<.xyz> files?
++
++Yes!  Please see L</"Defining your own types"> in the ack manual.
++
++=head2 Will you make ack recognize F<.xyz> files by default?
++
++We might, depending on how widely-used the file format is.
++
++Submit an issue at in the GitHub issue queue at
++L<https://github.com/beyondgrep/ack3/issues>.  Explain what the file format
++is, where we can find out more about it, and what you have been using
++in your F<.ackrc> to support it.
++
++Please do not bother creating a pull request.  The code for filetypes
++is trivial compared to the rest of the process we go through.
++
++=head2 Why is it called ack if it's called ack-grep?
++
++The name of the program is "ack".  Some packagers have called it
++"ack-grep" when creating packages because there's already a package
++out there called "ack" that has nothing to do with this ack.
++
++I suggest you make a symlink named F<ack> that points to F<ack-grep>
++because one of the crucial benefits of ack is having a name that's
++so short and simple to type.
++
++To do that, run this with F<sudo> or as root:
++
++   ln -s /usr/bin/ack-grep /usr/bin/ack
++
++Alternatively, you could use a shell alias:
++
++    # bash/zsh
++    alias ack=ack-grep
++
++    # csh
++    alias ack ack-grep
++
++=head2 What does F<ack> mean?
++
++Nothing.  I wanted a name that was easy to type and that you could
++pronounce as a single syllable.
++
++=head2 Can I do multi-line regexes?
++
++No, ack does not support regexes that match multiple lines.  Doing
++so would require reading in the entire file at a time.
++
++If you want to see lines near your match, use the C<--A>, C<--B>
++and C<--C> switches for displaying context.
++
++=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 submit an issue to GitHub, and/or bring it up on the ack-users
++mailing list.
++
++=cut
++
++1;
diff --cc lib/App/Ack/Docs/Manual.pm
index 0000000,0000000..e8d7992
new file mode 100644
--- /dev/null
+++ b/lib/App/Ack/Docs/Manual.pm
@@@ -1,0 -1,0 +1,1118 @@@
++package App::Ack::Docs::Manual;
++
++=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<grep> for programmers.
++
++ack searches the named input FILEs or DIRECTORYs 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<http://perldoc.perl.org/perlreref.html|perlreref>.  If you don't know
++how to use regular expression but are interested in learning, you may
++consult L<http://perldoc.perl.org/perlretut.html|perlretut>.  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<ack> delves into
++subdirectories selecting files for searching.
++
++I<ack> is intelligent about the files it searches.  It knows about
++certain file types, based on both the extension on the file and,
++in some cases, the contents of the file.  These selections can be
++made with the B<--type> option.
++
++With no file selection, I<ack> searches through regular files that
++are not explicitly excluded by B<--ignore-dir> and B<--ignore-file>
++options, either present in F<ackrc> files or on the command line.
++
++The default options for I<ack> ignore certain files and directories.  These
++include:
++
++=over 4
++
++=item * Backup files: Files matching F<#*#> or ending with F<~>.
++
++=item * Coredumps: Files matching F<core.\d+>
++
++=item * Version control directories like F<.svn> and F<.git>.
++
++=back
++
++Run I<ack> with the C<--dump> option to see what settings are set.
++
++However, I<ack> always searches the files given on the command line,
++no matter what type.  If you tell I<ack> to search in a coredump,
++it will search in a coredump.
++
++=head1 DIRECTORY SELECTION
++
++I<ack> 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<ack --dump>.
++
++=head1 OPTIONS
++
++=over 4
++
++=item B<--ackrc>
++
++Specifies an ackrc file to load after all others; see L</"ACKRC LOCATION SEMANTICS">.
++
++=item B<-A I<NUM>>, B<--after-context=I<NUM>>
++
++Print I<NUM> lines of trailing context after matching lines.
++
++=item B<-B I<NUM>>, B<--before-context=I<NUM>>
++
++Print I<NUM> lines of leading context before matching lines.
++
++=item B<--[no]break>
++
++Print a break between results from different files. On by default
++when used interactively.
++
++=item B<-C [I<NUM>]>, B<--context[=I<NUM>]>
++
++Print I<NUM> 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<Win32::Console::ANSI> module is installed or the C<ACK_PAGER_COLOR>
++environment variable is used.
++
++=item B<--color-filename=I<color>>
++
++Sets the color to be used for filenames.
++
++=item B<--color-match=I<color>>
++
++Sets the color to be used for matches.
++
++=item B<--color-colno=I<color>>
++
++Sets the color to be used for column numbers.
++
++=item B<--color-lineno=I<color>>
++
++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<--cookbook>
++
++Print the ack cookbook, a list of common tricks and recipes for using ack.
++
++=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<ack>
++considers F<.ackrc> and settings in the environment.
++
++=item B<--faq>
++
++Print the list of frequently asked questions.
++
++=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<FILE>>
++
++The list of files to be searched is specified in I<FILE>.  The list of
++files are separated by newlines.  If I<FILE> is C<->, the list is loaded
++from standard input.
++
++Note that the list of files is B<not> filtered in any way.  If you
++add C<--type=html> in addition to C<--files-from>, the C<--type> will
++be ignored.
++
++
++=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<PATTERN>>
++
++Print searchable files where the relative path + filename matches
++I<PATTERN>.
++
++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.  Overrides B<--smart-case> and B<-I>.
++
++=item B<-I>
++
++Turns on case distinctions in PATTERN.  Overrides B<--smart-case> and B<-i>.
++
++=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<really> want
++to customize ack.
++
++=item B<--[no]ignore-dir=I<DIRNAME>>, B<--[no]ignore-directory=I<DIRNAME>>
++
++Ignore directory (as CVS, .svn, etc are ignored). May be used
++multiple times to ignore multiple directories. For example, mason
++users may wish to include B<--ignore-dir=data>. The B<--noignore-dir>
++option allows users to search directories which would normally be
++ignored (perhaps to research the contents of F<.svn/props> directories).
++
++The I<DIRNAME> must always be a simple directory name. Nested
++directories like F<foo/bar> are NOT supported. You would need to
++specify B<--ignore-dir=foo> and then no files from any foo directory
++are taken into account by ack unless given explicitly on the command
++line.
++
++=item B<--ignore-file=I<FILTER:ARGS>>
++
++Ignore files matching I<FILTER:ARGS>.  The filters are specified
++identically to file type filters as seen in L</"Defining your own types">.
++
++=item B<-k>, B<--known-types>
++
++Limit selected files to those with types that ack knows about.
++
++=item B<--lines=I<NUM>>
++
++Only print line I<NUM> of each file. Multiple lines can be given
++with multiple B<--lines> options or as a comma separated list
++(B<--lines=3,5,7>). Using a range such as B<--lines=4-7> also works. The
++lines are always output in the order found in the file, no matter the
++order given on the command line.
++
++=item B<-l>, B<--files-with-matches>
++
++Only print the filenames of matching files, instead of the matching text.
++
++=item B<-L>, B<--files-without-matches>
++
++Only print the filenames of files that do I<NOT> match.
++
++=item B<--match I<PATTERN>>
++
++Specify the I<PATTERN> explicitly. This is helpful if you don't want to put the
++regex as your first argument, e.g. when executing multiple searches over the
++same set of files.
++
++    # search for foo and bar in given files
++    ack file1 t/file* --match foo
++    ack file1 t/file* --match bar
++
++=item B<-m=I<NUM>>, B<--max-count=I<NUM>>
++
++Print only I<NUM> matches out of each file.  If you want to stop ack
++after printing the first match of any kind, use the B<-1> options.
++
++=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).  This is exactly the same as C<--output=$&>.
++
++=item B<--output=I<expr>>
++
++Output the evaluation of I<expr> for each line (turns off text
++highlighting). If PATTERN matches more than once then a line is
++output for each non-overlapping match.
++
++I<expr> may contain the strings "\n", "\r" and "\t", which will be
++expanded to their corresponding characters line feed, carriage return
++and tab, respectively.
++
++I<expr> may also contain the following Perl special variables:
++
++=over 4
++
++=item C<$1> through C<$9>
++
++The subpattern from the corresponding set of capturing parentheses.
++If your pattern is C<(.+) and (.+)>, and the string is "this and
++that', then C<$1> is "this" and C<$2> is "that".
++
++=item C<$_>
++
++The contents of the line in the file.
++
++=item C<$.>
++
++The number of the line in the file.
++
++=item C<$&>, C<$`> and C<$'>
++
++C<$&> is the the string matched by the pattern, C<$`> is what
++precedes the match, and C<$'> is what follows it.  If the pattern
++is C<gra(ph|nd)> and the string is "lexicographic", then C<$&> is
++"graph", C<$`> is "lexico" and C<$'> is "ic".
++
++Use of these variables in your output will slow down the pattern
++matching.
++
++=item C<$+>
++
++The match made by the last parentheses that matched in the pattern.
++For example, if your pattern is C<Version: (.+)|Revision: (.+)>,
++then C<$+> will contain whichever set of parentheses matched.
++
++=item C<$f>
++
++C<$f> is available, in C<--output> only, to insert the filename.
++This is a stand-in for the discovered C<$filename> usage in old C<< ack2 --output >>,
++which is disallowed with C<ack3> improved security.
++
++The intended usage is to provide the grep or compile-error syntax needed for editor/IDE go-to-line integration,
++e.g. C<--output=$f:$.:$_> or C<--output=$f\t$.\t$&>
++
++=back
++
++For examples of using C<--output>, see the Cookbook section of the manual.
++
++=item B<--pager=I<program>>, B<--nopager>
++
++B<--pager> directs ack's output through I<program>.  This can also be specified
++via the C<ACK_PAGER> and C<ACK_PAGER_COLOR> environment variables.
++
++Using --pager does not suppress grouping and coloring like piping
++output on the command-line does.
++
++B<--nopager> cancels any setting in F<~/.ackrc>, C<ACK_PAGER> or C<ACK_PAGER_COLOR>.
++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 B<-f>, B<-g>, B<-l> or B<-c>, options
++that only list filenames.  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<--proximate[=N]>
++
++Groups together match lines that are within N lines of each other.
++This is useful for visually picking out matches that appear close
++to other matches.
++
++For example, if you got these results without the C<--proximate> option,
++
++    15: First match
++    18: Second match
++    19: Third match
++    37: Fourth match
++
++they would look like this with C<--proximate=1>
++
++    15: First match
++
++    18: Second match
++    19: Third match
++
++    37: Fourth match
++
++and this with C<--proximate=3>.
++
++    15: First match
++    18: Second match
++    19: Third match
++
++    37: Fourth match
++
++If N is omitted, N is set to 1.
++
++=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<-S>, 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<smartcase> in the vim text editor.
++The options overrides B<-i> and B<-I>.
++
++B<-S> is a synonym for B<--smart-case>.
++
++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<ack>.
++
++=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<perl> or I<xml>.  B<--type=perl> can
++also be specified as B<--perl>, and B<--type=noperl> can be done
++as B<--noperl>.
++
++If a file is of both type "foo" and "bar", specifying --foo and
++--nobar will exclude the file, because an exclusion takes precedence
++over an inclusion.
++
++Type specifications can be repeated and are ORed together.
++
++See I<ack --help=types> for a list of valid types.
++
++=item B<--type-add I<TYPE>:I<FILTER>:I<ARGS>>
++
++Files with the given ARGS applied to the given FILTER
++are recognized as being of (the existing) type TYPE.
++See also L</"Defining your own types">.
++
++=item B<--type-set I<TYPE>:I<FILTER>:I<ARGS>>
++
++Files with the given ARGS applied to the given FILTER are recognized as
++being of type TYPE. This replaces an existing definition for type TYPE.  See
++also L</"Defining your own types">.
++
++=item B<--type-del I<TYPE>>
++
++The filters associated with TYPE are removed from Ack, and are no longer considered
++for searches.
++
++=item B<-u>, B<--[no]underline>
++
++Turns on underlining of matches, where "underlining" is not a true
++underlining, but printing a line of carets under the match.
++
++    $ ack -u foo
++    peanuts.txt
++    17: Come kick the football you fool
++                      ^^^          ^^^
++    623: Price per square foot
++                          ^^^
++
++This is useful if you're dumping the results of an ack run into a text
++file or printer and don't get any coloring.
++
++The setting of underline does not affect highlighting of matches.
++
++=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>
++
++Force PATTERN to match only whole words.
++
++=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.
++
++Note that the list of files is B<not> filtered in any way.  If you add
++C<--type=html> in addition to C<-x>, the C<--type> will be ignored.
++
++=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<line>
++in the F<.ackrc> file is interpreted as one element of C<@ARGV>.
++
++F<ack> looks in several locations for F<.ackrc> files; the searching
++process is detailed in L</"ACKRC LOCATION SEMANTICS">.  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<ack --perl foo> searches for foo in all perl files. I<ack --help=types>
++tells you, that perl files are files ending
++in .pl, .pm, .pod or .t. So what if you would like to include .xs
++files as well when searching for --perl files? I<ack --type-add perl:ext:xs --perl foo>
++does this for you. B<--type-add> appends
++additional extensions to an existing type.
++
++If you want to define a new type, or completely redefine an existing
++type, then use B<--type-set>. I<ack --type-set eiffel:ext:e,eiffel> defines
++the type I<eiffel> to include files with
++the extensions .e or .eiffel. So to search for all eiffel files
++containing the word Bertrand use I<ack --type-set eiffel:ext:e,eiffel --eiffel Bertrand>.
++As usual, you can also write B<--type=eiffel>
++instead of B<--eiffel>. Negation also works, so B<--noeiffel> excludes
++all eiffel files from a search. Redefining also works: I<ack --type-set cc:ext:c,h>
++and I<.xs> files no longer belong to the type I<cc>.
++
++When defining your own types in the F<.ackrc> file you have to use
++the following:
++
++  --type-set=eiffel:ext:e,eiffel
++
++or writing on separate lines
++
++  --type-set
++  eiffel:ext:e,eiffel
++
++The following does B<NOT> 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<ack --type-set backup:ext:bak --type-add perl:ext:perl --help-types>
++
++In addition to filtering based on extension, ack offers additional
++filter types.  The generic syntax is
++I<--type-set TYPE:FILTER:ARGS>; I<ARGS> depends on the value
++of I<FILTER>.
++
++=over 4
++
++=item is:I<FILENAME>
++
++I<is> 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<EXTENSION>[,I<EXTENSION2>[,...]]
++
++I<ext> 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<PATTERN>
++
++I<match> 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<PATTERN>
++
++I<firstlinematch> matches the first line of the target file against a
++regular expression.  Like I<match>, the regular expression is made
++case insensitive.
++
++Example:
++
++    --type-add perl:firstlinematch:/perl/
++
++=back
++
++=head1 ENVIRONMENT VARIABLES
++
++For commonly-used ack options, environment variables can make life
++much easier.  These variables are ignored if B<--noenv> is specified
++on the command line.
++
++=over 4
++
++=item ACKRC
++
++Specifies the location of the user's F<.ackrc> file.  If this file doesn't
++exist, F<ack> looks in the default location.
++
++=item ACK_OPTIONS
++
++This variable specifies default options to be placed in front of
++any explicit options on the command line.
++
++=item ACK_COLOR_FILENAME
++
++Specifies the color of the filename when it's printed in B<--group>
++mode.  By default, it's "bold green".
++
++The recognized attributes are clear, reset, dark, bold, underline,
++underscore, blink, reverse, concealed black, red, green, yellow,
++blue, magenta, on_black, on_red, on_green, on_yellow, on_blue,
++on_magenta, on_cyan, and on_white.  Case is not significant.
++Underline and underscore are equivalent, as are clear and reset.
++The color alone sets the foreground color, and on_color sets the
++background color.
++
++This option can also be set with B<--color-filename>.
++
++=item ACK_COLOR_MATCH
++
++Specifies the color of the matching text when printed in B<--color>
++mode.  By default, it's "black on_yellow".
++
++This option can also be set with B<--color-match>.
++
++See B<ACK_COLOR_FILENAME> for the color specifications.
++
++=item ACK_COLOR_LINENO
++
++Specifies the color of the line number when printed in B<--color>
++mode.  By default, it's "bold yellow".
++
++This option can also be set with B<--color-lineno>.
++
++See B<ACK_COLOR_FILENAME> for the color specifications.
++
++=item ACK_PAGER
++
++Specifies a pager program, such as C<more>, C<less> or C<most>, to which
++ack will send its output.
++
++Using C<ACK_PAGER> does not suppress grouping and coloring like
++piping output on the command-line does, except that on Windows
++ack will assume that C<ACK_PAGER> does not support color.
++
++C<ACK_PAGER_COLOR> overrides C<ACK_PAGER> if both are specified.
++
++=item ACK_PAGER_COLOR
++
++Specifies a pager program that understands ANSI color sequences.
++Using C<ACK_PAGER_COLOR> does not suppress grouping and coloring
++like piping output on the command-line does.
++
++If you are not on Windows, you never need to use C<ACK_PAGER_COLOR>.
++
++=back
++
++=head1 AVAILABLE COLORS
++
++F<ack> uses the colors available in Perl's L<Term::ANSIColor> 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<ack> integrates easily with the Vim text editor. Set this in your
++F<.vimrc> to use F<ack> instead of F<grep>:
++
++    set grepprg=ack\ -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<ack> 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<https://beyondgrep.com/more-tools/>.
++
++=head2 Shell and Return Code
++
++For greater compatibility with I<grep>, I<ack> in normal use returns
++shell return or exit code of 0 only if something is found and 1 if
++no match is found.
++
++(Shell exit code 1 is C<$?=256> in perl with C<system> or backticks.)
++
++The I<grep> code 2 for errors is not used.
++
++If C<-f> or C<-g> are specified, then 0 is returned if at least one
++file is found.  If no files are found, then 1 is returned.
++
++=cut
++
++=head1 DEBUGGING ACK PROBLEMS
++
++If ack gives you output you're not expecting, start with a few simple steps.
++
++=head2 Try it with 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 for searching
++
++Ack's B<-f> was originally added as a debugging tool.  If ack is
++not finding matches you think it should find, run F<ack -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.  You may be loading an F<.ackrc> file that you didn't know
++you were loading.
++
++=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</etc/ackrc> on Unix-like systems.
++
++Under Windows XP and earlier, the global ackrc is at
++C<C:\Documents and Settings\All Users\Application Data\ackrc>
++
++Under Windows Vista/7, the global ackrc is at
++C<C:\ProgramData\ackrc>
++
++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<C:\Documents and Settings\$USER\Application Data\ackrc>.
++
++Under Windows Vista/7, the user's ackrc is at
++C<C:\Users\$USER\AppData\Roaming\ackrc>.
++
++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<ACK_OPTIONS>.  This can
++be omitted using C<--noenv>.
++
++=item * Command line
++
++Options are then loaded from the command line.
++
++=back
++
++=head1 BUGS & ENHANCEMENTS
++
++ack is based at GitHub at L<https://github.com/beyondgrep/ack3>
++
++Please report any bugs or feature requests to the issues list at
++Github: L<https://github.com/beyondgrep/ack3/issues>.
++
++Please include the operating system that you're using; the output of
++the command C<ack --version>; and any customizations in your F<.ackrc>
++you may have.
++
++To suggest enhancements, please submit an issue at
++L<https://github.com/beyondgrep/ack3/issues>.  Also read the
++F<DEVELOPERS.md> file in the ack code repository.
++
++Also, feel free to discuss your issues on the ack mailing
++list at L<https://groups.google.com/group/ack-users>.
++
++=head1 SUPPORT
++
++Support for and information about F<ack> can be found at:
++
++=over 4
++
++=item * The ack homepage
++
++L<https://beyondgrep.com/>
++
++=item * Source repository
++
++L<https://github.com/beyondgrep/ack3>
++
++=item * The ack issues list at Github
++
++L<https://github.com/beyondgrep/ack3/issues>
++
++=item * The ack announcements mailing list
++
++L<http://groups.google.com/group/ack-announcement>
++
++=item * The ack users' mailing list
++
++L<http://groups.google.com/group/ack-users>
++
++=item * The ack development mailing list
++
++L<http://groups.google.com/group/ack-users>
++
++=back
++
++=head1 COMMUNITY
++
++There are ack mailing lists and a Slack channel for ack.  See
++L<https://beyondgrep.com/community/> for details.
++
++=head1 ACKNOWLEDGEMENTS
++
++How appropriate to have I<ack>nowledgements!
++
++Thanks to everyone who has contributed to ack in any way, including
++H.Merijn Brand,
++Duke Leto,
++Gerhard Poul,
++Ethan Mallove,
++Marek Kubica,
++Ray Donnelly,
++Nikolaj Schumacher,
++Ed Avis,
++Nick Morrott,
++Austin Chamberlin,
++Varadinsky,
++SE<eacute>bastien FeugE<egrave>re,
++Jakub Wilk,
++Pete Houston,
++Stephen Thirlwall,
++Jonah Bishop,
++Chris Rebert,
++Denis Howe,
++RaE<uacute>l GundE<iacute>n,
++James McCoy,
++Daniel Perrett,
++Steven Lee,
++Jonathan Perret,
++Fraser Tweedale,
++RaE<aacute>l GundE<aacute>n,
++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,
++GE<aacute>bor SzabE<oacute>,
++Tod Hagan,
++Michael Hendricks,
++E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason,
++Piers Cawley,
++Stephen Steneker,
++Elias Lutfallah,
++Mark Leighton Fisher,
++Matt Diephouse,
++Christian Jaeger,
++Bill Sully,
++Bill Ricker,
++David Golden,
++Nilson Santos F. Jr,
++Elliot Shank,
++Merijn Broeren,
++Uwe Voelker,
++Rick Scott,
++Ask BjE<oslash>rn Hansen,
++Jerry Gay,
++Will Coleda,
++Mike O'Regan,
++Slaven ReziE<0x107>,
++Mark Stosberg,
++David Alan Pisoni,
++Adriano Ferreira,
++James Keenan,
++Leland Johnson,
++Ricardo Signes,
++Pete Krawczyk and
++Rob Hoelz.
++
++=head1 AUTHOR
++
++Andy Lester, C<< <andy at petdance.com> >>
++
++=head1 COPYRIGHT & LICENSE
++
++Copyright 2005-2018 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
++
++1;
diff --cc lib/App/Ack/File.pm
index 18e447a,18e447a..0765b40
--- a/lib/App/Ack/File.pm
+++ b/lib/App/Ack/File.pm
@@@ -1,15 -1,15 +1,13 @@@
--package App::Ack::Resource;
++package App::Ack::File;
  
  use App::Ack;
  
  use warnings;
  use strict;
--use overload
--    '""' => 'name';
  
  =head1 NAME
  
--App::Ack::Resource
++App::Ack::File
  
  =head1 DESCRIPTION
  
@@@ -45,9 -45,9 +43,9 @@@ sub new 
  }
  
  
--=head2 $res->name()
++=head2 $file->name()
  
--Returns the name of the resource.
++Returns the name of the file.
  
  =cut
  
@@@ -56,10 -56,10 +54,10 @@@ sub name 
  }
  
  
--=head2 $res->basename()
++=head2 $file->basename()
  
  Returns the basename (the last component the path)
--of the resource.
++of the file.
  
  =cut
  
@@@ -75,11 -75,11 +73,11 @@@ sub basename 
  }
  
  
--=head2 $res->open()
++=head2 $file->open()
  
--Opens a filehandle for reading this resource and returns it, or returns
++Opens a filehandle for reading this file and returns it, or returns
  undef if the operation fails (the error is in C<$!>).  Instead of calling
--C<close $fh>, C<$res-E<gt>close> should be called.
++C<close $fh>, C<$file-E<gt>close> should be called.
  
  =cut
  
@@@ -99,11 -99,11 +97,11 @@@ sub open 
  }
  
  
--=head2 $res->needs_line_scan( \%opts )
++=head2 $file->needs_line_scan( \%opts )
  
  Tells if the file needs a line-by-line scan.  This is a big
  optimization because if you can tell from the outset that the pattern
--is not found in the resource at all, then there's no need to do the
++is not found in the file at all, then there's no need to do the
  line-by-line iteration.
  
  Slurp up an entire file up to 100K, see if there are any matches
@@@ -139,9 -139,9 +137,9 @@@ sub needs_line_scan 
  }
  
  
--=head2 $res->reset()
++=head2 $file->reset()
  
--Resets the resource back to the beginning.  This is only called if
++Resets the file back to the beginning.  This is only called if
  C<needs_line_scan()> is true, but not always if C<needs_line_scan()>
  is true.
  
@@@ -150,20 -150,20 +148,17 @@@
  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}: $!" );
++    if ( defined($self->{fh}) ) {
++        if ( !seek( $self->{fh}, 0, 0 ) && $App::Ack::report_bad_filenames ) {
++            App::Ack::warn( "$self->{filename}: $!" );
++        }
      }
  
      return;
  }
  
  
--=head2 $res->close()
++=head2 $file->close()
  
  Close the file.
  
@@@ -187,9 -187,9 +182,9 @@@ sub close 
  }
  
  
--=head2 $res->clone()
++=head2 $file->clone()
  
--Clones this resource.
++Clones this file.
  
  =cut
  
@@@ -200,7 -200,7 +195,7 @@@ sub clone 
  }
  
  
--=head2 $res->firstliney()
++=head2 $file->firstliney()
  
  Returns the first line of a file (or first 250 characters, whichever
  comes first).
@@@ -216,19 -216,19 +211,23 @@@ sub firstliney 
              if ( $App::Ack::report_bad_filenames ) {
                  App::Ack::warn( $self->name . ': ' . $! );
              }
--            return '';
++            $self->{firstliney} = '';
          }
--
--        my $buffer = '';
--        my $rc     = sysread( $fh, $buffer, 250 );
--        unless($rc) { # XXX handle this better?
--            $buffer = '';
++        else {
++            my $buffer;
++            my $rc = sysread( $fh, $buffer, 250 );
++            if ( $rc ) {
++                $buffer =~ s/[\r\n].*//s;
++            }
++            else {
++                if ( !defined($rc) ) {
++                    App::Ack::warn( $self->name . ': ' . $! );
++                }
++                $buffer = '';
++            }
++            $self->{firstliney} = $buffer;
++            $self->reset;
          }
--        $buffer =~ s/[\r\n].*//s;
--        $self->{firstliney} = $buffer;
--        $self->reset;
--
--        $self->close;
      }
  
      return $self->{firstliney};
diff --cc lib/App/Ack/Files.pm
index 89f7164,89f7164..ca775e6
--- a/lib/App/Ack/Files.pm
+++ b/lib/App/Ack/Files.pm
@@@ -1,17 -1,17 +1,7 @@@
--package App::Ack::Resources;
--
--=head1 NAME
--
--App::Ack::Resources
--
--=head1 SYNOPSIS
--
--A factory object for creating a stream of L<App::Ack::Resource> objects.
--
--=cut
++package App::Ack::Files;
  
  use App::Ack;
--use App::Ack::Resource;
++use App::Ack::File;
  
  use File::Next 1.16;
  use Errno qw(EACCES);
@@@ -19,25 -19,25 +9,13 @@@
  use warnings;
  use strict;
  
--sub _generate_error_handler {
--    my $opt = shift;
++=head1 NAME
  
--    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 );
--        };
--    }
--}
++App::Ack::Files
++
++=head1 SYNOPSIS
++
++A factory object for creating a stream of L<App::Ack::File> objects.
  
  =head1 METHODS
  
@@@ -57,7 -57,7 +35,7 @@@ sub from_argv 
      my $file_filter    = undef;
      my $descend_filter = $opt->{descend_filter};
  
--    if( $opt->{n} ) {
++    if ( $opt->{n} ) {
          $descend_filter = sub {
              return 0;
          };
@@@ -100,10 -100,10 +78,17 @@@ sub from_file 
      }, $class;
  }
  
--# This is for reading input lines from STDIN, not the list of files from STDIN
++
++=head2 from_stdin()
++
++This is for reading input lines from STDIN, not the list of files
++from STDIN.
++
++=cut
++
++
  sub from_stdin {
      my $class = shift;
--    my $opt   = shift;
  
      my $self  = bless {}, $class;
  
@@@ -120,12 -120,12 +105,34 @@@
      return $self;
  }
  
++
  sub next {
      my $self = shift;
  
      my $file = $self->{iter}->() or return;
  
--    return App::Ack::Resource->new( $file );
++    return App::Ack::File->new( $file );
++}
++
++
++sub _generate_error_handler {
++    my $opt = shift;
++
++    if ( $App::Ack::report_bad_filenames ) {
++        return sub {
++            my $msg = shift;
++            App::Ack::warn( $msg );
++        };
++    }
++    else {
++        return sub {
++            my $msg = shift;
++            if ( $! == EACCES ) {
++                return;
++            }
++            App::Ack::warn( $msg );
++        };
++    }
  }
  
  1;
diff --cc lib/App/Ack/Filter.pm
index f4f1da7,f4f1da7..80881e5
--- a/lib/App/Ack/Filter.pm
+++ b/lib/App/Ack/Filter.pm
@@@ -1,18 -1,18 +1,9 @@@
  package App::Ack::Filter;
  
--=head1 NAME
--
--App::Ack::Filter
--
--=head1 DESCRIPTION
--
--An abstract superclass that represents objects that can filter `App::Ack::Resource` objects.
--
--=cut
--
  use strict;
  use warnings;
  
++use App::Ack ();
  use App::Ack::Filter::Inverse ();
  use Carp 1.04 ();
  
@@@ -22,6 -22,6 +13,12 @@@ my %filter_types
  
  App::Ack::Filter - Filter objects to filter files
  
++=head1 DESCRIPTION
++
++An abstract superclass that represents objects that can filter
++C<App::Ack::File> objects.  App::Ack::Filter implementations are
++responsible for filtering filenames to be searched.
++
  =head1 SYNOPSIS
  
      # filter implementation
@@@ -29,10 -29,10 +26,10 @@@
  
      use strict;
      use warnings;
--    use base 'App::Ack::Filter';
++    use parent 'App::Ack::Filter';
  
      sub filter {
--        my ( $self, $resource ) = @_;
++        my ( $self, $file ) = @_;
      }
  
      BEGIN {
@@@ -45,11 -45,11 +42,6 @@@
      App::Ack::Filter->create_filter('mine', @args);
  
  
--=head1 DESCRIPTION
--
--App::Ack::Filter implementations are responsible for filtering filenames
--to be searched.
--
  =head1 METHODS
  
  =head2 App::Ack:Filter->create_filter($type, @args)
@@@ -65,7 -65,7 +57,8 @@@ sub create_filter 
      if ( my $package = $filter_types{$type} ) {
          return $package->new(@args);
      }
--    Carp::croak "Unknown filter type '$type'";
++    my $allowed_types = join( ', ', sort keys %filter_types );
++    App::Ack::die( "Unknown filter type '$type'.  Type must be one of: $allowed_types." );
  }
  
  =head2 App::Ack:Filter->register_filter($type, $package)
@@@ -83,11 -83,11 +76,11 @@@ sub register_filter 
      return;
  }
  
--=head2 $filter->filter($resource)
++=head2 $filter->filter( $file )
  
  Must be implemented by filter implementations.  Returns
  true if the filter passes, false otherwise.  This method
--must B<not> alter the passed-in C<$resource> object.
++must B<not> alter the passed-in C<$file> object.
  
  =head2 $filter->invert()
  
diff --cc lib/App/Ack/Filter/Collection.pm
index e3ad300,e3ad300..beec02c
--- a/lib/App/Ack/Filter/Collection.pm
+++ b/lib/App/Ack/Filter/Collection.pm
@@@ -17,7 -17,7 +17,7 @@@ the C<--known> command line option
  
  use strict;
  use warnings;
--use base 'App::Ack::Filter';
++use parent 'App::Ack::Filter';
  
  sub new {
      my ( $class ) = @_;
@@@ -29,18 -29,18 +29,14 @@@
  }
  
  sub filter {
--    my ( $self, $resource ) = @_;
++    my ( $self, $file ) = @_;
  
--    for my $group (values %{$self->{'groups'}}) {
--        if ($group->filter($resource)) {
--            return 1;
--        }
++    for my $group (values %{$self->{groups}}) {
++        return 1 if $group->filter($file);
      }
  
--    for my $filter (@{$self->{'ungrouped'}}) {
--        if ($filter->filter($resource)) {
--            return 1;
--        }
++    for my $filter (@{$self->{ungrouped}}) {
++        return 1 if $filter->filter($file);
      }
  
      return 0;
@@@ -69,9 -69,9 +65,7 @@@ sub inspect 
  sub to_string {
      my ( $self ) = @_;
  
--    my $ungrouped = $self->{'ungrouped'};
--
--    return join(', ', map { "($_)" } @{$ungrouped});
++    return join(', ', map { "($_)" } @{$self->{ungrouped}});
  }
  
  1;
diff --cc lib/App/Ack/Filter/Default.pm
index 0a2250b,0a2250b..93e1a1e
--- a/lib/App/Ack/Filter/Default.pm
+++ b/lib/App/Ack/Filter/Default.pm
@@@ -13,7 -13,7 +13,7 @@@ default if you don't specify any filter
  
  use strict;
  use warnings;
--use base 'App::Ack::Filter';
++use parent 'App::Ack::Filter';
  
  sub new {
      my ( $class ) = @_;
@@@ -22,9 -22,9 +22,9 @@@
  }
  
  sub filter {
--    my ( $self, $resource ) = @_;
++    my ( $self, $file ) = @_;
  
--    return -T $resource->name;
++    return -T $file->name;
  }
  
  1;
diff --cc lib/App/Ack/Filter/Extension.pm
index dadb21d,dadb21d..c90a74f
--- a/lib/App/Ack/Filter/Extension.pm
+++ b/lib/App/Ack/Filter/Extension.pm
@@@ -12,7 -12,7 +12,7 @@@ Implements filters based on extensions
  
  use strict;
  use warnings;
--use base 'App::Ack::Filter';
++use parent 'App::Ack::Filter';
  
  use App::Ack::Filter ();
  use App::Ack::Filter::ExtensionGroup ();
@@@ -35,27 -35,27 +35,21 @@@ sub create_group 
  }
  
  sub filter {
--    my ( $self, $resource ) = @_;
++    my ( $self, $file ) = @_;
  
--    my $re = $self->{'regex'};
--
--    return $resource->name =~ /$re/;
++    return $file->name =~ /$self->{regex}/;
  }
  
  sub inspect {
      my ( $self ) = @_;
  
--    my $re = $self->{'regex'};
--
--    return ref($self) . " - $re";
++    return ref($self) . ' - ' . $self->{regex};
  }
  
  sub to_string {
      my ( $self ) = @_;
  
--    my $exts = $self->{'extensions'};
--
--    return join(' ', map { ".$_" } @{$exts});
++    return join( ' ', map { ".$_" } @{$self->{extensions}} );
  }
  
  BEGIN {
diff --cc lib/App/Ack/Filter/ExtensionGroup.pm
index ce64b4b,ce64b4b..28e5c94
--- a/lib/App/Ack/Filter/ExtensionGroup.pm
+++ b/lib/App/Ack/Filter/ExtensionGroup.pm
@@@ -13,7 -13,7 +13,7 @@@ calls into one container.  See App::Ack
  
  use strict;
  use warnings;
--use base 'App::Ack::Filter';
++use parent 'App::Ack::Filter';
  
  sub new {
      my ( $class ) = @_;
@@@ -34,9 -34,9 +34,9 @@@ sub add 
  }
  
  sub filter {
--    my ( $self, $resource ) = @_;
++    my ( $self, $file ) = @_;
  
--    if ($resource->name =~ /[.]([^.]*)$/) {
++    if ($file->name =~ /[.]([^.]*)$/) {
          return exists $self->{'data'}->{lc $1};
      }
  
diff --cc lib/App/Ack/Filter/FirstLineMatch.pm
index 2e32022,2e32022..fa37559
--- a/lib/App/Ack/Filter/FirstLineMatch.pm
+++ b/lib/App/Ack/Filter/FirstLineMatch.pm
@@@ -6,14 -6,14 +6,14 @@@ App::Ack::Filter::FirstLineMatc
  
  =head1 DESCRIPTION
  
--The class that implements filtering resources by their first line.
++The class that implements filtering files by their first line.
  
  =cut
  
  
  use strict;
  use warnings;
--use base 'App::Ack::Filter';
++use parent 'App::Ack::Filter';
  
  sub new {
      my ( $class, $re ) = @_;
@@@ -31,21 -31,21 +31,16 @@@
  # .min.js file (which might be only one "line" long) into memory.
  
  sub filter {
--    my ( $self, $resource ) = @_;
++    my ( $self, $file ) = @_;
  
--    my $re = $self->{'regex'};
--
--    my $line = $resource->firstliney;
--
--    return $line =~ /$re/;
++    return $file->firstliney =~ /$self->{regex}/;
  }
  
  sub inspect {
      my ( $self ) = @_;
  
--    my $re = $self->{'regex'};
  
--    return ref($self) . " - $re";
++    return ref($self) . ' - ' . $self->{regex};
  }
  
  sub to_string {
@@@ -53,7 -53,7 +48,7 @@@
  
      (my $re = $self->{regex}) =~ s{\([^:]*:(.*)\)$}{$1};
  
--    return "first line matches /$re/";
++    return "First line matches /$re/";
  }
  
  BEGIN {
diff --cc lib/App/Ack/Filter/Inverse.pm
index 2ace009,2ace009..f1c18f6
--- a/lib/App/Ack/Filter/Inverse.pm
+++ b/lib/App/Ack/Filter/Inverse.pm
@@@ -13,7 -13,7 +13,7 @@@ The filter class that inverts another f
  
  use strict;
  use warnings;
--use base 'App::Ack::Filter';
++use parent 'App::Ack::Filter';
  
  sub new {
      my ( $class, $filter ) = @_;
@@@ -24,10 -24,10 +24,9 @@@
  }
  
  sub filter {
--    my ( $self, $resource ) = @_;
++    my ( $self, $file ) = @_;
  
--    my $filter = $self->{'filter'};
--    return !$filter->filter( $resource );
++    return !$self->{filter}->filter( $file );
  }
  
  sub invert {
diff --cc lib/App/Ack/Filter/Is.pm
index d41a1fd,d41a1fd..b78e2e1
--- a/lib/App/Ack/Filter/Is.pm
+++ b/lib/App/Ack/Filter/Is.pm
@@@ -12,7 -12,7 +12,7 @@@ Filters based on exact filename match
  
  use strict;
  use warnings;
--use base 'App::Ack::Filter';
++use parent 'App::Ack::Filter';
  
  use File::Spec 3.00 ();
  use App::Ack::Filter::IsGroup;
@@@ -31,28 -31,28 +31,21 @@@ sub create_group 
  }
  
  sub filter {
--    my ( $self, $resource ) = @_;
++    my ( $self, $file ) = @_;
  
--    my $filename = $self->{'filename'};
--    my $base     = (File::Spec->splitpath($resource->name))[2];
--
--    return $base eq $filename;
++    return (File::Spec->splitpath($file->name))[2] eq $self->{filename};
  }
  
  sub inspect {
      my ( $self ) = @_;
  
--    my $filename = $self->{'filename'};
--
--    return ref($self) . " - $filename";
++    return ref($self) . ' - ' . $self->{filename};
  }
  
  sub to_string {
      my ( $self ) = @_;
  
--    my $filename = $self->{'filename'};
--
--    return $filename;
++    return $self->{filename};
  }
  
  BEGIN {
diff --cc lib/App/Ack/Filter/IsGroup.pm
index fa71be0,fa71be0..9ab70f7
--- a/lib/App/Ack/Filter/IsGroup.pm
+++ b/lib/App/Ack/Filter/IsGroup.pm
@@@ -27,7 -27,7 +27,7 @@@ overhead, etc.  So ::Is filters know ho
  
  use strict;
  use warnings;
--use base 'App::Ack::Filter';
++use parent 'App::Ack::Filter';
  
  use File::Spec 3.00 ();
  
@@@ -48,12 -48,12 +48,9 @@@ sub add 
  }
  
  sub filter {
--    my ( $self, $resource ) = @_;
++    my ( $self, $file ) = @_;
  
--    my $data = $self->{'data'};
--    my $base = $resource->basename;
--
--    return exists $data->{$base};
++    return exists $self->{data}->{ $file->basename };
  }
  
  sub inspect {
diff --cc lib/App/Ack/Filter/IsPath.pm
index dd4b80a,dd4b80a..f7fa220
--- a/lib/App/Ack/Filter/IsPath.pm
+++ b/lib/App/Ack/Filter/IsPath.pm
@@@ -12,7 -12,7 +12,7 @@@ Filters based on path
  
  use strict;
  use warnings;
--use base 'App::Ack::Filter';
++use parent 'App::Ack::Filter';
  
  use App::Ack::Filter::IsPathGroup;
  
@@@ -30,25 -30,25 +30,21 @@@ sub create_group 
  }
  
  sub filter {
--    my ( $self, $resource ) = @_;
++    my ( $self, $file ) = @_;
  
--    return $resource->name eq $self->{'filename'};
++    return $file->name eq $self->{filename};
  }
  
  sub inspect {
      my ( $self ) = @_;
  
--    my $filename = $self->{'filename'};
--
--    return ref($self) . " - $filename";
++    return ref($self) . ' - ' . $self->{filename};
  }
  
  sub to_string {
      my ( $self ) = @_;
  
--    my $filename = $self->{'filename'};
--
--    return $filename;
++    return $self->{filename};
  }
  
  1;
diff --cc lib/App/Ack/Filter/IsPathGroup.pm
index b319bd7,b319bd7..fad9772
--- a/lib/App/Ack/Filter/IsPathGroup.pm
+++ b/lib/App/Ack/Filter/IsPathGroup.pm
@@@ -1,6 -1,6 +1,5 @@@
  package App::Ack::Filter::IsPathGroup;
  
--
  =head1 NAME
  
  App::Ack::Filter::IsPathGroup
@@@ -12,10 -12,10 +11,9 @@@ calls into one container.  See App::Ack
  
  =cut
  
--
  use strict;
  use warnings;
--use base 'App::Ack::Filter';
++use parent 'App::Ack::Filter';
  
  sub new {
      my ( $class ) = @_;
@@@ -34,11 -34,11 +32,9 @@@ sub add 
  }
  
  sub filter {
--    my ( $self, $resource ) = @_;
--
--    my $data = $self->{'data'};
++    my ( $self, $file ) = @_;
  
--    return exists $data->{$resource->name};
++    return exists $self->{data}->{$file->name};
  }
  
  sub inspect {
diff --cc lib/App/Ack/Filter/Match.pm
index 15b59cf,15b59cf..7dbcd20
--- a/lib/App/Ack/Filter/Match.pm
+++ b/lib/App/Ack/Filter/Match.pm
@@@ -2,7 -2,7 +2,7 @@@ package App::Ack::Filter::Match
  
  use strict;
  use warnings;
--use base 'App::Ack::Filter';
++use parent 'App::Ack::Filter';
  
  use File::Spec 3.00;
  use App::Ack::Filter::MatchGroup ();
@@@ -13,7 -13,7 +13,7 @@@ App::Ack::Filter::Matc
  
  =head1 DESCRIPTION
  
--Implements filtering resources by their filename (regular expression).
++Implements filtering files by their filename (regular expression).
  
  =cut
  
@@@ -34,29 -34,29 +34,21 @@@ sub create_group 
  }
  
  sub filter {
--    my ( $self, $resource ) = @_;
++    my ( $self, $file ) = @_;
  
--    my $re = $self->{'regex'};
--
--    return $resource->basename =~ /$re/;
++    return $file->basename =~ /$self->{regex}/;
  }
  
  sub inspect {
      my ( $self ) = @_;
  
--    my $re = $self->{'regex'};
--
--    print ref($self) . " - $re";
--
--    return;
++    return ref($self) . ' - ' . $self->{regex};
  }
  
  sub to_string {
      my ( $self ) = @_;
  
--    my $re = $self->{'regex'};
--
--    return "filename matches $re";
++    return "Filename matches $self->{regex}";
  }
  
  BEGIN {
diff --cc lib/App/Ack/Filter/MatchGroup.pm
index d631976,d631976..d4c08ba
--- a/lib/App/Ack/Filter/MatchGroup.pm
+++ b/lib/App/Ack/Filter/MatchGroup.pm
@@@ -13,7 -13,7 +13,7 @@@ into one container.  See App::Ack::Filt
  
  use strict;
  use warnings;
--use base 'App::Ack::Filter';
++use parent 'App::Ack::Filter';
  
  sub new {
      my ( $class ) = @_;
@@@ -36,23 -36,23 +36,12 @@@ sub add 
  }
  
  sub filter {
--    my ( $self, $resource ) = @_;
++    my ( $self, $file ) = @_;
  
--    my $re = $self->{big_re};
--
--    return $resource->basename =~ /$re/;
--}
--
--sub inspect {
--    my ( $self ) = @_;
--
--    # XXX Needs an explicit return.
++    return $file->basename =~ /$self->{big_re}/;
  }
  
--sub to_string {
--    my ( $self ) = @_;
--
--    # XXX Needs an explicit return.
--}
++# This class has no inspect() or to_string() method.
++# It will just use the default one unless someone writes something useful.
  
  1;
diff --cc squash
index 3fadf0e,3fadf0e..839c04d
--- a/squash
+++ b/squash
@@@ -9,6 -9,6 +9,8 @@@ The arguments to squash are either file
  like Ack.pm, or module names like File::Next.  For modules, squash
  loads them and then pushes them into ack-standalone.
  
++POD gets stripped from all modules except for App::Ack::Docs:: modules.
++
  =cut
  
  use warnings;
@@@ -23,7 -23,7 +25,7 @@@ my $NO_EDIT_COMMENT = <<'EOCOMMENT'
  # Please DO NOT EDIT or send patches for it.
  #
  # Please take a look at the source from
--# https://github.com/beyondgrep/ack2
++# https://github.com/beyondgrep/ack3
  # and submit patches against the individual files
  # that build ack.
  #
@@@ -32,7 -32,7 +34,12 @@@ EOCOMMEN
  my $debug_mode = grep { $_ eq '--debug' } @ARGV;
  @ARGV          = grep { $_ ne '--debug' } @ARGV;
  
--my $code = "#!/usr/bin/env perl\n$NO_EDIT_COMMENT\n";
++my $code = <<"PERL";
++#!/usr/bin/env perl
++$NO_EDIT_COMMENT
++\$App::Ack::STANDALONE = 1;
++PERL
++
  for my $arg ( @ARGV ) {
      my $filename = $arg;
      if ( $arg =~ /::/ ) {
@@@ -49,7 -49,7 +56,7 @@@
      while ( <$fh> ) {
          next if /^use (?:File::Next|App::Ack)/;
  
--        s/^use base (.+)/BEGIN {\n    our \@ISA = $1\n}/;
++        s/^use parent (.+)/BEGIN {\n    our \@ISA = $1\n}/;
  
          if ( $was_in_pod && !$in_pod ) {
              $was_in_pod = 0;
@@@ -58,8 -58,8 +65,8 @@@
              }
          }
  
--        # See if we're in module POD blocks
--        if ( $filename ne 'ack' ) {
++        # Omit any POD unless it's the doc modules.
++        if ( $filename !~ m{/App/Ack/Docs/} ) {
              $was_in_pod = $in_pod;
  
              if ( /^=(\w+)/ ) {
diff --cc t/00-load.t
index dbb579e,dbb579e..7c865b8
--- a/t/00-load.t
+++ b/t/00-load.t
@@@ -2,13 -2,13 +2,9 @@@
  
  use warnings;
  use strict;
--use Test::More tests => 1;
++use Test::More tests => 23;
  
--use App::Ack;
--use App::Ack::Resource;
--use App::Ack::ConfigDefault;
--use App::Ack::ConfigFinder;
--use App::Ack::ConfigLoader;
++use App::Ack;   # For the VERSION
  use File::Next;
  use Test::Harness;
  use Getopt::Long;
@@@ -24,7 -24,7 +20,7 @@@ my @modules = qw
      Test::More
  );
  
--pass( 'All modules loaded' );
++pass( 'All external modules loaded' );
  
  diag( "Testing ack version $App::Ack::VERSION under Perl $], $^X" );
  for my $module ( @modules ) {
@@@ -33,4 -33,4 +29,24 @@@
      diag( "Using $module $ver" );
  }
  
++# Find all the .pm files in blib/ and make sure they can be C<use>d.
++my $iter = File::Next::files( { file_filter => sub { /\.pm$/ } }, 'blib' );
++while ( my $file = $iter->() ) {
++    $file =~ s/\.pm$// or die "There should be a .pm at the end of $file but there isn't";
++    my @dirs = File::Spec->splitdir( $file );
++
++    # Break apart the path, throw away blib/lib/, and reconstitute the module name.
++    my $junk = shift @dirs;
++    die unless $junk eq 'blib';
++
++    $junk = shift @dirs;
++    die unless $junk eq 'lib';
++
++    my $module = join( '::', @dirs );
++    $module =~ /^([a-z::]+)$/i or die "Invalid module name $module";
++    $module = $1;   # Untainted
++    my $rc = eval "use $module; 1;";
++    ok( $rc, "use $module" );
++}
++
  done_testing();
diff --cc t/Barfly.pm
index 0000000,0000000..7408deb
new file mode 100644
--- /dev/null
+++ b/t/Barfly.pm
@@@ -1,0 -1,0 +1,162 @@@
++package Barfly;
++
++use warnings;
++use strict;
++
++use Test::More;
++
++sub run_tests {
++    my $class    = shift;
++    my $filename = shift;
++
++    my $self = bless {
++        blocks => [],
++    }, $class;
++
++    my $block;
++    my $section;
++
++    open( my $fh, '<', $filename ) or die "Can't open $filename: $!";
++
++    my %blocknames_seen;
++    my $lineno = 0;
++    while ( my $line = <$fh> ) {
++        ++$lineno;
++        chomp $line;
++        $line =~ s/\s*$//;
++        next if $line =~ /^#/;
++        next unless $line =~ /\S/;
++
++        $line =~ s/\s+$//;
++        if ( $line =~ /^BEGIN\s+(.*)/ ) {
++            if ( defined($block) ) {
++                die 'We are already in the middle of a block';
++            }
++
++            my $blockname = $1;
++            $blocknames_seen{ $blockname }++ and die qq{Block "$blockname" is duplicated in $filename at $lineno};
++
++            $block = Barfly::Block->new( $blockname, $filename, $lineno );
++            $section = undef;
++        }
++        elsif ( $line eq 'END' ) {
++            push( @{$self->{blocks}}, $block );
++            $block = undef;
++            $section = undef;
++        }
++        elsif ( $line eq 'RUN' || $line eq 'YES' || $line eq 'NO' || $line eq 'YESLINES' ) {
++            $section = $line;
++        }
++        else {
++            $block->add_line( $section, $line );
++        }
++    }
++    close $fh or die "Can't close $filename: $!";
++
++    my @blocks = @{$self->{blocks}} or return fail( "No blocks found in $filename!" );
++    for my $block ( @blocks ) {
++        $block->run;
++    }
++
++    return;
++}
++
++
++package Barfly::Block;
++
++use Test::More;
++use Util;
++
++sub new {
++    my $class    = shift;
++    my $label    = shift // die 'Block label cannot be blank';
++    my $filename = shift;
++    my $lineno   = shift;
++
++    return bless {
++        label    => $label,
++        filename => $filename,
++        lineno   => $lineno,
++    }, $class;
++}
++
++sub add_line {
++    my $self    = shift;
++    my $section = shift;
++    my $line    = shift;
++
++    push @{$self->{$section}}, $line;
++
++    return;
++}
++
++
++sub run {
++    local $Test::Builder::Level = $Test::Builder::Level + 1;
++
++    my $self = shift;
++
++    return subtest sprintf( '%s: %s, line %d', $self->{label}, $self->{filename}, $self->{lineno} ) => sub {
++        plan tests => 2;
++
++        my @command_lines = @{$self->{RUN} // []} or die 'No RUN lines specified!';
++
++        subtest "$self->{label}: YES/NO" => sub {
++            plan tests => scalar @command_lines;
++
++            # Set up scratch file
++            my @yes = @{$self->{YES} // []};
++            my @no  = @{$self->{NO} // []};
++
++            my $tempfile = create_tempfile( @yes, @no );
++
++            for my $command_line ( @command_lines ) {
++                subtest $command_line => sub {
++                    plan tests => 2;
++
++                    $command_line = _untaint( $command_line );
++
++                    my @args = split( / /, $command_line );
++                    @args > 1 or die "Invalid command line: $command_line";
++                    shift @args eq 'ack' or die 'Command line must begin with ack';
++
++                    my @results = main::run_ack( @args, $tempfile->filename );
++                    main::lists_match( \@results, \@yes, $command_line );
++                };
++            }
++        };
++
++        subtest "$self->{label}: YESLINES" => sub {
++            return pass( 'No yeslines' ) if !$self->{YESLINES};
++
++            plan tests => scalar @command_lines;
++
++            my @all_lines   = @{$self->{YESLINES}};
++            my @input_lines = grep { /[^ ^]/ } @all_lines;
++            my $tempfile    = create_tempfile( @input_lines );
++
++            for my $command_line ( @command_lines ) {
++                subtest $command_line => sub {
++                    plan tests => 2;
++
++                    $command_line = _untaint( $command_line );
++
++                    my @args = split( / /, $command_line );
++                    @args > 1 or die "Invalid command line: $command_line";
++                    shift @args eq 'ack' or die 'Command line must begin with ack';
++
++                    push( @args, '--underline' );
++
++                    my @results = main::run_ack( @args, $tempfile->filename );
++                    main::lists_match( \@results, \@all_lines, $command_line );
++                };
++            }
++        };
++    };
++}
++
++sub _untaint {
++    return $_[0] =~ /(.*)/ ? $1 : undef;
++}
++
++1;
diff --cc t/FilterTest.pm
index b7deba0,b7deba0..a78c04a
--- a/t/FilterTest.pm
+++ b/t/FilterTest.pm
@@@ -2,9 -2,9 +2,9 @@@ package FilterTest
  
  use strict;
  use warnings;
--use base 'Exporter';
++use parent 'Exporter';
  
--use App::Ack::Resource;
++use App::Ack::File;
  use File::Next;
  use Util;
  use Test::More;
@@@ -41,7 -41,7 +41,7 @@@ sub filter_test 
          } grep {
              $filter->filter($_)
          } map {
--            App::Ack::Resource->new($_)
++            App::Ack::File->new($_)
          } swamp_files();
  
          sets_match(\@matches, $expected_matches, $msg);
diff --cc t/Util.pm
index b1eca9e,b1eca9e..793cf8c
--- a/t/Util.pm
+++ b/t/Util.pm
@@@ -1,17 -1,17 +1,25 @@@
  package Util;
  
++use 5.010001;
++
  use parent 'Exporter';
  
++use warnings;
++use strict;
++
  use Carp ();
  use Cwd ();
  use File::Next ();
  use File::Spec ();
  use File::Temp ();
++use Scalar::Util qw( tainted );
  use Term::ANSIColor ();
  use Test::More;
  
  our @EXPORT = qw(
      prep_environment
++    create_globals
++    clean_up_globals
      touch_ackrc
  
      has_io_pty
@@@ -27,6 -27,6 +35,8 @@@
      read_file
      write_file
      append_file
++    create_tempfile
++    touch
  
      reslash
      reslash_all
@@@ -43,11 -43,11 +53,13 @@@
      sets_match
      ack_lists_match
      ack_sets_match
++    ack_error_matches
  
      untaint
  
      line_split
      colorize
++    get_expected_options
      caret_X
      get_rc
      getcwd_clean
@@@ -55,37 -55,37 +67,29 @@@
      safe_chdir
      safe_mkdir
  
--    get_options
++    msg
++    subtest_name
  );
  
  my $orig_wd;
  my @temp_files; # We store temp files here to make sure they're properly reclaimed at interpreter shutdown.
  
--sub check_message {
--    my $msg = shift;
--
--    if ( !$msg ) {
--        my (undef,undef,undef,$sub) = caller(1);
--        Carp::croak( "You must pass a message to $sub" );
--    }
--
--    return $msg;
--}
--
  sub prep_environment {
--    my @ack_args   = qw( ACK_OPTIONS ACKRC ACK_PAGER HOME ACK_COLOR_MATCH ACK_COLOR_FILENAME ACK_COLOR_LINE );
++    my @ack_args   = qw( ACK_OPTIONS ACKRC ACK_PAGER HOME ACK_COLOR_MATCH ACK_COLOR_FILENAME ACK_COLOR_LINENO ACK_COLOR_COLNO );
      my @taint_args = qw( PATH CDPATH IFS ENV );
      delete @ENV{ @ack_args, @taint_args };
  
      if ( is_windows() ) {
          # To pipe, perl must be able to find cmd.exe, so add %SystemRoot%\system32 to the path.
          # See http://kstruct.com/2006/09/13/perl-taint-mode-and-cmdexe/
--        $ENV{'SystemRoot'} =~ /([A-Z]:(\\[A-Z0-9_]+)+)/i;
++        $ENV{SystemRoot} =~ /([A-Z]:(\\[A-Z0-9_]+)+)/i or die 'Unrecognizable SystemRoot';
          my $system32_dir = File::Spec->catdir($1,'system32');
          $ENV{'PATH'} = $system32_dir;
      }
  
      $orig_wd = getcwd_clean();
++
++    return;
  }
  
  sub is_windows {
@@@ -158,11 -158,11 +162,8 @@@ sub build_ack_invocation 
  
      if ( my $ackrc = $options->{ackrc} ) {
          if ( ref($ackrc) eq 'SCALAR' ) {
--            my $temp_ackrc = File::Temp->new;
++            my $temp_ackrc = create_tempfile( ${$ackrc} );
              push @temp_files, $temp_ackrc;
--
--            print { $temp_ackrc } $$ackrc, "\n";
--            close $temp_ackrc;
              $ackrc = $temp_ackrc->filename;
          }
  
@@@ -239,7 -239,7 +240,7 @@@ sub run_ack 
      my @args = @_;
  
      my ($stdout, $stderr) = run_ack_with_stderr( @args );
--    @args = grep { ref($_) ne 'HASH' } @args;
++    @args = grep { ref ne 'HASH' } @args;
  
      if ( $TODO ) {
          fail( q{Automatically fail stderr check for TODO tests.} );
@@@ -265,9 -265,9 +266,6 @@@ our $ack_return_code
  sub run_cmd {
      my ( @cmd ) = @_;
  
--    # my $cmd = join( ' ', @cmd );
--    # diag( "Running command: $cmd" );
--
      my $options = {};
  
      foreach my $arg (@cmd) {
@@@ -275,22 -275,22 +273,25 @@@
              $options = $arg;
          }
      }
--    @cmd = grep { ref($_) ne 'HASH' } @cmd;
++    @cmd = grep { ref ne 'HASH' } @cmd;
  
--    record_option_coverage(@cmd);
++    _record_option_coverage(@cmd);
  
--    check_command_for_taintedness( @cmd );
++    _check_command_for_taintedness( @cmd );
  
      my ( @stdout, @stderr );
  
      if ( is_windows() ) {
++        ## no critic ( InputOutput::ProhibitTwoArgOpen )
++        ## no critic ( InputOutput::ProhibitBarewordFileHandles )
          require Win32::ShellQuote;
          # Capture stderr & stdout output into these files (only on Win32).
--        my $catchout_file = 'stdout.log';
--        my $catcherr_file = 'stderr.log';
++        my $tempdir = File::Temp->newdir;
++        my $catchout_file = File::Spec->catfile( $tempdir->dirname, 'stdout.log' );
++        my $catcherr_file = File::Spec->catfile( $tempdir->dirname, 'stderr.log' );
  
--        open(SAVEOUT, ">&STDOUT") or die "Can't dup STDOUT: $!";
--        open(SAVEERR, ">&STDERR") or die "Can't dup STDERR: $!";
++        open(SAVEOUT, '>&STDOUT') or die "Can't dup STDOUT: $!";
++        open(SAVEERR, '>&STDERR') or die "Can't dup STDERR: $!";
          open(STDOUT, '>', $catchout_file) or die "Can't open $catchout_file: $!";
          open(STDERR, '>', $catcherr_file) or die "Can't open $catcherr_file: $!";
          my $cmd = Win32::ShellQuote::quote_system_string(@cmd);
@@@ -301,8 -301,8 +302,8 @@@
          system( $cmd );
          close STDOUT;
          close STDERR;
--        open(STDOUT, ">&SAVEOUT") or die "Can't restore STDOUT: $!";
--        open(STDERR, ">&SAVEERR") or die "Can't restore STDERR: $!";
++        open(STDOUT, '>&SAVEOUT') or die "Can't restore STDOUT: $!";
++        open(STDERR, '>&SAVEERR') or die "Can't restore STDERR: $!";
          close SAVEOUT;
          close SAVEERR;
          @stdout = read_file($catchout_file);
@@@ -367,7 -367,7 +368,7 @@@
              close $stderr_read;
  
              if (my $input = $options->{input}) {
--                check_command_for_taintedness( @{$input} );
++                _check_command_for_taintedness( @{$input} );
                  open STDIN, '-|', @{$input} or die "Can't open STDIN: $!";
              }
  
@@@ -378,7 -378,7 +379,7 @@@
          }
      } # end else not Win32
  
--    my ($sig,$core,$rc) = (($? & 127),  ($? & 128) , ($? >> 8));
++    my ($sig,$core,$rc) = (($? & 127), ($? & 128), ($? >> 8));  ## no critic ( Bangs::ProhibitBitwiseOperators )
      $ack_return_code = $rc;
      ## XXX what to do with $core or $sig?
  
@@@ -419,14 -419,14 +420,10 @@@ sub pipe_into_ack_with_stderr 
      my $input = shift;
      my @args = @_;
  
--    my $tempfile;
--
      if ( ref($input) eq 'SCALAR' ) {
          # We could easily do this without temp files, but that would take
          # slightly more time than I'm willing to spend on this right now.
--        $tempfile = File::Temp->new;
--        print {$tempfile} $$input . "\n";
--        close $tempfile;
++        my $tempfile = create_tempfile( ${$input} );
          $input = $tempfile->filename;
      }
  
@@@ -439,26 -439,26 +436,27 @@@
  # Pipe into ack and return STDOUT as array, for arguments see pipe_into_ack_with_stderr.
  sub pipe_into_ack {
      my ($stdout, $stderr) = pipe_into_ack_with_stderr( @_ );
--    return @$stdout;
++    return @{$stdout};
  }
  
  
  # Use this one if order is important.
  sub lists_match {
--    local $Test::Builder::Level = $Test::Builder::Level + 1; ## no critic
++    local $Test::Builder::Level = $Test::Builder::Level + 1;
  
      my @actual   = @{+shift};
      my @expected = @{+shift};
--    my $msg      = check_message( shift );
++    my $msg      = _check_message( shift );
  
      # Normalize all the paths
      for my $path ( @expected, @actual ) {
--        $path = File::Next::reslash( $path ); ## no critic (Variables::ProhibitPackageVars)
++        $path = File::Next::reslash( $path );
      }
  
--    return subtest "lists_match( $msg )" => sub {
++    return subtest subtest_name( $msg ) => sub {
          plan tests => 1;
  
++        my $ok;
          my $rc = eval 'use Test::Differences; 1;';
          if ( $rc ) {
              $ok = eq_or_diff( [@actual], [@expected], $msg );
@@@ -476,33 -476,33 +474,32 @@@
  }
  
  sub ack_lists_match {
--    local $Test::Builder::Level = $Test::Builder::Level + 1; ## no critic
++    local $Test::Builder::Level = $Test::Builder::Level + 1;
  
      my $args     = shift;
      my $expected = shift;
--    my $message  = check_message( shift );
--
--    my @args     = @{$args};
++    my $msg      = _check_message( shift );
  
--    my @results = run_ack( @args );
++    my @args = @{$args};
++    return subtest subtest_name( $msg, @args ) => sub {
++        plan tests => 2;
  
--    return subtest "ack_lists_match( $message )" => sub {
--        plan tests => 1;
++        my @results = run_ack( @args );
++        my $ok = lists_match( \@results, $expected, $msg );
  
--        my $ok = lists_match( \@results, $expected, $message );
--        $ok or diag( join( ' ', '$ ack', @args ) );
++        return $ok;
      };
  }
  
  # Use this one if you don't care about order of the lines.
  sub sets_match {
--    local $Test::Builder::Level = $Test::Builder::Level + 1; ## no critic
++    local $Test::Builder::Level = $Test::Builder::Level + 1;
  
      my @actual   = @{+shift};
      my @expected = @{+shift};
--    my $msg      = check_message( shift );
++    my $msg      = _check_message( shift );
  
--    return subtest "sets_match( $msg )" => sub {
++    return subtest subtest_name( $msg ) => sub {
          plan tests => 1;
  
          return lists_match( [sort @actual], [sort @expected], $msg );
@@@ -510,26 -510,26 +507,44 @@@
  }
  
  sub ack_sets_match {
--    local $Test::Builder::Level = $Test::Builder::Level + 1; ## no critic
++    local $Test::Builder::Level = $Test::Builder::Level + 1;
  
      my $args     = shift;
      my $expected = shift;
--    my $message  = check_message( shift );
--    my @args     = @{$args};
++    my $msg      = _check_message( shift );
  
--    return subtest "ack_sets_match( $message )" => sub {
++    my @args = @{$args};
++
++    return subtest subtest_name( $msg, @args ) => sub {
          plan tests => 2;
  
          my @results = run_ack( @args );
--        my $ok = sets_match( \@results, $expected, $message );
--        $ok or diag( join( ' ', '$ ack', @args ) );
  
--        return $ok;
++        return sets_match( \@results, $expected, $msg );
      };
  }
  
  
--sub record_option_coverage {
++sub ack_error_matches {
++    local $Test::Builder::Level = $Test::Builder::Level + 1;
++
++    my $args     = shift;
++    my $expected = shift;
++    my $msg      = shift;
++
++    return subtest subtest_name( $msg, $args, $expected ) => sub {
++        plan tests => 4;
++
++        my ( $stdout, $stderr ) = run_ack_with_stderr( @{$args} );
++        isnt( get_rc(), 0, 'Nonzero error' );
++        is_empty_array( $stdout, 'No normal output' );
++        is( scalar @{$stderr}, 1, 'Just one error' );
++        like( $stderr->[0], $expected, 'Error matches' );
++    };
++}
++
++
++sub _record_option_coverage {
      my ( @command_line ) = @_;
  
      return unless $ENV{ACK_OPTION_COVERAGE};
@@@ -574,7 -574,7 +589,7 @@@ Turns a multi-line input string into it
  sub colorize {
      my $input = shift;
  
--    my @lines = split( /\n/, $input );
++    my @lines = line_split( $input );
  
      for my $line ( @lines ) {
          # File name
@@@ -587,7 -587,7 +602,7 @@@
          my $n;
          $n += $line =~ s/\((.+?)\)/Term::ANSIColor::colored($1, 'black on_yellow')/eg;
  
--        $line .= "\033[0m\033[K" if $n;
++        $line .= "\e[0m\e[K" if $n;
      }
  
      return @lines;
@@@ -610,22 -610,22 +625,22 @@@ BEGIN 
              my ( @args) = @_;
  
              my @cmd = build_ack_invocation(@args);
--            @cmd    = grep { ref($_) ne 'HASH' } @cmd;
++            @cmd    = grep { ref ne 'HASH' } @cmd;
  
--            record_option_coverage(@cmd);
++            _record_option_coverage(@cmd);
  
              my $pty = IO::Pty->new;
  
              my $pid = fork;
  
--            if($pid) {
++            if ( $pid ) {
                  $pty->close_slave();
                  $pty->set_raw();
  
--                if(wantarray) {
++                if ( wantarray ) {
                      my @lines;
  
--                    while(<$pty>) {
++                    while ( <$pty> ) {
                          chomp;
                          push @lines, $_;
                      }
@@@ -636,7 -636,7 +651,7 @@@
                  else {
                      my $output = '';
  
--                    while(<$pty>) {
++                    while ( <$pty> ) {
                          $output .= $_;
                      }
                      close $pty;
@@@ -647,7 -647,7 +662,7 @@@
              else {
                  $pty->make_slave_controlling_terminal();
                  my $slave = $pty->slave();
--                if(-t *STDIN) {
++                if ( -t *STDIN ) {
                      # Is there something we can fall back on? Maybe re-opening /dev/console?
                      $slave->clone_winsize_from(\*STDIN);
                  }
@@@ -681,7 -681,7 +696,7 @@@
              Test::More::fail(<<'HERE');
  Your system doesn't seem to have IO::Pty, and the developers
  forgot to check in this test file.  Please file a bug report
--at https://github.com/beyondgrep/ack2/issues with the name of
++at https://github.com/beyondgrep/ack3/issues with the name of
  the file that generated this failure.
  HERE
          };
@@@ -691,7 -691,7 +706,7 @@@
  # This should not be treated as a complete list of the available
  # options, but it's complete enough to rely on until we find a
  # more elegant way to generate this list.
--sub get_options {
++sub get_expected_options {
      return (
          '--ackrc',
          '--after-context',
@@@ -771,6 -771,6 +786,7 @@@
          '-L',
          '-Q',
          '-R',
++        '-S',
          '-c',
          '-f',
          '-g',
@@@ -790,17 -790,17 +806,12 @@@
  
  
  # This is just a handy diagnostic tool.
--sub check_command_for_taintedness {
++sub _check_command_for_taintedness {
      my @args = @_;
  
      my $bad = 0;
  
--    my @tainted;
--    for my $arg ( @args ) {
--        if ( is_tainted( $arg ) ) {
--            push( @tainted, $arg );
--        }
--    }
++    my @tainted = grep { tainted( $_ ) } @args;
  
      if ( @tainted ) {
          die "Can't execute this command because of taintedness:\nAll args: @args\nTainted:  @tainted\n";
@@@ -810,35 -810,35 +821,23 @@@
  }
  
  
--sub is_tainted {
--    no warnings qw(void uninitialized);
--
--    return !eval { local $SIG{__DIE__} = 'DEFAULT'; join('', shift), kill 0; 1 };
--}
--
--
  sub untaint {
      my ( $s ) = @_;
  
--    $s =~ /\A(.*)\z/;
++    $s =~ /\A(.*)\z/ or die 'Somehow unable to untaint';
      return $1;
  }
  
  
  sub caret_X {
--    # XXX How is it $^X can be tainted?  We should not have to untaint it.
--    $^X =~ /(.+)/;
--    my $perl = $1;
--
--    return $perl;
++    return untaint( $^X ); # XXX How is it $^X can be tainted?  We should not have to untaint it.
  }
  
  
  sub getcwd_clean {
--    # XXX How is it that this guy is tainted?
--    my $wd = Cwd::getcwd();
--    $wd =~ /(.+)/;
--    return $1;
++    my $cwd = Cwd::getcwd();
++    $cwd =~ /./ or die 'cwd is empty';
++    return untaint( $cwd ); # XXX How is it that Cwd is tainted?
  }
  
  
@@@ -850,6 -850,6 +849,168 @@@ sub windows_slashify 
      return $str;
  }
  
++
++sub create_tempfile {
++    my @lines = @_;
++
++    my $tempfile = File::Temp->new();
++    print {$tempfile} join( "\n", @lines );
++    close $tempfile or die $!;
++
++    return $tempfile;
++}
++
++
++sub touch {
++    my $filename = shift;
++
++    open my $fh, '>>', $filename or die "Unable to append to $filename: $!";
++    close $fh or die $!;
++
++    return;
++}
++
++
++sub _check_message {
++    my $msg = shift;
++
++    if ( !defined( $msg ) ) {
++        my (undef,undef,undef,$sub) = caller(1);
++        Carp::croak( "You must pass a message to $sub" );
++    }
++
++    return $msg;
++}
++
++=head2 msg( [@args] )
++
++Returns a basic diagnostic string based on the arguments passed in.
++It is not strictly accurate, like something from Data::Dumper, but is
++meant to balance accuracy of diagnostics with ease.
++
++    msg( 'User codes', [ 'ABC', '123' ], undef, { foo => bar } )
++
++will return
++
++    'User codes, [ABC, 123], undef, { foo => bar }'
++
++=cut
++
++sub msg {
++    my @args = @_;
++
++    my @disp;
++    for my $i ( @args ) {
++        if ( !defined($i) ) {
++            push( @disp, 'undef' );
++        }
++        elsif ( ref($i) eq 'HASH' ) {
++            push( @disp, join( ', ', map { "$_=>" . ($i->{$_} // 'undef') } sort keys %{$i} ) );
++        }
++        elsif ( ref($i) eq 'ARRAY' ) {
++            push( @disp, '[' . join( ', ', map { $_ // 'undef' } @{$i} ) . ']' );
++        }
++        else {
++            push( @disp, "$i" );
++        }
++    }
++
++    return join( ', ', @disp );
++}
++
++
++=head2 subtest_name( [@args] )
++
++Returns a string for a name for a subtest, including the name of the
++subroutine and basic string representations of the arguments.
++
++This makes it easy for you to keep track of the important args passed into
++the test, and include the function name without repetitively typing it.
++
++    sub test_whatever {
++        my $user = shift;
++        my $foo  = shift;
++        my $bar  = shift;
++        my $msg  = shfit;
++
++        return subtest subtest_name( $foo, $bar, $msg ) => sub {
++            ....
++    }
++
++    test_whatever( 17, { this => 'that', other => undef }, 'Try it again without NYP' );
++
++This will then give you TAP output like this:
++
++    # Subtest: main::test_whatever( 17, {other=>undef, this=>that}, Try it again without NYP )
++
++Note that in the example, we didn't pass C<$user> because it wasn't
++interesting to debugging.
++
++=cut
++
++sub subtest_name {
++    my @args = @_;
++
++    my (undef, undef, undef, $sub) = caller(1);
++
++    ($sub ne '') or die 'subtest_name() can only be called inside a function';
++
++    return $sub unless @args;
++
++    my $disp = msg( @args );
++
++    return "$sub( $disp )";
++}
++
++
++# The tests blow up on Windows if the global files don't exist,
++# so here we create them if they don't, keeping track of the ones
++# we make so we can delete them later.
++
++my @created_global_files;
++
++sub create_globals {
++    my @files;
++
++    if ( is_windows() ) {
++        require Win32;
++
++        my @paths = map {
++            File::Spec->catfile( Win32::GetFolderPath( $_ ), 'ackrc' )
++        } (
++            Win32::CSIDL_COMMON_APPDATA(),
++            Win32::CSIDL_APPDATA()
++        );
++
++        # Brute-force untaint the paths we built so they can be unlinked later.
++        @files = map { /(.+)/ ? $1 : die } @paths;
++    }
++    else {
++        @files = ( '/etc/ackrc' );
++    }
++
++    if ( is_windows() || is_cygwin() ) {
++        for my $filename ( @files ) {
++            if ( not -e $filename ) {
++                touch_ackrc( $filename );
++                push @created_global_files, $filename;
++            }
++        }
++    }
++
++    return @files;
++}
++
++
++sub clean_up_globals {
++    foreach my $filename ( @created_global_files ) {
++        unlink $filename or warn "Couldn't unlink $filename: $!";
++    }
++
++    return;
++}
++
++
  sub touch_ackrc {
      my $filename = shift or die;
      write_file( $filename, () );
diff --cc t/ack-1.t
index 341e0e4,341e0e4..868cf50
--- a/t/ack-1.t
+++ b/t/ack-1.t
@@@ -58,9 -58,9 +58,8 @@@ DASH_G: 
  }
  
  DASH_L: {
--    my $target   = 'the';
      my @files    = reslash( 't/text' );
--    my @args     = ( '-1', '-l', '--sort-files', $target );
++    my @args     = qw( -1 -l --sort-files the );   # --sort-files to make sure we get the same first file each time.
      my @results  = run_ack( @args, @files );
      my $expected = reslash( 't/text/amontillado.txt' );
  
diff --cc t/ack-color.t
index f7134b5,f7134b5..d0b046d
--- a/t/ack-color.t
+++ b/t/ack-color.t
@@@ -8,13 -8,13 +8,19 @@@ use Test::More
  use lib 't';
  use Util;
  
--plan tests => 11;
++plan tests => 13;
  
  prep_environment();
  
--my $match_start = "\e[30;43m";
--my $match_end   = "\e[0m";
--my $line_end    = "\e[0m\e[K";
++my $match_start  = "\e[30;43m";
++my $match_end    = "\e[0m";
++my $line_end     = "\e[0m\e[K";
++
++my $green_start  = "\e[1;32m";
++my $green_end    = "\e[0m";
++
++my $yellow_start = "\e[1;33m";
++my $yellow_end   = "\e[0m";
  
  NORMAL_COLOR: {
      my @files = qw( t/text/bill-of-rights.txt );
@@@ -58,8 -58,8 +64,53 @@@ ADJACENT_CAPTURE_COLORING: 
      my @args = qw( (Temp)(ter) --color );
      my @results = run_ack( @args, @files );
  
--    # The double end + start is kinda weird; this test could probably be more robust.
      is_deeply( \@results, [
--        "Whether ${match_start}Temp${match_end}${match_start}ter${match_end} sent, or whether tempest tossed thee here ashore,",
++        "Whether ${match_start}Tempter${match_end} sent, or whether tempest tossed thee here ashore,$line_end",
      ] );
  }
++
++
++subtest 'Heading colors, single line' => sub {
++    plan tests => 4;
++
++    # Without the column number
++    my $file = reslash( 't/text/ozymandias.txt' );
++    my @args = qw( mighty -i -w --color -H );
++    my @results = run_ack( @args, $file );
++
++    is_deeply( \@results, [
++        "${green_start}$file${green_end}:${yellow_start}11${yellow_end}:Look on my works, ye ${match_start}Mighty${match_end}, and despair!'$line_end",
++    ] );
++
++    # With column number
++    @results = run_ack( @args, '--column', $file );
++    is_deeply( \@results, [
++        "${green_start}$file${green_end}:${yellow_start}11${yellow_end}:${yellow_start}22${yellow_end}:Look on my works, ye ${match_start}Mighty${match_end}, and despair!'$line_end",
++    ] );
++};
++
++
++subtest 'Heading colors, grouped' => sub {
++    plan tests => 4;
++
++    # Without the column number
++    my $file = reslash( 't/text/ozymandias.txt' );
++    my @args = qw( mighty -i -w --color --group );
++    my @results = run_ack( @args, 't/text' );
++
++    is_deeply( \@results, [
++        "${green_start}$file${green_end}",
++        "${yellow_start}11${yellow_end}:Look on my works, ye ${match_start}Mighty${match_end}, and despair!'$line_end",
++    ] );
++
++    # With column number
++    @results = run_ack( @args, '--column', 't/text' );
++    is_deeply( \@results, [
++        "${green_start}$file${green_end}",
++        "${yellow_start}11${yellow_end}:${yellow_start}22${yellow_end}:Look on my works, ye ${match_start}Mighty${match_end}, and despair!'$line_end",
++    ] );
++};
++
++done_testing();
++
++exit 0;
diff --cc t/ack-column.t
index 3b19746,3b19746..4e75010
--- a/t/ack-column.t
+++ b/t/ack-column.t
@@@ -5,6 -5,6 +5,7 @@@ use strict
  
  use Test::More tests => 4;
  
++
  use lib 't';
  use Util;
  
@@@ -59,3 -59,3 +60,6 @@@ HER
  
      lists_match( \@results, \@expected, 'Checking without column numbers' );
  }
++
++done_testing();
++exit 0;
diff --cc t/ack-files-from.t
index da80144,da80144..acb0d40
--- a/t/ack-files-from.t
+++ b/t/ack-files-from.t
@@@ -4,22 -4,22 +4,22 @@@ use strict
  use warnings;
  use lib 't';
  
--use File::Temp;
  use Test::More tests => 3;
++
  use Util;
  
  prep_environment();
  
  
  subtest 'Basic reading from files, no switches' => sub {
--    plan tests => 2;
++    plan tests => 1;
  
      my $target_file = reslash( 't/swamp/options.pl' );
      my @expected = line_split( <<"HERE" );
  $target_file:2:use strict;
  HERE
  
--    my $tempfile = fill_temp_file( qw( t/swamp/options.pl t/swamp/pipe-stress-freaks.F ) );
++    my $tempfile = create_tempfile( qw( t/swamp/options.pl t/swamp/pipe-stress-freaks.F ) );
  
      ack_lists_match( [ '--files-from=' . $tempfile->filename, 'strict' ], \@expected, 'Looking for strict in multiple files' );
  
@@@ -43,7 -43,7 +43,7 @@@ subtest 'Non-existent file specified' =
  subtest 'Source file exists, but non-existent files mentioned in the file' => sub {
      plan tests => 4;
  
--    my $tempfile = fill_temp_file( qw( t/swamp/options.pl file-that-isnt-there ) );
++    my $tempfile = create_tempfile( qw( t/swamp/options.pl file-that-isnt-there ) );
      my ( $stdout, $stderr ) = run_ack_with_stderr( '--files-from=' . $tempfile->filename, 'CASE');
  
      is( scalar @{$stdout}, 1, 'One hit found' );
@@@ -53,13 -53,13 +53,6 @@@
      like( $stderr->[0], qr/file-that-isnt-there: No such file/, 'Correct warning message for non-existent file' );
  };
  
++done_testing();
  
--sub fill_temp_file {
--    my @lines = @_;
--
--    my $tempfile = File::Temp->new;
--    print {$tempfile} "$_\n" for @lines;
--    close $tempfile;
--
--    return $tempfile;
--}
++exit 0;
diff --cc t/ack-g.t
index 21827ea,21827ea..9347301
--- a/t/ack-g.t
+++ b/t/ack-g.t
@@@ -11,6 -11,6 +11,8 @@@ use Util
  prep_environment();
  
  subtest 'No starting directory specified' => sub {
++    plan tests => 3;
++
      my $regex = 'non';
  
      my @files = qw( t/foo/non-existent );
@@@ -23,7 -23,7 +25,10 @@@
          'Correct warning message for non-existent file' );
  };
  
++
  subtest 'regex comes before -g on the command line' => sub {
++    plan tests => 3;
++
      my $regex = 'non';
  
      my @files = qw( t/foo/non-existent );
@@@ -36,7 -36,7 +41,10 @@@
          'Correct warning message for non-existent file' );
  };
  
++
  subtest 'No metacharacters' => sub {
++    plan tests => 1;
++
      my @expected = qw(
          t/swamp/Makefile
          t/swamp/Makefile.PL
@@@ -52,6 -52,6 +60,8 @@@
  
  
  subtest 'With metacharacters' => sub {
++    plan tests => 1;
++
      my @expected = qw(
          t/swamp/html.htm
          t/swamp/html.html
@@@ -64,9 -64,9 +74,14 @@@
      ack_sets_match( [ @args, @files ], \@expected, "Looking for $regex" );
  };
  
++
  subtest 'Front anchor' => sub {
++    plan tests => 1;
++
      my @expected = qw(
++        t/file-iterator.t
          t/file-permission.t
++        t/filetype-detection.t
          t/filetypes.t
          t/filter.t
      );
@@@ -78,7 -78,7 +93,10 @@@
      ack_sets_match( [ @args, @files ], \@expected, "Looking for $regex" );
  };
  
++
  subtest 'Back anchor' => sub {
++    plan tests => 1;
++
      my @expected = qw(
          t/runtests.pl
          t/swamp/options-crlf.pl
@@@ -93,7 -93,7 +111,10 @@@
      ack_sets_match( [ @args, @files ], \@expected, "Looking for $regex" );
  };
  
++
  subtest 'Case-insensitive via -i' => sub {
++    plan tests => 1;
++
      my @expected = qw(
          t/swamp/pipe-stress-freaks.F
      );
@@@ -105,7 -105,7 +126,10 @@@
      ack_sets_match( [ @args, @files ], \@expected, "Looking for -i -g $regex " );
  };
  
++
  subtest 'Case-insensitive via (?i:)' => sub {
++    plan tests => 1;
++
      my @expected = qw(
          t/swamp/pipe-stress-freaks.F
      );
@@@ -117,7 -117,7 +141,10 @@@
      ack_sets_match( [ @args, @files ], \@expected, "Looking for $regex" );
  };
  
++
  subtest 'File on command line is always searched' => sub {
++    plan tests => 1;
++
      my @expected = ( 't/swamp/#emacs-workfile.pl#' );
      my $regex = 'emacs';
  
@@@ -127,7 -127,7 +154,10 @@@
      ack_sets_match( [ @args, @files ], \@expected, 'File on command line is always searched' );
  };
  
++
  subtest 'File on command line is always searched, even with wrong filetype' => sub {
++    plan tests => 1;
++
      my @expected = qw(
          t/swamp/parrot.pir
      );
@@@ -139,8 -139,8 +169,11 @@@
      ack_sets_match( [ @args, @files ], \@expected, 'File on command line is always searched, even with wrong type.' );
  };
  
++
  subtest '-Q works on -g' => sub {
--    my @expected = ();
++    plan tests => 1;
++
++    my @expected = qw();
      my $regex = 'ack-g.t$';
  
      my @files = qw( t );
@@@ -149,7 -149,7 +182,10 @@@
      ack_sets_match( [ @args, @files ], \@expected, "Looking for $regex with quotemeta." );
  };
  
++
  subtest '-w works on -g' => sub {
++    plan tests => 1;
++
      my @expected = qw(
          t/text/number.txt
      );
@@@ -161,7 -161,7 +197,10 @@@
      ack_sets_match( [ @args, @files ], \@expected, "Looking for $regex with '-w'." );
  };
  
++
  subtest '-v works on -g' => sub {
++    plan tests => 1;
++
      my @expected = qw(
          t/text/bill-of-rights.txt
          t/text/gettysburg.txt
@@@ -174,7 -174,7 +213,10 @@@
      ack_sets_match( [ @args, @files ], \@expected, "Looking for file names that do not match $file_regex" );
  };
  
++
  subtest '--smart-case works on -g' => sub {
++    plan tests => 2;
++
      my @expected = qw(
          t/swamp/pipe-stress-freaks.F
          t/swamp/crystallography-weenies.f
@@@ -188,12 -188,12 +230,15 @@@
      @expected = qw(
          t/swamp/pipe-stress-freaks.F
      );
--    @args = ( '--smart-case', '-g', 'F$' );
++    @args = ( '-S', '-g', 'F$' );
  
      ack_sets_match( [ @args, @files ], \@expected, 'Looking for f$' );
  };
  
++
  subtest 'test exit codes' => sub {
++    plan tests => 4;
++
      my $file_regex = 'foo';
      my @files      = ( 't/text/' );
  
@@@ -206,7 -206,7 +251,10 @@@
      is( get_rc(), 0, '-g with matches must exit with 0' );
  };
  
++
  subtest 'test -g on a path' => sub {
++    plan tests => 1;
++
      my $file_regex = 'text';
      my @expected   = qw(
          t/context.t
@@@ -224,7 -224,7 +272,10 @@@
      ack_sets_match( [ @args ], \@expected, 'Make sure -g matches the whole path' );
  };
  
++
  subtest 'test -g with --color' => sub {
++    plan tests => 2;
++
      my $file_regex = 'text';
      my $expected_original = <<'HERE';
  t/con(text).t
@@@ -249,6 -249,6 +300,7 @@@ HER
      is_deeply( \@results, \@expected, 'Colorizing -g output with --color should work');
  };
  
++
  subtest q{test -g without --color; make sure colors don't show} => sub {
      if ( !has_io_pty() ) {
          plan skip_all => 'IO::Pty is required for this test';
@@@ -278,3 -278,3 +330,5 @@@ HER
  };
  
  done_testing();
++
++exit 0;
diff --cc t/ack-h.t
index c363311,c363311..b33754a
--- a/t/ack-h.t
+++ b/t/ack-h.t
@@@ -55,7 -55,7 +55,7 @@@ HER
  
  WITH_SWITCHES_MULTIPLE_FILES: {
      for my $opt ( qw( -h --no-filename ) ) {
--        my @expected = line_split( <<"HERE" );
++        my @expected = line_split( <<'HERE' );
  use strict;
  HERE
  
@@@ -66,3 -66,3 +66,7 @@@
          lists_match( \@results, \@expected, "Looking for strict in multiple files with $opt" );
      }
  }
++
++done_testing();
++
++exit 0;
diff --cc t/ack-help-types.t
index f13b736,f13b736..9e0a77f
--- a/t/ack-help-types.t
+++ b/t/ack-help-types.t
@@@ -37,3 -37,3 +37,7 @@@ foreach my $option ( @options ) 
          }
      }
  }
++
++done_testing();
++
++exit 0;
diff --cc t/ack-help.t
index 98253f1,98253f1..9ee7e64
--- a/t/ack-help.t
+++ b/t/ack-help.t
@@@ -60,7 -60,7 +60,7 @@@ sub option_in_usage 
      return;
  }
  
--my @options = get_options();
++my @options = get_expected_options();
  
  plan tests => scalar(@options);
  
diff --cc t/ack-i.barfly
index 0000000,0000000..719c6b3
new file mode 100644
--- /dev/null
+++ b/t/ack-i.barfly
@@@ -1,0 -1,0 +1,140 @@@
++BEGIN No -i, or -I
++
++RUN
++ack foo
++ack -I foo
++ack --no-smart-case foo
++
++YES
++football
++foo bar
++
++NO
++Football
++foOtball
++
++END
++
++
++BEGIN Normal -i
++
++RUN
++ack -i foo
++ack -i Foo
++ack --ignore-case foo
++ack --ignore-case Foo
++
++YES
++football
++foo bar
++Football
++foOtball
++
++NO
++
++END
++
++
++BEGIN --smart-case invoked
++
++RUN
++ack --smart-case foo
++ack -S foo
++
++YES
++football
++foo bar
++Football
++foOtball
++
++NO
++
++END
++
++
++BEGIN --smart-case bypassed
++
++RUN
++ack --smart-case Foo
++ack -S Foo
++
++YES
++Football
++
++NO
++football
++foo bar
++foOtball
++
++END
++
++
++BEGIN --smart-case overrides -i
++
++RUN
++ack -i --smart-case Foo
++ack -i -S Foo
++
++YES
++Football
++
++NO
++football
++foo bar
++foOtball
++
++END
++
++
++BEGIN -i overrides --smart-case
++
++RUN
++ack --smart-case -i Foo
++ack --smart-case -i foo
++ack -S -i Foo
++ack -S -i foo
++
++YES
++Football
++football
++foo bar
++foOtball
++
++NO
++
++END
++
++
++BEGIN -I overrides -i
++
++RUN
++ack --i -I Foo
++
++YES
++Football
++
++NO
++foOtball
++football
++foo bar
++
++END
++
++
++BEGIN -I overrides --smart-case
++
++RUN
++ack --smart-case -I Foo
++ack -S -I Foo
++
++YES
++Football
++
++NO
++football
++foo bar
++foOtball
++
++END
++
++# vi:set ft=barfly:
diff --cc t/ack-i.t
index 1598dab,1598dab..2290701
--- a/t/ack-i.t
+++ b/t/ack-i.t
@@@ -4,26 -4,26 +4,36 @@@ use strict
  use warnings;
  use lib 't';
  
++use Test::More tests => 9;
++
  use Util;
--use Test::More tests => 4;
++use Barfly;
  
  prep_environment();
  
--my @expected = (
--    't/swamp/groceries/fruit:1:apple',
--    't/swamp/groceries/junk:1:apple fritters',
--);
++Barfly->run_tests( 't/ack-i.barfly' );
++
++subtest 'Straight -i' => sub {
++    plan tests => 4;
++
++    my @expected = (
++        't/swamp/groceries/fruit:1:apple',
++        't/swamp/groceries/junk:1:apple fritters',
++    );
++
++    my @targets = map { "t/swamp/groceries/$_" } qw( fruit junk meat );
++
++    my @args    = qw( --nocolor APPLE -i );
++    my @results = run_ack( @args, @targets );
  
--my @targets = map {
--    "t/swamp/groceries/$_"
--} qw/fruit junk meat/;
++    lists_match( \@results, \@expected, '-i flag' );
  
--my @args    = ( qw( --nocolor APPLE -i ), @targets );
--my @results = run_ack( @args );
++    @args    = qw( --nocolor APPLE --ignore-case );
++    @results = run_ack( @args, @targets );
  
--lists_match( \@results, \@expected, '-i flag' );
++    lists_match( \@results, \@expected, '--ignore-case flag' );
++};
  
-- at args    = ( qw( --nocolor APPLE --ignore-case ), @targets );
-- at results = run_ack( @args );
++done_testing();
  
--lists_match( \@results, \@expected, '--ignore-case flag' );
++exit 0;
diff --cc t/ack-ignore-file.t
index f765fcb,f765fcb..7007f45
--- a/t/ack-ignore-file.t
+++ b/t/ack-ignore-file.t
@@@ -2,9 -2,9 +2,102 @@@
  
  use strict;
  use warnings;
--use Test::More skip_all => 'Not yet implemented';
  
--# XXX look at --ignore-dir, but apply to --ignore-file
++use Test::More tests => 3;
  
--# XXX is: match: ext: firstlinematch:
--# XXX --no-ignorefile
++use lib 't';
++use Util;
++
++prep_environment();
++
++my @all = qw(
++    t/swamp/groceries/another_subdir/fruit
++    t/swamp/groceries/another_subdir/junk
++    t/swamp/groceries/another_subdir/meat
++    t/swamp/groceries/dir.d/fruit
++    t/swamp/groceries/dir.d/junk
++    t/swamp/groceries/dir.d/meat
++    t/swamp/groceries/fruit
++    t/swamp/groceries/junk
++    t/swamp/groceries/meat
++    t/swamp/groceries/subdir/fruit
++    t/swamp/groceries/subdir/junk
++    t/swamp/groceries/subdir/meat
++);
++my @meat = grep { /meat/ } @all;
++my @junk = grep { /junk/ } @all;
++
++
++subtest 'is:xxx matching' => sub {
++    plan tests => 5;
++
++    ack_sets_match(
++        [qw( -f t/swamp/groceries )],
++        [ @all ],
++        'Unfiltered'
++    );
++
++    ack_sets_match(
++        [qw( -f t/swamp/groceries --ignore-file=is:fruit )],
++        [ @meat, @junk ],
++        'Ignoring fruit with is'
++    );
++
++    ack_sets_match(
++        [qw( -f t/swamp/groceries --ignore-file=is:bongo )],
++        [ @all ],
++        'Ignoring with is that does not match'
++    );
++
++    ack_sets_match(
++        [qw( -f t/swamp/groceries --ignore-file=is:subdir )],
++        [ @all ],
++        '--ignore-file only operatoes on filenames, not dirnames'
++    );
++    ack_sets_match(
++        [qw( -f t/swamp/groceries --ignore-file=is:fruit --ignore-file=is:junk )],
++        [ @meat ],
++        'Multiple is arguments'
++    );
++};
++
++
++subtest 'match:xxx matching' => sub {
++    plan tests => 6;
++
++    # The match is case-insensitive, unaffected by -i or -I.
++    for my $u ( 'u', 'U' ) {
++        for my $I ( '-i', '-I', undef ) {
++            my @args = ( qw( -f t/swamp/groceries ), "--ignore-file=match:$u" );
++            push( @args, $I ) if defined $I;
++            ack_sets_match(
++                [ @args ],
++                [ @meat ],
++                'Should only match files with do not have "u" in them: ' . join( ' ', map { $_ // 'undef' } @args )
++            );
++        }
++    }
++};
++
++
++subtest 'Invalid invocation' => sub {
++    plan tests => 8;
++
++    my @bad_args = (
++        '--ignore-file=foo',
++        '--ignore-file=foo:bar',
++    );
++
++    for my $bad_arg ( @bad_args ) {
++        my ( $man_output, $man_stderr ) = run_ack_with_stderr( $bad_arg );
++
++        is_empty_array( $man_output, "No output for $bad_arg" );
++        is( scalar @{$man_stderr}, 2, "Exactly two errors for $bad_arg" );
++        like( $man_stderr->[0], qr/ack(?:-standalone)?: Unknown filter type 'foo'.  Type must be one of: ext, firstlinematch, is, match./ );
++        like( $man_stderr->[1], qr/ack(?:-standalone)?: Invalid option on command line/ );
++    }
++};
++
++
++done_testing();
++exit 0;
diff --cc t/ack-known-types.t
index a24deb2,a24deb2..615e9e2
--- a/t/ack-known-types.t
+++ b/t/ack-known-types.t
@@@ -44,6 -44,6 +44,7 @@@ t/swamp/stuff.cmak
  t/swamp/example.R
  t/swamp/fresh.css
  t/swamp/lua-shebang-test
++t/swamp/notes.md
  );
  
  my @files_no_perl = qw(
@@@ -69,6 -69,6 +70,7 @@@ t/swamp/stuff.cmak
  t/swamp/example.R
  t/swamp/fresh.css
  t/swamp/lua-shebang-test
++t/swamp/notes.md
  );
  
  ack_sets_match( [ '--known-types', '-f', 't/swamp' ], \@files, '--known-types test #1' );
diff --cc t/ack-line.t
index 76906b0,76906b0..2ae842a
--- a/t/ack-line.t
+++ b/t/ack-line.t
@@@ -13,7 -13,7 +13,7 @@@ if ( not has_io_pty() ) 
      exit(0);
  }
  
--plan tests => 16;
++plan tests => 12;
  
  prep_environment();
  
@@@ -55,23 -55,23 +55,6 @@@ HER
      ack_sets_match( [ @args, @files ], \@expected, 'Looking for lines 3 to 6' );
  }
  
--LINES_WITH_CONTEXT: {
--    my @expected = line_split( <<'HERE' );
--of that House shall agree to pass the Bill, it shall be sent, together
--with the Objections, to the other House, by which it shall likewise be
--reconsidered, and if approved by two thirds of that House, it shall become
--a Law. But in all such Cases the Votes of both Houses shall be determined
--by Yeas and Nays, and the Names of the Persons voting for and against
--the Bill shall be entered on the Journal of each House respectively. If
--any Bill shall not be returned by the President within ten Days (Sundays
--HERE
--
--    my @files = qw( t/text/constitution.txt );
--    my @args = qw( --lines=156 -C3 );
--
--    ack_lists_match( [ @files, @args ], \@expected, 'Looking for line 3 with two lines of context' );
--}
--
  LINES_THAT_MAY_BE_NON_EXISTENT: {
      my @expected = line_split( <<'HERE' );
  "For the love of God, Montresor!"
@@@ -84,20 -84,20 +67,6 @@@ HER
      ack_sets_match( [ @args, @files ], \@expected, 'Looking for non existent line' );
  }
  
--LINE_AND_PASSTHRU: {
--    my @expected = line_split( <<'HERE' );
--=head1 Dummy document
--
--=head2 There's important stuff in here!
--HERE
--
--    my @files = qw( t/swamp/perl.pod );
--    my @args = qw( --lines=2 --passthru );
--
--    ack_lists_match( [ @args, @files ], \@expected, 'Checking --passthru behaviour with --line' );
--}
--
--
  LINE_1_MULTIPLE_FILES: {
      my @target_file = map { reslash( $_ ) } qw(
          t/swamp/c-header.h
@@@ -138,7 -138,7 +107,7 @@@ HER
      ack_lists_match( [ @args, @files ], \@expected, 'Looking for first line in multiple files' );
  }
  
--LINE_NO_WARNINGS: {
++LINENO_WARNINGS: {
      my @expected = (
          'Well, my daddy left home when I was three',
      );
@@@ -151,15 -151,15 +120,29 @@@
  }
  
  LINE_WITH_REGEX: {
--    # Specifying both --line and a regex should result in an error.
--    my @files = qw( t/text/boy-named-sue.txt );
++    # Specifying both --lines and a regex should result in an error.
      my @args = qw( --lines=1 --match Sue );
++    my @files = qw( t/text/boy-named-sue.txt );
++
++    ack_error_matches( [@args, at files], qr/Options '--lines' and '--match' are mutually exclusive/ );
++}
++
++LINES_WITH_CONTEXT: {
++    for my $arg ( qw( -A -B -C ) ) {
++        my @args = ( '--lines=156', "${arg}3" );
++        my @files = qw( t/text/constitution.txt );
  
--    my ($stdout, $stderr) = run_ack_with_stderr( @args, @files );
--    isnt( get_rc(), 0, 'Specifying both --line and --match must lead to an error RC' );
--    is_empty_array( $stdout, 'No normal output' );
--    is( scalar @{$stderr}, 1, 'One line of stderr output' );
--    like( $stderr->[0], qr/\Q(Sue)/, 'Error message must contain "(Sue)"' );
++        ack_error_matches( [@args, at files], qr/Options '--lines' and '$arg' are mutually exclusive/ );
++    }
++}
++
++LINE_AND_PASSTHRU: {
++    my @args = qw( --lines=2 --passthru );
++    my @files = qw( t/swamp/perl.pod );
++
++    ack_error_matches( [@args, at files], qr/Options '--lines' and '--passthru' are mutually exclusive/ );
  }
  
  done_testing();
++
++exit 0;
diff --cc t/ack-m.t
index 97b9def,97b9def..3714851
--- a/t/ack-m.t
+++ b/t/ack-m.t
@@@ -3,20 -3,20 +3,22 @@@
  use strict;
  use warnings;
  
--use Test::More tests => 6;
++use Test::More tests => 2;
  
  use lib 't';
  use Util;
  
  prep_environment();
  
--LIMIT_MATCHES_RETURNED: {
++subtest 'Basic -m' => sub {
++    plan tests => 2;
++
      my @text = sort map { untaint($_) } glob( 't/text/[bc]*.txt' );
  
      my $bill_ = reslash( 't/text/bill-of-rights.txt' );
      my $const = reslash( 't/text/constitution.txt' );
  
--    my @expected = line_split( <<"HERE" );
++    my @expected = split( /\n/, <<"HERE" );
  $bill_:4:or prohibiting the free exercise thereof; or abridging the freedom of
  $bill_:5:speech, or of the press; or the right of the people peaceably to assemble,
  $bill_:6:and to petition the Government for a redress of grievances.
@@@ -27,22 -27,22 +29,25 @@@ HER
  
      ack_lists_match( [ '-m', 3, '-w', 'the', @text ], \@expected, 'Should show only 3 lines per file' );
  
--    @expected = line_split( <<"HERE" );
++    @expected = split( /\n/, <<"HERE" );
  $bill_:4:or prohibiting the free exercise thereof; or abridging the freedom of
  HERE
  
  ack_lists_match( [ '-1', '-w', 'the', @text ], \@expected, 'We should only get one line back for the entire run, not just per file.' );
--}
++};
++
  
++subtest '-m with -L' => sub {
++    plan tests => 2;
  
--DASH_L: {
--    my $target   = 'the';
      my @files    = reslash( 't/text' );
--    my @args     = ( '-m', 3, '-l', '--sort-files', $target );
++    my @args     = ( '-m', 3, '-l', '--sort-files', 'the' );
      my @results  = run_ack( @args, @files );
--    my @expected = map { reslash( "t/text/$_" ) } (
--        'amontillado.txt', 'bill-of-rights.txt', 'constitution.txt'
--    );
++    my @expected = map { reslash( "t/text/$_" ) } qw( amontillado.txt bill-of-rights.txt constitution.txt );
  
      is_deeply(\@results, \@expected);
--}
++};
++
++done_testing();
++
++exit 0;
diff --cc t/ack-man.t
index 0000000,0000000..d540bac
new file mode 100644
--- /dev/null
+++ b/t/ack-man.t
@@@ -1,0 -1,0 +1,75 @@@
++#!perl -T
++
++use strict;
++use warnings;
++
++use lib 't';
++use Util;
++
++use Test::More tests => 2;
++
++prep_environment();
++
++# Some things to expect, not all.
++my @manual_sections = qw(
++    AUTHOR
++    BUGS
++    SUPPORT
++);
++
++my @faq_sections = qw(
++    FAQ
++);
++
++subtest 'ack --man' => sub {
++    local $TODO = 'ack-standalone currently dumps all sections';
++    plan tests => 5;
++
++    my ($stdout, $stderr) = run_ack_with_stderr( '--man' );
++    is_empty_array( $stderr, 'Nothing in STDERR' );
++    want( $stdout, \@manual_sections );
++    dont( $stdout, \@faq_sections );
++};
++
++subtest 'ack --faq' => sub {
++    local $TODO = 'ack-standalone currently dumps all sections';
++    plan tests => 5;
++
++    my ($stdout, $stderr) = run_ack_with_stderr( '--faq' );
++    is_empty_array( $stderr, 'Nothing in STDERR' );
++    want( $stdout, \@faq_sections );
++    dont( $stdout, \@manual_sections );
++};
++
++done_testing();
++
++exit 0;
++
++
++sub want {
++    local $Test::Builder::Level = $Test::Builder::Level + 1;
++
++    my $stdout   = shift;
++    my $sections = shift;
++    # We're sloppy with checking for headings because of potential embedded ANSI codes.
++    for my $wanted ( @{$sections} ) {
++        my $found = scalar grep { /\Q$wanted/ } @{$stdout};
++        is( $found, 1, "Found one $wanted section" );
++    }
++
++    return;
++}
++
++
++sub dont {
++    local $Test::Builder::Level = $Test::Builder::Level + 1;
++
++    my $stdout   = shift;
++    my $sections = shift;
++
++    for my $verboten ( @{$sections} ) {
++        is_empty_array( [grep { /\Q$verboten/ } @{$stdout}], "Find zero $verboten sections" );
++    }
++
++    return;
++}
diff --cc t/ack-match.t
index 0cc51c3,0cc51c3..859cd5c
--- a/t/ack-match.t
+++ b/t/ack-match.t
@@@ -3,8 -3,8 +3,6 @@@
  use strict;
  use warnings;
  
--use File::Spec ();
--use File::Temp ();
  use Test::More;
  use lib 't';
  use Util;
@@@ -18,73 -18,73 +16,29 @@@ my @tests = 
      [ qw/gon -w/ ], # words            is handled correctly with --match
  );
  
--plan tests => @tests + 11;
++plan tests => @tests + 2;
  
  test_match( @{$_} ) for @tests;
  
  # Giving only the --match argument (and no other args) should not result in an error.
  run_ack( '--match', 'Sue' );
  
--# Not giving a regex when piping into ack should result in an error.
--my ($stdout, $stderr) = pipe_into_ack_with_stderr( 't/text/amontillado.txt', '--perl' );
--isnt( get_rc(), 0, 'ack should return an error when piped into without a regex' );
--is_empty_array( $stdout, 'ack should return no STDOUT when piped into without a regex' );
--cmp_ok( scalar @{$stderr}, '>', 0, 'Has to have at least one line of error message, but could have more under Appveyor' );
++subtest 'Not giving a regex when piping into ack should result in an error' => sub {
++    plan tests => 4;
  
--my $name = $ENV{ACK_TEST_STANDALONE} ? 'ack-standalone' : 'ack';
--is( $stderr->[0], "$name: No regular expression found.", 'Error message matches' );
++    # Not giving a regex when piping into ack should result in an error.
++    my ($stdout, $stderr) = pipe_into_ack_with_stderr( 't/text/amontillado.txt', '--perl' );
++    isnt( get_rc(), 0, 'ack should return an error when piped into without a regex' );
++    is_empty_array( $stdout, 'ack should return no STDOUT when piped into without a regex' );
++    cmp_ok( scalar @{$stderr}, '>', 0, 'Has to have at least one line of error message, but could have more under Appveyor' );
  
--my $wd      = getcwd_clean();
--my $tempdir = File::Temp->newdir;
--safe_mkdir( File::Spec->catdir($tempdir->dirname, 'subdir') );
++    my $name = $ENV{ACK_TEST_STANDALONE} ? 'ack-standalone' : 'ack';
++    is( $stderr->[0], "$name: No regular expression found.", 'Error message matches' );
++};
  
--PROJECT_ACKRC_MATCH_FORBIDDEN: {
--    my @files = untaint( File::Spec->rel2abs('t/text/') );
--    my @args = qw/ --env /;
++done_testing();
  
--    safe_chdir( $tempdir->dirname );
--    write_file '.ackrc', "--match=question\n";
--
--    my ( $stdout, $stderr ) = run_ack_with_stderr(@args, @files);
--
--    is_empty_array( $stdout );
--    first_line_like( $stderr, qr/\QOptions --output, --pager and --match are forbidden in project .ackrc files/ );
--
--    safe_chdir( $wd );
--}
--
--HOME_ACKRC_MATCH_PERMITTED: {
--    my @files = untaint( File::Spec->rel2abs('t/text/') );
--    my @args = qw/ --env /;
--
--    write_file(File::Spec->catfile($tempdir->dirname, '.ackrc'), "--match=question\n");
--    safe_chdir( File::Spec->catdir($tempdir->dirname, 'subdir') );
--    local $ENV{'HOME'} = $tempdir->dirname;
--
--    my ( $stdout, $stderr ) = run_ack_with_stderr(@args, @files);
--
--    is_nonempty_array( $stdout );
--    is_empty_array( $stderr );
--
--    safe_chdir( $wd );
--}
--
--ACKRC_ACKRC_MATCH_PERMITTED: {
--    my @files = untaint( File::Spec->rel2abs('t/text/') );
--    my @args = qw/ --env /;
--
--    write_file(File::Spec->catfile($tempdir->dirname, '.ackrc'), "--match=question\n");
--    safe_chdir( File::Spec->catdir($tempdir->dirname, 'subdir') );
--    local $ENV{'ACKRC'} = File::Spec->catfile($tempdir->dirname, '.ackrc');
--
--    my ( $stdout, $stderr ) = run_ack_with_stderr(@args, @files);
--
--    is_nonempty_array( $stdout );
--    is_empty_array( $stderr );
--
--    safe_chdir( $wd );
--}
--done_testing;
++exit 0;
  
  # Call ack normally and compare output to calling with --match regex.
  #
@@@ -96,7 -96,7 +50,7 @@@ sub test_match 
      my @args  = @_;
      push @args, '--sort-files';
  
--    return subtest "test_match( @args )" => sub {
++    return subtest subtest_name( @args ) => sub {
          my @files = ( 't/text' );
          my @results_normal = run_ack( @args, $regex, @files );
          my @results_match  = run_ack( @args, @files, '--match', $regex );
diff --cc t/ack-n.t
index f327a06,f327a06..aae884b
--- a/t/ack-n.t
+++ b/t/ack-n.t
@@@ -7,12 -7,12 +7,12 @@@ use Test::More tests => 10
  use lib 't';
  use Util;
  
--my $expected_norecurse = <<'END';
++my @expected_norecurse = line_split( <<'END' );
  t/swamp/groceries/fruit:1:apple
  t/swamp/groceries/junk:1:apple fritters
  END
  
--my $expected_recurse = <<'END';
++my @expected_recurse = line_split( <<'END' );
  t/swamp/groceries/another_subdir/fruit:1:apple
  t/swamp/groceries/another_subdir/junk:1:apple fritters
  t/swamp/groceries/dir.d/fruit:1:apple
@@@ -23,37 -23,37 +23,37 @@@ t/swamp/groceries/subdir/fruit:1:appl
  t/swamp/groceries/subdir/junk:1:apple fritters
  END
  
--chomp $expected_norecurse;
--chomp $expected_recurse;
--
  if ( is_windows() ) {
--    $expected_norecurse =~ s{/}{\\}g;
--    $expected_recurse =~ s{/}{\\}g;
++    s{/}{\\}g for ( @expected_norecurse, @expected_recurse );
  }
  
  my @args;
--my $lines;
++my @lines;
  
  prep_environment();
  
  # We sort to ensure deterministic results.
-- at args  = ('-n', '--sort-files', 'apple', 't/swamp/groceries');
--$lines = run_ack(@args);
--lists_match $lines, $expected_norecurse, '-n should disable recursion';
++ at args  = qw( -n --sort-files apple t/swamp/groceries );
++ at lines = run_ack(@args);
++lists_match( \@lines, \@expected_norecurse, '-n should disable recursion' );
  
-- at args  = ('--no-recurse', '--sort-files', 'apple', 't/swamp/groceries');
--$lines = run_ack(@args);
--lists_match $lines, $expected_norecurse, '--no-recurse should disable recursion';
++ at args  = qw( --no-recurse --sort-files apple t/swamp/groceries );
++ at lines = run_ack(@args);
++lists_match( \@lines, \@expected_norecurse, '--no-recurse should disable recursion' );
  
  # Make sure that re-enabling recursion works.
-- at args  = ('-n', '-r', '--sort-files', 'apple', 't/swamp/groceries');
--$lines = run_ack(@args);
--lists_match $lines, $expected_recurse, '-r after -n should re-enable recursion';
++ at args  = qw( -n -r --sort-files apple t/swamp/groceries );
++ at lines = run_ack(@args);
++lists_match( \@lines, \@expected_recurse, '-r after -n should re-enable recursion' );
++
++ at args  = qw( --no-recurse -R --sort-files apple t/swamp/groceries );
++ at lines = run_ack(@args);
++lists_match( \@lines, \@expected_recurse, '-R after --no-recurse should re-enable recursion' );
++
++ at args  = qw( --no-recurse --recurse --sort-files apple t/swamp/groceries );
++ at lines = run_ack(@args);
++lists_match( \@lines, \@expected_recurse, '--recurse after --no-recurse should re-enable recursion' );
  
-- at args  = ('--no-recurse', '-R', '--sort-files', 'apple', 't/swamp/groceries');
--$lines = run_ack(@args);
--lists_match $lines, $expected_recurse, '-R after --no-recurse should re-enable recursion';
++done_testing();
  
-- at args  = ('--no-recurse', '--recurse', '--sort-files', 'apple', 't/swamp/groceries');
--$lines = run_ack(@args);
--lists_match $lines, $expected_recurse, '--recurse after --no-recurse should re-enable recursion';
++exit 0;
diff --cc t/ack-named-pipes.t
index 7a50640,7a50640..0000000
deleted file mode 100644,100644
--- a/t/ack-named-pipes.t
+++ /dev/null
@@@ -1,62 -1,62 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--use lib 't';
--
--use File::Temp;
--use Test::More;
--use Util;
--use POSIX ();
--
--local $SIG{'ALRM'} = sub {
--    fail 'Timeout';
--    exit;
--};
--
--prep_environment();
--
--my $tempdir = File::Temp->newdir;
--safe_mkdir( "$tempdir/foo" );
--my $rc = eval { POSIX::mkfifo( "$tempdir/foo/test.pipe", oct(660) ) };
--if ( !$rc ) {
--    dir_cleanup( $tempdir );
--    plan skip_all => $@ ? $@ : q{I can't run a mkfifo, so cannot run this test.};
--}
--
--plan tests => 2;
--
--touch( "$tempdir/foo/bar.txt" );
--
--alarm 5; # Should be plenty of time.
--
--my @results = run_ack( '-f', $tempdir );
--
--is_deeply( \@results, [
--    "$tempdir/foo/bar.txt",
--], 'Acking should not find the fifo' );
--
--dir_cleanup( $tempdir );
--
--done_testing();
--
--sub dir_cleanup {
--    my $tempdir = shift;
--
--    unlink "$tempdir/foo/bar.txt";
--    rmdir "$tempdir/foo";
--    rmdir $tempdir;
--
--    return;
--}
--
--
--sub touch {
--    my $filename = shift;
--
--    my $fh;
--    open $fh, '>>', $filename or die "Unable to append to $filename: $!";
--    close $fh;
--
--    return;
--}
diff --cc t/ack-o.t
index 86d0dc6,86d0dc6..bd91ee4
--- a/t/ack-o.t
+++ b/t/ack-o.t
@@@ -3,9 -3,9 +3,7 @@@
  use warnings;
  use strict;
  
--use Test::More tests => 11;
--use File::Spec ();
--use File::Temp ();
++use Test::More tests => 4;
  
  use lib 't';
  use Util;
@@@ -48,6 -48,6 +46,24 @@@ HER
  }
  
  
++# Give an output function and find match in multiple files (so print filenames, just like grep -o).
++WITH_OUTPUT: {
++    my @files = qw( t/text/ );
++    my @args = qw/ --output=x$1x free(\\S+) --sort-files /;
++
++    my @target_file = map { reslash($_) } qw(
++        t/text/bill-of-rights.txt
++        t/text/gettysburg.txt
++    );
++    my @expected = (
++        "$target_file[0]:4:xdomx",
++        "$target_file[1]:23:xdomx",
++    );
++
++    ack_sets_match( [ @args, @files ], \@expected, 'Find all the things with --output function' );
++}
++
++
  # Find a match in multiple files, and output it in double quotes.
  OUTPUT_DOUBLE_QUOTES: {
      my @files = qw( t/text/ );
@@@ -69,55 -69,55 +85,4 @@@
      ack_sets_match( [ @args, @files ], \@expected, 'Find all the things with --output function' );
  }
  
--my $wd      = getcwd_clean();
--my $tempdir = File::Temp->newdir;
--safe_mkdir( File::Spec->catdir($tempdir->dirname, 'subdir') );
--
--PROJECT_ACKRC_OUTPUT_FORBIDDEN: {
--    my @files = untaint( File::Spec->rel2abs('t/text/') );
--    my @args = qw/ --env question(\\S+) /;
--
--    safe_chdir( $tempdir->dirname );
--    write_file '.ackrc', "--output=foo\n";
--
--    my ( $stdout, $stderr ) = run_ack_with_stderr(@args, @files);
--
--    is_empty_array( $stdout );
--    first_line_like( $stderr, qr/\QOptions --output, --pager and --match are forbidden in project .ackrc files/ );
--
--    safe_chdir( $wd );
--}
--
--HOME_ACKRC_OUTPUT_PERMITTED: {
--    my @files = untaint( File::Spec->rel2abs('t/text/') );
--    my @args = qw/ --env question(\\S+) --sort-files /;
--
--    write_file(File::Spec->catfile($tempdir->dirname, '.ackrc'), "--output=foo\n");
--    safe_chdir( File::Spec->catdir($tempdir->dirname, 'subdir') );
--    local $ENV{'HOME'} = $tempdir->dirname;
--
--    my ( $stdout, $stderr ) = run_ack_with_stderr(@args, @files);
--
--    is_nonempty_array( $stdout );
--    is_empty_array( $stderr );
--
--    safe_chdir( $wd );
--}
--
--ACKRC_ACKRC_OUTPUT_PERMITTED: {
--    my @files = untaint( File::Spec->rel2abs('t/text/') );
--    my @args = qw/ --env question(\\S+) --sort-files /;
--
--    write_file(File::Spec->catfile($tempdir->dirname, '.ackrc'), "--output=foo\n");
--    safe_chdir( File::Spec->catdir($tempdir->dirname, 'subdir') );
--    local $ENV{'ACKRC'} = File::Spec->catfile($tempdir->dirname, '.ackrc');
--
--    my ( $stdout, $stderr ) = run_ack_with_stderr(@args, @files);
--
--    is_nonempty_array( $stdout );
--    is_empty_array( $stderr );
--
--    safe_chdir( $wd );
--}
--
  done_testing();
diff --cc t/ack-output.t
index 3ad4066,3ad4066..1a70f59
--- a/t/ack-output.t
+++ b/t/ack-output.t
@@@ -3,7 -3,7 +3,7 @@@
  use warnings;
  use strict;
  
--use Test::More tests => 24;
++use Test::More tests => 42;
  
  use lib 't';
  use Util;
@@@ -12,11 -12,11 +12,11 @@@ prep_environment()
  
  ARG: {
      my @expected = line_split( <<'HERE' );
--shall have a new birth of freedom -- and that government of the people,
++shall have a new birth of freedom -- and that government of the people,xshall have a new birth of freedom -- and that government of the people,
  HERE
  
      my @files = qw( t/text/gettysburg.txt );
--    my @args = qw( free --output=$_ );
++    my @args = qw( free --output=$_x$_ );
      my @results = run_ack( @args, @files );
  
      lists_match( \@results, \@expected, 'Matching line' );
@@@ -95,7 -95,7 +95,7 @@@ PREMATCH_MULTIPLE_FILES: 
  }
  
  POSTMATCH: {
--    my @expected = line_split( <<'HERE' );
++    my @expected = split( /\n/, <<'HERE' );
   -- and that government of the people,
  HERE
  
@@@ -172,3 -172,3 +172,136 @@@ HER
  
      lists_match( \@results, \@expected, 'Line number' );
  }
++
++LAST_PAREN_MATCH: {
++    my @expected = line_split( <<'HERE' );
++t/text/amontillado.txt:124:love
++t/text/amontillado.txt:309:love
++t/text/amontillado.txt:311:love
++t/text/constitution.txt:267:hate
++HERE
++
++    my @files = qw( t/text/ );
++    my @args = qw( (love)|(hate) --sort-files --output=$+ );
++    my @results = run_ack( @args, @files );
++
++    lists_match( \@results, \@expected, 'Last paren match' );
++}
++
++
++COMBOS_1: {
++    my @expected = line_split( <<'HERE' );
++t/text/amontillado.txt:124:love-124-d; you are happy,
++t/text/amontillado.txt:309:love-309- of God, Montresor!"
++t/text/amontillado.txt:311:love-311- of God!"
++t/text/constitution.txt:267:hate-267-ver, from any King, Prince, or foreign State.
++HERE
++
++    my @files = qw( t/text/ );
++    my @args = qw( (love)|(hate) --sort-files --output=$+-$.-$' );
++    my @results = run_ack( @args, @files );
++
++    lists_match( \@results, \@expected, 'Combos 1' );
++}
++
++
++COMBOS_2: {
++    my @expected = line_split( <<'HERE' );
++t/text/amontillado.txt:124:happy-happy-happy
++t/text/raven.txt:73:happy-happy-happy
++HERE
++
++    my @files = qw( t/text/ );
++    my @args = qw( (happy) --sort-files -i --output=$1-$&-$1 );
++    my @results = run_ack( @args, @files );
++
++    lists_match( \@results, \@expected, 'Combos 2' );
++}
++
++
++COMBOS_3: {
++    my @expected = line_split( <<'HERE' );
++t/text/amontillado.txt:124:precious. You are rich, respected, admired, beloved; you are ---,--happy
++t/text/raven.txt:73:Caught from some un--- master whom unmerciful Disaster--happy
++HERE
++
++    my @files = qw( t/text/ );
++    my @args = qw( (happy) --sort-files -i --output=$`---$'--$+ );
++    my @results = run_ack( @args, @files );
++
++    lists_match( \@results, \@expected, 'Combos 2' );
++}
++
++
++NUMERIC_SUBSTITUTIONS: {
++    # Make sure that substitutions don't affect future substitutions.
++    my @expected = line_split( <<'HERE' );
++t/text/constitution.txt:269:Section 10 on line 269
++HERE
++
++    my @files = qw( t/text/bill-of-rights.txt t/text/constitution.txt );
++    my @args = ( '(\d\d)', '--output=Section $1 on line $.' );
++    my @results = run_ack( @args, @files );
++
++    lists_match( \@results, \@expected, 'Numeric substitutions' );
++}
++
++
++CHARACTER_SUBSTITUTIONS: {
++    # Make sure that substitutions don't affect future substitutions.
++    my @expected = line_split( <<"HERE" );
++t/text/bill-of-rights.txt:15:No Soldier shall, in time of peace be
++in any house, without\tin any house, without
++HERE
++
++    my @files = qw( t/text/ );
++    my @args = ( '\s+quartered\s+(.+)', '--output=$`\n$1\t$1' );
++    my @results = run_ack( @args, @files );
++
++    lists_match( \@results, \@expected, 'Character substitutions' );
++}
++
++
++# $f is the filenname, needed for grep, emulating ack2 $filename:$lineno:$_
++FILENAME_SUBSTITUTION_1 : {
++    my @expected = line_split( <<'HERE' );
++t/text/ozymandias.txt:4:Half sunk, a shattered visage lies, whose frown,
++HERE
++
++    my @files = qw( t/text/ozymandias.txt );
++    my @args = qw( visage --output=$f:$.:$_ );
++    my @results = run_ack( @args, @files );
++
++    lists_match( \@results, \@expected, 'Filename with matching line' );
++}
++
++
++FILENAME_SUBSTITUTION_2 : {
++    my @expected = line_split( <<'HERE' );
++t/text/ozymandias.txt:4:visage
++HERE
++
++    my @files = qw( t/text/ozymandias.txt );
++    my @args = qw( visage --output=$f:$.:$& );
++    my @results = run_ack( @args, @files );
++
++    lists_match( \@results, \@expected, 'Filename with match' );
++}
++
++
++FILENAME_SUBSTITUTION_3 : {
++    my @expected = line_split( <<'HERE' );
++t/text/ozymandias.txt:4:visage
++HERE
++
++    my @files = qw( t/text/ozymandias.txt );
++    my @args = qw( (visage) --output=$f:$.:$+ );
++    my @results = run_ack( @args, @files );
++
++    lists_match( \@results, \@expected, 'Filename with last match' );
++}
++
++
++
++done_testing();
++exit 0;
diff --cc t/ack-pager.t
index 6fe0010,6fe0010..082401a
--- a/t/ack-pager.t
+++ b/t/ack-pager.t
@@@ -3,8 -3,8 +3,6 @@@
  use strict;
  use warnings;
  
--use File::Spec ();
--use File::Temp ();
  use Test::More;
  
  use lib 't';
@@@ -15,7 -15,7 +13,7 @@@ if ( not has_io_pty() ) 
      exit(0);
  }
  
--plan tests => 15;
++plan tests => 9;
  
  prep_environment();
  
@@@ -66,7 -66,7 +64,14 @@@ HER
  }
  
  PAGER_WITH_OPTS: {
--    my @args = ('--nocolor', '--pager=./test-pager --skip=2', '--sort-files', '-i', 'nevermore', 't/text');
++    my @args = (
++        '--nocolor',
++        '--pager=./test-pager --skip=2',    # --skip is an argument passed to test-pager
++        '--sort-files',
++        '-i',
++        'nevermore',
++        't/text',
++    );
  
      my @expected = line_split( <<'HERE' );
  t/text/raven.txt
@@@ -83,8 -83,8 +88,15 @@@ HER
  }
  
  FORCE_NO_PAGER: {
--    my @args = ('--nocolor', '--pager=./test-pager --skip=2', '--nopager', '--sort-files',
--        '-i', 'nevermore', 't/text');
++    my @args = (
++        '--nocolor',
++        '--pager=./test-pager --skip=2',    # --skip is an argument passed to test-pager
++        '--nopager',
++        '--sort-files',
++        '-i',
++        'nevermore',
++        't/text',
++    );
  
      my @expected = line_split( <<'HERE' );
  t/text/raven.txt
@@@ -110,7 -110,7 +122,7 @@@ PAGER_ENV: 
      local $ENV{'ACK_PAGER'} = './test-pager --skip=2';
      local $TODO             = q{Setting ACK_PAGER in tests won't work for the time being};
  
--    my @args = ('--nocolor', '--sort-files', '-i', 'nevermore', 't/text');
++    my @args = qw( --nocolor --sort-files -i nevermore t/text );
  
      my @expected = line_split( <<'HERE' );
  t/text/raven.txt
@@@ -129,7 -129,7 +141,7 @@@ HER
  PAGER_ENV_OVERRIDE: {
      local $ENV{'ACK_PAGER'} = './test-pager --skip=2';
  
--    my @args = ('--nocolor', '--nopager', '--sort-files', '-i', 'nevermore', 't/text');
++    my @args = qw( --nocolor --nopager --sort-files -i nevermore t/text );
  
      my @expected = line_split( <<'HERE' );
  t/text/raven.txt
@@@ -153,7 -153,7 +165,7 @@@ HER
  
  
  PAGER_ACKRC: {
--    my @args = ('--nocolor', '--sort-files', '-i', 'nevermore', 't/text');
++    my @args = qw( --nocolor --sort-files -i nevermore t/text );
  
      my $ackrc = <<'HERE';
  --pager=./test-pager --skip=2
@@@ -177,7 -177,7 +189,7 @@@ HER
  
  
  PAGER_ACKRC_OVERRIDE: {
--    my @args = ('--nocolor', '--nopager', '--sort-files', '-i', 'nevermore', 't/text');
++    my @args = qw( --nocolor --nopager --sort-files -i nevermore t/text );
  
      my $ackrc = <<'HERE';
  --pager=./test-pager --skip=2
@@@ -208,7 -208,7 +220,7 @@@ HER
  PAGER_NOENV: {
      local $ENV{'ACK_PAGER'} = './test-pager --skip=2';
  
--    my @args = ('--nocolor', '--noenv', '--sort-files', '-i', 'nevermore', 't/text');
++    my @args = qw( --nocolor --noenv --sort-files -i nevermore t/text );
  
      my @expected = line_split( <<'HERE' );
  t/text/raven.txt
@@@ -230,57 -230,57 +242,6 @@@ HER
      lists_match( \@got, \@expected, 'PAGER_NOENV' );
  }
  
--my $wd      = getcwd_clean();
--my $tempdir = File::Temp->newdir;
--my $pager   = File::Spec->rel2abs('test-pager');
--safe_mkdir( File::Spec->catdir($tempdir->dirname, 'subdir') );
--
--PROJECT_ACKRC_PAGER_FORBIDDEN: {
--    my @files = untaint( File::Spec->rel2abs('t/text/') );
--    my @args = qw/ --env question(\\S+) /;
--
--    safe_chdir( $tempdir->dirname );
--    write_file '.ackrc', "--pager=$pager\n";
--
--    my ( $stdout, $stderr ) = run_ack_with_stderr(@args, @files);
--
--    is_empty_array( $stdout );
--    first_line_like( $stderr, qr/\QOptions --output, --pager and --match are forbidden in project .ackrc files/ );
--
--    safe_chdir( $wd );
--}
--
--HOME_ACKRC_PAGER_PERMITTED: {
--    my @files = untaint( File::Spec->rel2abs('t/text/') );
--    my @args = qw/ --env question(\\S+) /;
--
--    write_file(File::Spec->catfile($tempdir->dirname, '.ackrc'), "--pager=$pager\n");
--    safe_chdir( File::Spec->catdir($tempdir->dirname, 'subdir') );
--    local $ENV{'HOME'} = $tempdir->dirname;
--
--    my ( $stdout, $stderr ) = run_ack_with_stderr(@args, @files);
--
--    is_nonempty_array( $stdout );
--    is_empty_array( $stderr );
--
--    safe_chdir( $wd );
--}
--
--ACKRC_ACKRC_PAGER_PERMITTED: {
--    my @files = untaint( File::Spec->rel2abs('t/text/') );
--    my @args = qw/ --env question(\\S+) /;
--
--    write_file(File::Spec->catfile($tempdir->dirname, '.ackrc'), "--pager=$pager\n");
--    safe_chdir( File::Spec->catdir($tempdir->dirname, 'subdir') );
--    local $ENV{'ACKRC'} = File::Spec->catfile($tempdir->dirname, '.ackrc');
--
--    my ( $stdout, $stderr ) = run_ack_with_stderr(@args, @files);
--
--    is_nonempty_array( $stdout );
--    is_empty_array( $stderr );
--
--    safe_chdir( $wd );
--}
  
  done_testing();
  exit 0;
diff --cc t/ack-passthru.t
index 170ce53,170ce53..e3960e3
--- a/t/ack-passthru.t
+++ b/t/ack-passthru.t
@@@ -50,6 -50,6 +50,9 @@@ SKIP: 
      is( scalar @escaped_lines, 2, 'Only two lines are highlighted' );
  }
  
++done_testing();
++exit 0;
++
  __DATA__
  Four score and seven years ago our fathers brought forth on this
  continent, a new nation, conceived in Liberty, and dedicated to the
diff --cc t/ack-proximate.t
index 0000000,0000000..b34e8b8
new file mode 100644
--- /dev/null
+++ b/t/ack-proximate.t
@@@ -1,0 -1,0 +1,166 @@@
++#!perl -T
++
++use warnings;
++use strict;
++
++use Test::More tests => 5;
++
++use lib 't';
++use Util;
++
++prep_environment();
++
++my $const = reslash( 't/text/constitution.txt' );
++my $bill  = reslash( 't/text/bill-of-rights.txt' );
++
++subtest 'Grouped proximate' => sub {
++    plan tests => 1;
++
++    my @expected = line_split( <<"HERE" );
++$bill
++53:fact tried by a jury, shall be otherwise re-examined in any Court of
++
++$const
++199:To constitute Tribunals inferior to the supreme Court;
++
++372:Judges of the supreme Court, and all other Officers of the United States,
++
++376:in the Courts of Law, or in the Heads of Departments.
++
++404:Court, and in such inferior Courts as the Congress may from time to
++
++406:Courts, shall hold their Offices during good Behaviour, and shall, at
++
++425:and those in which a State shall be Party, the supreme Court shall
++
++427:the supreme Court shall have appellate Jurisdiction, both as to Law and
++
++441:of two Witnesses to the same overt Act, or on Confession in open Court.
++HERE
++
++    my @files = qw( t/text );
++    my @args = qw( --proximate -i --group --sort court );
++
++    ack_lists_match( [ @args, @files ], \@expected, 'Grouped proximate' );
++};
++
++
++subtest 'Ungrouped proximate' => sub {
++    plan tests => 1;
++
++    my @expected = line_split( <<"HERE" );
++$bill:53:fact tried by a jury, shall be otherwise re-examined in any Court of
++
++$const:199:To constitute Tribunals inferior to the supreme Court;
++
++$const:372:Judges of the supreme Court, and all other Officers of the United States,
++
++$const:376:in the Courts of Law, or in the Heads of Departments.
++
++$const:404:Court, and in such inferior Courts as the Congress may from time to
++
++$const:406:Courts, shall hold their Offices during good Behaviour, and shall, at
++
++$const:425:and those in which a State shall be Party, the supreme Court shall
++
++$const:427:the supreme Court shall have appellate Jurisdiction, both as to Law and
++
++$const:441:of two Witnesses to the same overt Act, or on Confession in open Court.
++HERE
++
++    my @files = qw( t/text );
++    my @args = qw( --proximate -i --nogroup --sort court );
++
++    ack_lists_match( [ @args, @files ], \@expected, 'Ungrouped proximate' );
++};
++
++
++subtest 'Grouped proximate=2' => sub {
++    plan tests => 1;
++
++    my @expected = line_split( <<"HERE" );
++$bill
++53:fact tried by a jury, shall be otherwise re-examined in any Court of
++
++$const
++199:To constitute Tribunals inferior to the supreme Court;
++
++372:Judges of the supreme Court, and all other Officers of the United States,
++
++376:in the Courts of Law, or in the Heads of Departments.
++
++404:Court, and in such inferior Courts as the Congress may from time to
++406:Courts, shall hold their Offices during good Behaviour, and shall, at
++
++425:and those in which a State shall be Party, the supreme Court shall
++427:the supreme Court shall have appellate Jurisdiction, both as to Law and
++
++441:of two Witnesses to the same overt Act, or on Confession in open Court.
++HERE
++
++    my @files = qw( t/text );
++    my @args = qw( --proximate=2 --group -i court );
++
++    ack_lists_match( [ @args, @files ], \@expected, 'Grouped proximate=2' );
++};
++
++
++
++subtest 'Ungrouped proximate=2' => sub {
++    plan tests => 1;
++
++    my @expected = line_split( <<"HERE" );
++$bill:53:fact tried by a jury, shall be otherwise re-examined in any Court of
++
++$const:199:To constitute Tribunals inferior to the supreme Court;
++
++$const:372:Judges of the supreme Court, and all other Officers of the United States,
++
++$const:376:in the Courts of Law, or in the Heads of Departments.
++
++$const:404:Court, and in such inferior Courts as the Congress may from time to
++$const:406:Courts, shall hold their Offices during good Behaviour, and shall, at
++
++$const:425:and those in which a State shall be Party, the supreme Court shall
++$const:427:the supreme Court shall have appellate Jurisdiction, both as to Law and
++
++$const:441:of two Witnesses to the same overt Act, or on Confession in open Court.
++HERE
++
++    my @files = qw( t/text );
++    my @args = qw( --proximate=2 --nogroup -i court );
++
++    ack_lists_match( [ @args, @files ], \@expected, 'Ungrouped proximate=2' );
++};
++
++
++
++subtest 'Ungrouped proximate=20' => sub {
++    plan tests => 1;
++
++    my @expected = line_split( <<"HERE" );
++$bill:53:fact tried by a jury, shall be otherwise re-examined in any Court of
++
++$const:199:To constitute Tribunals inferior to the supreme Court;
++
++$const:372:Judges of the supreme Court, and all other Officers of the United States,
++$const:376:in the Courts of Law, or in the Heads of Departments.
++
++$const:404:Court, and in such inferior Courts as the Congress may from time to
++$const:406:Courts, shall hold their Offices during good Behaviour, and shall, at
++$const:425:and those in which a State shall be Party, the supreme Court shall
++$const:427:the supreme Court shall have appellate Jurisdiction, both as to Law and
++$const:441:of two Witnesses to the same overt Act, or on Confession in open Court.
++HERE
++
++    my @files = qw( t/text );
++    my @args = qw( --proximate=20 --nogroup -i court );
++
++    ack_lists_match( [ @args, @files ], \@expected, 'Ungrouped proximate=20' );
++};
++
++
++
++done_testing();
++
++exit 0;
diff --cc t/ack-removed-options.t
index 701b08f,701b08f..0000000
deleted file mode 100644,100644
--- a/t/ack-removed-options.t
+++ /dev/null
@@@ -1,34 -1,34 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--
--use Test::More;
--use lib 't';
--use Util;
--
--prep_environment();
--
--my @options = (qw{
--    -a
--    --all
--    -u
--}, ['-G', 'sue']);
--
--
--plan tests => scalar @options;
--
--foreach my $option (@options) {
--    my @args = ref($option) ? @{$option} : ( $option );
--    $option  = $option->[0] if ref($option);
--    push @args, 'the', 't/text';
--
--    my ( $stdout, $stderr ) = run_ack_with_stderr( @args );
--
--    subtest "options = @args" => sub {
--        plan tests => 2;
--
--        is_empty_array( $stdout, 'Nothing in stdout' );
--        like( $stderr->[0], qr/Option '$option' is not valid in ack 2/, 'Found error message' );
--    };
--}
diff --cc t/ack-s.t
index bfacb41,bfacb41..9696fe9
--- a/t/ack-s.t
+++ b/t/ack-s.t
@@@ -36,8 -36,8 +36,8 @@@ WITH_RESTRICTED_DIR: 
      safe_chdir( $dir->dirname );
  
      safe_mkdir( 'foo' );
--    write_file 'foo/bar' => "hello\n";
--    write_file 'baz'     => "hello\n";
++    write_file( 'foo/bar' => "hello\n" );
++    write_file( 'baz'     => "hello\n" );
  
      chmod 0000, 'foo';
      chmod 0000, 'baz';
@@@ -48,3 -48,3 +48,6 @@@
  
      safe_chdir( $wd );
  }
++
++done_testing();
++exit 0;
diff --cc t/ack-type-del.t
index a89c1dc,a89c1dc..66f3a79
--- a/t/ack-type-del.t
+++ b/t/ack-type-del.t
@@@ -35,7 -35,7 +35,7 @@@ unlike( $help_types_output, qr/\Q--[no]
  DUMP: {
      my @dump_output = run_ack( '--type-del=perl', '--type-del=perltest', '--dump' );
      # discard everything up to the ARGV section
--    while(@dump_output && $dump_output[0] ne 'ARGV') {
++    while ( @dump_output && $dump_output[0] ne 'ARGV' ) {
          shift @dump_output;
      }
      shift @dump_output; # discard ARGV
diff --cc t/ack-type.t
index e232323,e232323..a47d953
--- a/t/ack-type.t
+++ b/t/ack-type.t
@@@ -2,10 -2,10 +2,10 @@@
  
  use strict;
  use warnings;
--use lib 't';
  
--use Cwd ();
  use Test::More tests => 16;
++
++use lib 't';
  use Util;
  
  prep_environment();
@@@ -25,8 -25,8 +25,9 @@@ t/swamp/perl.pl:1:#!perl -
  t/swamp/perl.pm:1:#!perl -T
  HERE
  
--    foreach my $line ( @expected ) {
--        $line =~ s/^(.*?)(?=:)/reslash( $1 )/ge;
++    # Reslash the filenames in case we are on Windows.
++    foreach ( @expected ) {
++        s/^(.*?)(?=:)/reslash( $1 )/ge;
      }
  
      my @args    = qw( --type=perl --nogroup --noheading --nocolor );
@@@ -43,8 -43,8 +44,9 @@@ t/swamp/c-header.h:1:/*    perl.
  t/swamp/Makefile:1:# This Makefile is for the ack extension to perl.
  HERE
  
--    foreach my $line ( @expected ) {
--        $line =~ s/^(.*?)(?=:)/reslash( $1 )/ge;
++    # Reslash the filenames in case we are on Windows.
++    for ( @expected ) {
++        s/^(.*?)(?=:)/reslash( $1 )/ge;
      }
  
      my @args    = qw( --type=noperl --nogroup --noheading --nocolor );
@@@ -120,3 -120,3 +122,6 @@@ HER
      });
      is_deeply( \@lines, \@expected );
  }
++
++done_testing();
++exit 0;
diff --cc t/ack-u.barfly
index 0000000,0000000..0f3bac3
new file mode 100644
--- /dev/null
+++ b/t/ack-u.barfly
@@@ -1,0 -1,0 +1,47 @@@
++# BEGIN comment here
++#
++# RUN
++# ack command line(s)
++# They should NOT be shell-escaped.  Args are split on whitespace before
++# being passed in to ack.
++#
++# YESLINES
++# Lines that should match with underlines shown
++#                              ^^^^^^^^^^
++# YES
++# Lines that should match, but without the underlines.
++#
++# NO
++# Lines that should not match.
++#
++# END
++#
++# Blank lines are always ignored.
++
++
++BEGIN Back-references
++
++RUN
++ack (\w+)-\1
++ack (?:(\w+)-\1)
++
++
++YES
++Bamm-Bamm
++Ack-Ack
++foo-foo
++
++NO
++Bamm Bamm
++Bamm-Bam
++
++YESLINES
++Pebbles & Bamm-Bamm
++          ^^^^^^^^^
++
++ha-ha-ha-ha-ho-ho-hee-hee-hoo-hah
++^^^^^ ^^^^^ ^^^^^ ^^^^^^^
++
++END
++
++# vi:set ft=barfly:
diff --cc t/ack-u.t
index 0000000,0000000..6e7705e
new file mode 100644
--- /dev/null
+++ b/t/ack-u.t
@@@ -1,0 -1,0 +1,70 @@@
++#!perl -T
++
++use warnings;
++use strict;
++
++use Test::More tests => 4;
++
++use lib 't';
++use Util;
++use Barfly;
++
++prep_environment();
++
++Barfly->run_tests( 't/ack-u.barfly' );
++
++my $bill_ = reslash( 't/text/bill-of-rights.txt' );
++my $space = ' ' x length($bill_);
++
++subtest 'Single file' => sub {
++    plan tests => 1;
++
++    my @expected = line_split( <<'HERE' );
++A well regulated Militia, being necessary to the security of a free State,
++                 ^^^^^^^
++cases arising in the land or naval forces, or in the Militia, when in
++                                                     ^^^^^^^
++HERE
++
++    my @files = $bill_;
++    my @args  = ( qw( -u ), 'Militia' );
++
++    ack_lists_match( [ @args, @files ], \@expected, 'Single file' );
++};
++
++
++subtest 'Grouped' => sub {
++    plan tests => 1;
++
++    my @expected = line_split( <<"HERE" );
++$bill_
++10:A well regulated Militia, being necessary to the security of a free State,
++                    ^^^^^^^
++31:cases arising in the land or naval forces, or in the Militia, when in
++                                                        ^^^^^^^
++HERE
++
++    my @files = qw( t/text/bill-of-rights.txt t/text/ozymandias.txt ); # Don't want Constitution in here.
++    my @args  = ( qw( -u --group ), 'Militia' );
++
++    ack_lists_match( [ @args, @files ], \@expected, 'Grouped' );
++};
++
++
++subtest 'Not grouped, with leading filename' => sub {
++    my @expected = line_split( <<"HERE" );
++$bill_:10:A well regulated Militia, being necessary to the security of a free State,
++$space                     ^^^^^^^
++$bill_:31:cases arising in the land or naval forces, or in the Militia, when in
++$space                                                         ^^^^^^^
++HERE
++
++    my $regex = 'Militia';
++    my @files = $bill_;
++    my @args  = ( qw( -u --nogroup -H ), $regex );
++
++    ack_lists_match( [ @args, @files ], \@expected, "Looking for $regex - before with line numbers" );
++};
++done_testing();
++
++exit 0;
diff --cc t/ack-underline.t
index 0000000,0000000..a02c419
new file mode 100644
--- /dev/null
+++ b/t/ack-underline.t
@@@ -1,0 -1,0 +1,107 @@@
++#!perl -T
++
++use warnings;
++use strict;
++
++use Test::More tests => 4;
++
++use lib 't';
++use Util;
++
++prep_environment();
++
++# We need to do this tediously here rather than with Barfly because
++# Barfly relies on --underline working correctly.
++
++my $bill_ = reslash( 't/text/bill-of-rights.txt' );
++my $getty = reslash( 't/text/gettysburg.txt' );
++
++# Spacing that the filenames take up.
++my $spc_b = ' ' x length( $bill_ );
++my $spc_g = ' ' x length( $getty );
++
++subtest 'Grouped --underline' => sub {
++    plan tests => 1;
++
++    my @expected = line_split( <<"HERE" );
++$bill_
++4:or prohibiting the free exercise thereof; or abridging the freedom of
++                                                             ^^^^^^^
++
++$getty
++23:shall have a new birth of freedom -- and that government of the people,
++                             ^^^^^^^
++HERE
++
++    my @args = qw( --underline --sort-files --group freedom t/text );
++
++    ack_lists_match( [ @args ], \@expected, 'Looking for freedom, grouped' );
++};
++
++
++subtest 'Ungrouped --underline' => sub {
++    plan tests => 1;
++
++    my @expected = line_split( <<"HERE" );
++$bill_:4:or prohibiting the free exercise thereof; or abridging the freedom of
++$spc_b                                                              ^^^^^^^
++$getty:23:shall have a new birth of freedom -- and that government of the people,
++$spc_g                              ^^^^^^^
++HERE
++
++    my @args = qw( --underline --sort-files --nogroup freedom t/text );
++
++    ack_lists_match( [ @args ], \@expected, 'Looking for freedom, ungrouped' );
++};
++
++
++subtest 'Grouped --underline with context' => sub {
++    plan tests => 1;
++
++    my @expected = line_split( <<"HERE" );
++$bill_
++2-
++3-Congress shall make no law respecting an establishment of religion,
++4:or prohibiting the free exercise thereof; or abridging the freedom of
++                                                             ^^^^^^^
++5-speech, or of the press; or the right of the people peaceably to assemble,
++6-and to petition the Government for a redress of grievances.
++
++$getty
++21-the last full measure of devotion -- that we here highly resolve that
++22-these dead shall not have died in vain -- that this nation, under God,
++23:shall have a new birth of freedom -- and that government of the people,
++                             ^^^^^^^
++24-by the people, for the people, shall not perish from the earth.
++HERE
++
++    my @args = qw( --underline --sort-files --group -C free\w+ t/text );
++
++    ack_lists_match( [ @args ], \@expected, 'Looking for freedom, grouped with context' );
++};
++
++
++subtest 'Ungrouped --underline with --context' => sub {
++    plan tests => 1;
++
++    my @expected = line_split( <<"HERE" );
++$bill_-2-
++$bill_-3-Congress shall make no law respecting an establishment of religion,
++$bill_:4:or prohibiting the free exercise thereof; or abridging the freedom of
++$spc_b                                                              ^^^^^^^
++$bill_-5-speech, or of the press; or the right of the people peaceably to assemble,
++$bill_-6-and to petition the Government for a redress of grievances.
++--
++$getty-21-the last full measure of devotion -- that we here highly resolve that
++$getty-22-these dead shall not have died in vain -- that this nation, under God,
++$getty:23:shall have a new birth of freedom -- and that government of the people,
++$spc_g                              ^^^^^^^
++$getty-24-by the people, for the people, shall not perish from the earth.
++HERE
++
++    my @args = qw( --underline --sort-files --nogroup -C free\w+ t/text );
++
++    ack_lists_match( [ @args ], \@expected, 'Looking for freedom, ungrouped' );
++};
++
++exit 0;
diff --cc t/ack-v.t
index 6e62a11,6e62a11..7279059
--- a/t/ack-v.t
+++ b/t/ack-v.t
@@@ -3,7 -3,7 +3,7 @@@
  use warnings;
  use strict;
  
--use Test::More tests => 5;
++use Test::More tests => 4;
  
  use lib 't';
  use Util;
diff --cc t/ack-version.t
index 0000000,0000000..386c780
new file mode 100644
--- /dev/null
+++ b/t/ack-version.t
@@@ -1,0 -1,0 +1,22 @@@
++#!perl -T
++
++use strict;
++use warnings;
++
++use Test::More tests => 2;
++use lib 't';
++use Util;
++
++use App::Ack;
++
++prep_environment();
++
++my ( $stdout, $stderr ) = run_ack_with_stderr( '--version' );
++
++is_empty_array( $stderr, 'Nothing in stderr' );
++my @lines = @{$stdout};
++like( $lines[0], qr/\Q$App::Ack::VERSION/, 'Found the version in the first line' );
++
++done_testing();
++
++exit 0;
diff --cc t/ack-w.barfly
index 0000000,0000000..0c3e915
new file mode 100644
--- /dev/null
+++ b/t/ack-w.barfly
@@@ -1,0 -1,0 +1,200 @@@
++# BEGIN comment here
++#
++# RUN
++# ack command line(s)
++# They should NOT be shell-escaped.  Args are split on whitespace before
++# being passed in to ack.
++#
++# YESLINES
++# Lines that should match with underlines shown
++#                              ^^^^^^^^^^
++# YES
++# Lines that should match, but without the underlines.
++#
++# NO
++# Lines that should not match.
++#
++# END
++#
++# Blank lines are always ignored.
++
++
++
++BEGIN Straight -w
++
++RUN
++ack -w foo
++
++YES
++foo
++foo bar
++
++NO
++foobar
++foot
++underfoot
++
++YESLINES
++foo-foo football
++^^^ ^^^
++
++foo
++^^^
++
++End of the line foo
++                ^^^
++
++I pity da foo'.
++          ^^^
++
++END
++
++
++BEGIN optional character
++RUN
++ack foot?
++
++YES
++foo
++foot
++Trampled underfoot
++foobarf
++foo-bar
++foo-bart
++football
++
++YESLINES
++Our ten-foot foo-bird is foobar.
++        ^^^^ ^^^         ^^^
++
++END
++
++
++BEGIN -w and optional character
++RUN
++ack -w foot?
++ack -w (foot?)
++ack -w (?:foot?)
++
++NO
++Trampled underfoot
++football
++foolish
++
++YES
++foo
++foot
++foo-bar
++foot-bar
++
++YESLINES
++foo
++^^^
++
++By the foot
++       ^^^^
++
++I pity da foo'.
++          ^^^
++
++Our ten-foot foo-bird is foobar.
++        ^^^^ ^^^
++END
++
++
++BEGIN -w and optional group
++RUN
++ack -w foo(bar)?
++
++YES
++foo
++foobar
++foo-bar
++foo-bart
++
++NO
++Trampled underfoot
++foobarf
++
++YESLINES
++foobar
++^^^^^^
++
++x foobar x
++  ^^^^^^
++
++I pity da foo'.
++          ^^^
++
++Now everything's all foobar.
++                     ^^^^^^
++
++END
++
++
++BEGIN -w and alternation
++RUN
++ack -w foo|bar
++ack -w (foo|bar)
++
++YES
++foo
++bar
++
++NO
++schmfoo
++schmofool
++barfly
++fubar
++barometric
++subarometric
++
++YESLINES
++Little bunny foo-foo's ten-foot bar is foobar.
++             ^^^ ^^^            ^^^
++
++END
++
++
++BEGIN -w and a function definition
++RUN
++ack -w (set|get)_user_(name|perm)
++ack -w ((set|get)_user_(name|perm))
++ack -w (?:(?:set|get)_user_(?:name|perm))
++ack -w (?:(set|get)_user_(name|perm))
++ack -w ((?:set|get)_user_(?:name|perm))
++
++YES
++set_user_name
++get_user_perm
++
++NO
++reset_user_name
++get_user_permission
++
++YESLINES
++my $foo = set_user_name( $bar ) + set_user_perm( $foo );
++          ^^^^^^^^^^^^^           ^^^^^^^^^^^^^
++END
++
++
++BEGIN Single-letter words
++RUN
++ack -w \w
++
++YES
++A
++b
++c
++!E!
++
++NO
++dd
++xxx
++
++YESLINES
++A man, a plan, a canal: Panama
++^      ^       ^
++END
++
++# vi:set ft=barfly:
diff --cc t/ack-w.t
index 157eab3,157eab3..ddbc5d7
--- a/t/ack-w.t
+++ b/t/ack-w.t
@@@ -3,142 -3,142 +3,112 @@@
  use warnings;
  use strict;
  
--use Test::More tests => 16;
++use Test::More tests => 15;
  
  use lib 't';
  use Util;
++use Barfly;
  
  prep_environment();
  
--TRAILING_PUNC: {
--    my @expected = line_split( <<'HERE' );
--Respite-respite and nepenthe from thy memories of Lenore!"
--Clasp a rare and radiant maiden whom the angels name Lenore!"
--HERE
++Barfly->run_tests( 't/ack-w.barfly' );
  
--    my @files = qw( t/text );
--    my @args = qw( Lenore! -w -h --sort-files );
--
--    ack_lists_match( [ @args, @files ], \@expected, 'Looking for Lenore!' );
--}
++subtest '-w with trailing metachar \w' => sub {
++    plan tests => 1;
  
--TRAILING_METACHAR_BACKSLASH_W: {
      my @expected = line_split( <<'HERE' );
--be a Majority of the whole Number of Electors appointed; and if there be
--President. But if there should remain two or more who have equal Votes,
++A well regulated Militia, being necessary to the security of a free State,
++cases arising in the land or naval forces, or in the Militia, when in
  HERE
  
--    my @files = qw( t/text/constitution.txt );
--    my @args = qw( ther\w -w --sort-files );
++    my @files = qw( t/text/bill-of-rights.txt );
++    my @args = ( 'Milit\w\w', qw( -w -h --sort-files ) );
++
++    ack_lists_match( [ @args, @files ], \@expected, 'Looking for militia with metacharacters' );
++};
  
--    ack_lists_match( [ @args, @files ], \@expected, 'Looking for ther\\w, with -w, so no thereofs or thereins' );
--}
  
++subtest '-w with trailing dot' => sub {
++    plan tests => 1;
  
--TRAILING_METACHAR_DOT: {
--    # Because the . at the end of the regular expression is not a word
--    # character, a word boundary is not required after the match.
      my @expected = line_split( <<'HERE' );
--speech, or of the press; or the right of the people peaceably to assemble,
--the right of the people to keep and bear Arms, shall not be infringed.
--The right of the people to be secure in their persons, houses, papers,
--In all criminal prosecutions, the accused shall enjoy the right to a
--twenty dollars, the right of trial by jury shall be preserved, and no
--The enumeration in the Constitution, of certain rights, shall not be
--limited Times to Authors and Inventors the exclusive Right to their
++A well regulated Militia, being necessary to the security of a free State,
++cases arising in the land or naval forces, or in the Militia, when in
  HERE
  
--    my @files = qw( t/text );
--    my @args = ( 'right.', qw( -i -w -h --sort-files ) );
++    my @files = qw( t/text/bill-of-rights.txt );
++    my @args = ( 'Milit..', qw( -w -h --sort-files ) );
  
--    ack_lists_match( [ @args, @files ], \@expected, 'Looking for right.' );
--}
++    ack_lists_match( [ @args, @files ], \@expected, 'Looking for Milit..' );
++};
++
++
++subtest 'Begins and ends with word char' => sub {
++    plan tests => 1;
  
--BEGINS_AND_ENDS_WITH_WORD_CHAR: {
      # Normal case of whole word match.
      my @expected = line_split( <<'HERE' );
--Each House shall be the Judge of the Elections, Returns and Qualifications
--shall judge necessary and expedient; he may, on extraordinary Occasions,
++A well regulated Militia, being necessary to the security of a free State,
++cases arising in the land or naval forces, or in the Militia, when in
  HERE
  
--    my @files = qw( t/text );
--    my @args = ( 'judge', qw( -w -h -i --sort-files ) );
++    my @files = qw( t/text/bill-of-rights.txt );
++    my @args = qw( Militia -w -h --sort-files );
  
--    ack_lists_match( [ @args, @files ], \@expected, 'Looking for two "judge" as whole word, not five "judge/judges"' );
--}
++    ack_lists_match( [ @args, @files ], \@expected, 'Looking for Militia as whole word' );
++};
++
++
++subtest 'Ends with grouping parens' => sub {
++    plan tests => 1;
  
--BEGINS_BUT_NOT_ENDS_WITH_WORD_CHAR: {
      # The last character of the regexp is not a word, disabling the word boundary check at the end of the match.
      my @expected = line_split( <<'HERE' );
--All legislative Powers herein granted shall be vested in a Congress
--and shall have the sole Power of Impeachment.
--The Senate shall have the sole Power to try all Impeachments. When
--The Congress shall have Power To lay and collect Taxes, Duties, Imposts
--Execution the foregoing Powers, and all other Powers vested by this
--or Compact with another State, or with a foreign Power, or engage in War,
--The executive Power shall be vested in a President of the United States
--Resignation, or Inability to discharge the Powers and Duties of the said
--and he shall have Power to Grant Reprieves and Pardons for Offences
--He shall have Power, by and with the Advice and Consent of the Senate,
--The President shall have Power to fill up all Vacancies that may happen
--The judicial Power of the United States, shall be vested in one supreme
--The judicial Power shall extend to all Cases, in Law and Equity,
--The Congress shall have Power to declare the Punishment of Treason, but
--The Congress shall have Power to dispose of and make all needful Rules and
++A well regulated Militia, being necessary to the security of a free State,
++cases arising in the land or naval forces, or in the Militia, when in
  HERE
  
--    my @files = qw( t/text/constitution.txt );
--    my @args = ( 'pow()', qw( -w -h -i ) );
++    my @files = qw( t/text/bill-of-rights.txt );
++    my @args = ( 'Militia()', qw( -w -h --sort-files ) );
  
--    ack_lists_match( [ @args, @files ], \@expected, 'Looking for "pow()" with word flag, but regexp does not end with word char' );
--}
++    ack_lists_match( [ @args, @files ], \@expected, 'Looking for Militia with word flag, but regexp does not end with word char' );
++};
++
++
++subtest 'Begins with grouping parens' => sub {
++    plan tests => 1;
  
--ENDS_BUT_NOT_BEGINS_WITH_WORD_CHAR: {
--    # The first character of the regexp is not a word, disabling the word boundary check at the start of the match.
      my @expected = line_split( <<'HERE' );
--each State shall have the Qualifications requisite for Electors of the
--Providence Plantations one, Connecticut five, New-York six, New Jersey
--The Times, Places and Manner of holding Elections for Senators and
--Regulations, except as to the Places of chusing Senators.
--Each House shall be the Judge of the Elections, Returns and Qualifications
--return it, with his Objections to that House in which it shall have
--originated, who shall enter the Objections at large on their Journal,
--with the Objections, to the other House, by which it shall likewise be
--and House of Representatives, according to the Rules and Limitations
--To regulate Commerce with foreign Nations, and among the several States,
--and Offences against the Law of Nations;
--suppress Insurrections and repel Invasions;
--Appropriations made by Law; and a regular Statement and Account of the
--Fact, with such Exceptions, and under such Regulations as the Congress
--Regulations respecting the Territory or other Property belonging to the
--by Conventions in three fourths thereof, as the one or the other Mode of
--The Ratification of the Conventions of nine States, shall be sufficient
++A well regulated Militia, being necessary to the security of a free State,
++cases arising in the land or naval forces, or in the Militia, when in
  HERE
  
--    my @files = qw( t/text/constitution.txt );
--    my @args = ( '()tions', qw( -w -h --sort-files ) );
++    my @files = qw( t/text/bill-of-rights.txt );
++    my @args = ( '()Militia', qw( -w -h --sort-files ) );
  
--    ack_lists_match( [ @args, @files ], \@expected, 'Looking for "()tions" with word flag, but regexp does not begin with word char' );
--}
++    ack_lists_match( [ @args, @files ], \@expected, 'Looking for Militia with word flag, but regexp does not begin with word char' );
++};
++
++
++subtest 'Wrapped in grouping parens' => sub {
++    plan tests => 1;
  
--NEITHER_BEGINS_NOR_ENDS_WITH_WORD_CHAR: {
--    # Because the regular expression doesn't begin or end with a word character, the 'words mode' doesn't affect the match.
      my @expected = line_split( <<'HERE' );
--Each House shall be the Judge of the Elections, Returns and Qualifications
--Session of their respective Houses, and in going to and returning from
--return it, with his Objections to that House in which it shall have
--any Bill shall not be returned by the President within ten Days (Sundays
--their Adjournment prevent its Return, in which Case it shall not be a Law.
++A well regulated Militia, being necessary to the security of a free State,
++cases arising in the land or naval forces, or in the Militia, when in
  HERE
  
--    my @files = qw( t/text/constitution.txt );
--    my @args = ( '(return)', qw( -w -i -h ) );
++    my @files = qw( t/text/bill-of-rights.txt );
++    my @args = ( '(Militia)', qw( -w -h --sort-files ) );
  
--    ack_lists_match( [ @args, @files ], \@expected, 'Looking for "return" with word flag, but regexp does not begin or end with word char' );
--}
++    ack_lists_match( [ @args, @files ], \@expected, 'Looking for Militia with word flag, but regexp does not begin or end with word char' );
++};
++
++
++# Test for issue ack2#443
++subtest 'Alternating numbers' => sub {
++    plan tests => 1;
  
--# Test for issue #443
--ALTERNATING_NUMBERS: {
      my @expected = ();
  
      my @files = qw( t/text/number.txt );
@@@ -146,6 -146,6 +116,126 @@@
      my @args = ( '650|660|670|680', '-w' );
  
      ack_lists_match( [ @args, @files ], \@expected, 'Alternations should also respect boundaries when using -w' );
++};
++
++
++# In ack3, we try to warn people if they are misusing -w.
++subtest '-w warnings' => sub {
++    my ($good,$bad) = _get_good_and_bad();
++
++    plan tests => @{$good} + @{$bad};
++
++    my $happy = reslash( 't/text/ozymandias.txt' );
++    for my $pattern ( @{$good} ) {
++        subtest "Good example: $pattern" => sub {
++            plan tests => 1;
++
++            my ( $stdout, $stderr ) = run_ack_with_stderr( $pattern, '-w', $happy );
++            # Don't care what stdout is.
++            is_empty_array( $stderr, 'Should not trigger any warnings' );
++        }
++    }
++
++    for my $pattern ( @{$bad} ) {
++        subtest "Bad example: $pattern" => sub {
++            plan tests => 3;
++
++            # Add the -- because the pattern may have hyphens.
++            my ( $stdout, $stderr ) = run_ack_with_stderr( '-w', '--', $pattern, $happy );
++            is_empty_array( $stdout, 'Should have no output' );
++            is( scalar @{$stderr}, 1, 'One warning' );
++            like( $stderr->[0], qr/ack(-standalone)?: -w will not do the right thing/, 'Got the correct warning' );
++        };
++    }
++};
++
++
++sub _get_good_and_bad {
++    # BAD = should throw a warning with -w
++    # OK  = should not throw a warning with -w
++    my @examples = line_split( <<'HERE' );
++# Anchors
++BAD $foo
++BAD foo^
++BAD ^foo
++BAD foo$
++
++# Dot
++OK  foo.
++OK  .foo
++
++# Parentheses
++OK  (set|get)_foo
++OK  foo_(id|name)
++OK  func()
++OK  (all in one group)
++BAD )start with closing paren
++BAD end with opening paren(
++BAD end with an escaped closing paren\)
++
++# Character classes
++OK  [sg]et
++OK  foo[lt]
++OK  [one big character class]
++OK  [multiple][character][classes]
++BAD ]starting with a closing bracket
++BAD ending with a opening bracket[
++BAD ending with an escaped closing bracket \]
++
++# Quantifiers
++OK  thpppt{1,5}
++BAD }starting with an closing curly brace
++BAD ending with an opening curly brace{
++BAD ending with an escaped closing curly brace\}
++
++OK  foo+
++BAD foo\+
++BAD +foo
++OK  foo*
++BAD foo\*
++BAD *foo
++OK  foo?
++BAD foo\?
++BAD ?foo
++
++# Miscellaneous debris
++BAD -foo
++BAD foo-
++BAD &mpersand
++BAD ampersand&
++BAD function(
++BAD ->method
++BAD <header.h>
++BAD =14
++BAD /slashes/
++BAD ::Class::Whatever
++BAD Class::Whatever::
++OK  Class::Whatever
++
++HERE
++
++    my $good = [];
++    my $bad  = [];
++
++    for my $line ( @examples ) {
++        $line =~ s/\s*$//;
++        if ( $line eq '' || $line =~ /^#/ ) {
++            next;
++        }
++        elsif ( $line =~ /^OK\s+(.+)/ ) {
++            push( @{$good}, $1 );
++        }
++        elsif ( $line =~ /BAD\s+(.+)/ ) {
++            push( @{$bad}, $1 );
++        }
++        else {
++            die "Invalid line: $line";
++        }
++    }
++
++    return ($good,$bad);
  }
  
  done_testing();
++
++exit 0;
diff --cc t/ack-x.t
index 947c951,947c951..c2f2058
--- a/t/ack-x.t
+++ b/t/ack-x.t
@@@ -163,3 -163,3 +163,6 @@@ else 
  
  sets_match( $stdout, \@expected, __FILE__ );
  is_empty_array( $stderr );
++
++done_testing();
++exit 0;
diff --cc t/anchored.t
index ce244ed,ce244ed..c0cb8cb
--- a/t/anchored.t
+++ b/t/anchored.t
@@@ -5,7 -5,7 +5,8 @@@
  use strict;
  use warnings;
  
--use Test::More tests => 5;
++use Test::More tests => 3;
++
  use lib 't';
  use Util;
  
@@@ -74,3 -74,3 +75,4 @@@ HER
  }
  
  done_testing();
++exit 0;
diff --cc t/bad-ackrc-opt.t
index 8588bd7,8588bd7..1a53652
--- a/t/bad-ackrc-opt.t
+++ b/t/bad-ackrc-opt.t
@@@ -15,3 -15,3 +15,7 @@@ is_empty_array( $stdout, 'Nothing to st
  is( @{$stderr}, 1, 'only one line to stderr' );
  like( $stderr->[0], qr/Unable to load ackrc/, 'Got the right message' );
  isnt( get_rc(), 0, 'Non-zero return code' );
++
++done_testing();
++
++exit 0;
diff --cc t/config-backwards-compat.t
index 0e44593,0e44593..10e7c6c
--- a/t/config-backwards-compat.t
+++ b/t/config-backwards-compat.t
@@@ -5,12 -5,12 +5,11 @@@ use warnings
  
  use lib 't';
  use Util;
--use File::Temp;
  use Test::More tests => 3;
  
  prep_environment();
  
--my $old_config = <<'HERE';
++my $temp_config = create_tempfile( <<'HERE' );
  # Always sort
  --sort-files
  
@@@ -37,10 -37,10 +36,6 @@@
  --ignore-dir=nytprof
  HERE
  
--my $temp_config = File::Temp->new;
--print { $temp_config } $old_config;
--close $temp_config;
--
  my @args = ( '--ackrc=' . $temp_config->filename, '--md', 'One', 't/swamp/' );
  
  my $file = reslash('t/swamp/notes.md');
diff --cc t/config-finder.t
index c387c8d,c387c8d..468cacd
--- a/t/config-finder.t
+++ b/t/config-finder.t
@@@ -28,91 -28,91 +28,16 @@@ if ( $tmpdir && ($tmpdir =~ /^\Q$home/
  
  plan tests => 26;
  
--# Set HOME to a known value, so we get predictable results:
--$ENV{'HOME'} = realpath('t/home');
++# Set HOME to a known value, so we get predictable results.
++local $ENV{HOME} = realpath('t/home');
  
--# Clear the users ACKRC so it doesn't throw out expect_ackrcs().
++# Clear the user's ACKRC so it doesn't throw out expect_ackrcs().
  delete $ENV{'ACKRC'};
  
--{
--# The tests blow up on Windows if the global files don't exist,
--# so here we create them if they don't, keeping track of the ones
--# we make so we can delete them later.
--my @created_globals;
--
--sub set_up_globals {
--    my (@files) = @_;
--
--    foreach my $path (@files) {
--        my $filename = $path->{path};
--        if ( not -e $filename ) {
--            touch_ackrc( $filename );
--            push @created_globals, $path;
--        }
--    }
--
--    return;
--}
--
--sub clean_up_globals {
--    foreach my $path (@created_globals) {
--        my $filename = $path->{path};
--        unlink $filename or warn "Couldn't unlink $path";
--    }
--
--    return;
--}
--
--}
--sub no_home (&) { ## no critic (ProhibitSubroutinePrototypes)
--    my ( $fn ) = @_;
--
--    my $home = delete $ENV{'HOME'}; # Localized delete isn't supported in earlier Perls.
--    $fn->();
--    $ENV{'HOME'} = $home; # XXX this won't work on exceptions...
--
--    return;
--}
--
  my $finder;
++my @global_filenames = create_globals();
  
--sub expect_ackrcs {
--    local $Test::Builder::Level = $Test::Builder::Level + 1;
--
--    my $expected = shift;
--    my $name     = shift;
--
--    my @got      = $finder->find_config_files;
--    my @expected = @{$expected};
--
--    foreach my $element (@got, @expected) {
--        $element->{'path'} = realpath($element->{'path'});
--    }
--    is_deeply( \@got, \@expected, $name ) or diag(explain(\@got));
--
--    return;
--}
--
--my @global_files;
--
--if ( is_windows() ) {
--    require Win32;
--
--    @global_files = map { +{ path => File::Spec->catfile($_, 'ackrc') } } (
--        Win32::GetFolderPath(Win32::CSIDL_COMMON_APPDATA()),
--        Win32::GetFolderPath(Win32::CSIDL_APPDATA()),
--    );
--}
--else {
--    @global_files = (
--        { path => '/etc/ackrc' },
--    );
--}
--
--if ( is_windows() || is_cygwin() ) {
--    set_up_globals( @global_files );
--}
--
++my @global_files = map { +{ path => $_ } } @global_filenames;
  my @std_files = (@global_files, { path => File::Spec->catfile($ENV{'HOME'}, '.ackrc') });
  
  my $wd      = getcwd_clean();
@@@ -120,103 -120,103 +45,120 @@@ my $tempdir = File::Temp->newdir
  safe_chdir( $tempdir->dirname );
  
  $finder = App::Ack::ConfigFinder->new;
--expect_ackrcs \@std_files, 'having no project file should return only the top level files';
++with_home( sub {
++    expect_ackrcs( \@std_files, 'having no project file should return only the top level files' );
++} );
  
--no_home {
--    expect_ackrcs \@global_files, 'only system-wide ackrc is returned if HOME is not defined with no project files';
--};
++no_home( sub {
++    expect_ackrcs( \@global_files, 'only system-wide ackrc is returned if HOME is not defined with no project files' );
++} );
  
  safe_mkdir( 'foo' );
  safe_mkdir( File::Spec->catdir('foo', 'bar') );
  safe_mkdir( File::Spec->catdir('foo', 'bar', 'baz') );
--
  safe_chdir( File::Spec->catdir('foo', 'bar', 'baz') );
  
  touch_ackrc( '.ackrc' );
--expect_ackrcs [ @std_files, { project => 1, path => File::Spec->rel2abs('.ackrc') }], 'a project file in the same directory should be detected';
--no_home {
--    expect_ackrcs [ @global_files, { project => 1, path => File::Spec->rel2abs('.ackrc') } ], 'a project file in the same directory should be detected';
--};
++with_home( sub {
++    expect_ackrcs( [ @std_files, { project => 1, path => File::Spec->rel2abs('.ackrc') }], 'a project file in the same directory should be detected' );
++} );
++no_home( sub {
++    expect_ackrcs( [ @global_files, { project => 1, path => File::Spec->rel2abs('.ackrc') } ], 'a project file in the same directory should be detected' );
++} );
  
  unlink '.ackrc';
  
  my $project_file = File::Spec->catfile($tempdir->dirname, 'foo', 'bar', '.ackrc');
  touch_ackrc( $project_file );
--expect_ackrcs [ @std_files, { project => 1, path => $project_file } ], 'a project file in the parent directory should be detected';
--no_home {
--    expect_ackrcs [ @global_files, { project => 1, path => $project_file } ], 'a project file in the parent directory should be detected';
--};
++with_home( sub {
++    expect_ackrcs( [ @std_files, { project => 1, path => $project_file } ], 'a project file in the parent directory should be detected' );
++} );
++no_home( sub {
++    expect_ackrcs( [ @global_files, { project => 1, path => $project_file } ], 'a project file in the parent directory should be detected' );
++} );
  unlink $project_file;
  
  $project_file = File::Spec->catfile($tempdir->dirname, 'foo', '.ackrc');
  touch_ackrc( $project_file );
--expect_ackrcs [ @std_files, { project => 1, path => $project_file } ], 'a project file in the grandparent directory should be detected';
--no_home {
--    expect_ackrcs [ @global_files, { project => 1, path => $project_file } ], 'a project file in the grandparent directory should be detected';
--};
++with_home( sub {
++    expect_ackrcs( [ @std_files, { project => 1, path => $project_file } ], 'a project file in the grandparent directory should be detected' );
++} );
++no_home( sub {
++    expect_ackrcs( [ @global_files, { project => 1, path => $project_file } ], 'a project file in the grandparent directory should be detected' );
++} );
  
  touch_ackrc( '.ackrc' );
  
--expect_ackrcs [ @std_files, { project => 1, path => File::Spec->rel2abs('.ackrc') } ], 'a project file in the same directory should be detected, even with another one above it';
--no_home {
--    expect_ackrcs [ @global_files, { project => 1, path => File::Spec->rel2abs('.ackrc') } ], 'a project file in the same directory should be detected, even with another one above it';
--};
++with_home( sub {
++    expect_ackrcs( [ @std_files, { project => 1, path => File::Spec->rel2abs('.ackrc') } ], 'a project file in the same directory should be detected, even with another one above it' );
++} );
++no_home( sub {
++    expect_ackrcs( [ @global_files, { project => 1, path => File::Spec->rel2abs('.ackrc') } ], 'a project file in the same directory should be detected, even with another one above it' );
++} );
  
  unlink '.ackrc';
  unlink $project_file;
  
  touch_ackrc( '_ackrc' );
--expect_ackrcs [ @std_files, { project => 1, path => File::Spec->rel2abs('_ackrc') } ], 'a project file in the same directory should be detected';
--no_home {
--    expect_ackrcs [ @global_files, { project => 1, path => File::Spec->rel2abs('_ackrc') } ], 'a project file in the same directory should be detected';
--};
++with_home( sub {
++    expect_ackrcs( [ @std_files, { project => 1, path => File::Spec->rel2abs('_ackrc') } ], 'a project file in the same directory should be detected' );
++} );
++no_home( sub {
++    expect_ackrcs( [ @global_files, { project => 1, path => File::Spec->rel2abs('_ackrc') } ], 'a project file in the same directory should be detected' );
++} );
  
  unlink '_ackrc';
  
  $project_file = File::Spec->catfile($tempdir->dirname, 'foo', '_ackrc');
  touch_ackrc( $project_file );
--expect_ackrcs [ @std_files, { project => 1, path => $project_file } ], 'a project file in the grandparent directory should be detected';
--no_home {
--    expect_ackrcs [ @global_files, { project => 1, path => $project_file } ], 'a project file in the grandparent directory should be detected';
--};
++with_home( sub {
++    expect_ackrcs( [ @std_files, { project => 1, path => $project_file } ], 'a project file in the grandparent directory should be detected' );
++} );
++no_home( sub {
++    expect_ackrcs( [ @global_files, { project => 1, path => $project_file } ], 'a project file in the grandparent directory should be detected' );
++} );
  
  touch_ackrc( '_ackrc' );
--expect_ackrcs [ @std_files, { project => 1, path => File::Spec->rel2abs('_ackrc') } ], 'a project file in the same directory should be detected, even with another one above it';
--no_home {
--    expect_ackrcs [ @global_files, { project => 1, path => File::Spec->rel2abs('_ackrc') } ], 'a project file in the same directory should be detected, even with another one above it';
--};
++with_home( sub { expect_ackrcs( [ @std_files, { project => 1, path => File::Spec->rel2abs('_ackrc') } ], 'a project file in the same directory should be detected, even with another one above it' );
++} );
++no_home( sub {
++    expect_ackrcs( [ @global_files, { project => 1, path => File::Spec->rel2abs('_ackrc') } ], 'a project file in the same directory should be detected, even with another one above it' );
++} );
  
  unlink $project_file;
  touch_ackrc( '.ackrc' );
--my $ok = eval { $finder->find_config_files };
--my $err = $@;
--ok( !$ok, '.ackrc + _ackrc is error' );
--like( $err, qr/contains both \.ackrc and _ackrc/, 'Got the expected error' );
--
--no_home {
--    $ok = eval { $finder->find_config_files };
--    $err = $@;
--    ok( !$ok, '.ackrc + _ackrc is error' );
--    like( $err, qr/contains both \.ackrc and _ackrc/, 'Got the expected error' );
--};
  
--unlink '.ackrc';
--$project_file = File::Spec->catfile($tempdir->dirname, 'foo', '.ackrc');
--touch_ackrc( $project_file );
--expect_ackrcs [ @std_files, { project => 1, path => File::Spec->rel2abs('_ackrc') }], 'a lower-level _ackrc should be preferred to a higher-level .ackrc';
--no_home {
--    expect_ackrcs [ @global_files, { project => 1, path => File::Spec->rel2abs('_ackrc') } ], 'a lower-level _ackrc should be preferred to a higher-level .ackrc';
++do {
++    my $finder_fn = sub {
++        my $ok = eval { $finder->find_config_files };
++        my $err = $@;
++        ok( !$ok, '.ackrc + _ackrc is error' );
++        like( $err, qr/contains both \.ackrc and _ackrc/, 'Got the expected error' );
++    };
++    with_home( $finder_fn );
++    no_home( $finder_fn );
++
++    unlink '.ackrc';
++    $project_file = File::Spec->catfile($tempdir->dirname, 'foo', '.ackrc');
++    touch_ackrc( $project_file );
++    with_home( sub {
++        expect_ackrcs( [ @std_files, { project => 1, path => File::Spec->rel2abs('_ackrc') }], 'a lower-level _ackrc should be preferred to a higher-level .ackrc' );
++    } );
++    no_home( sub {
++        expect_ackrcs( [ @global_files, { project => 1, path => File::Spec->rel2abs('_ackrc') } ], 'a lower-level _ackrc should be preferred to a higher-level .ackrc' );
++    } );
++
++    unlink '_ackrc';
  };
  
--unlink '_ackrc';
--
  do {
--    local $ENV{'HOME'} = File::Spec->catdir($tempdir->dirname, 'foo');
++    my $test_home = File::Spec->catdir( $tempdir->dirname, 'foo' );
++    local $ENV{'HOME'} = $test_home;
  
--    my $user_file = File::Spec->catfile($tempdir->dirname, 'foo', '.ackrc');
++    my $user_file = File::Spec->catfile( $test_home, '.ackrc');
      touch_ackrc( $user_file );
  
--    expect_ackrcs [ @global_files, { path => $user_file } ], q{don't load the same ackrc file twice};
++    expect_ackrcs( [ @global_files, { path => $user_file } ], q{Don't load the same ackrc file twice} );
      unlink($user_file);
  };
  
@@@ -227,20 -227,20 +169,60 @@@ do 
      my $user_file = File::Spec->catfile($ENV{'HOME'}, '.ackrc');
      touch_ackrc( $user_file );
  
--    my $ackrc = File::Temp->new;
--    close $ackrc;
++    my $ackrc = create_tempfile();
      local $ENV{'ACKRC'} = $ackrc->filename;
  
--    expect_ackrcs [ @global_files, { path => $ackrc->filename } ], q{ACKRC overrides user's HOME ackrc};
++    expect_ackrcs( [ @global_files, { path => $ackrc->filename } ], q{ACKRC overrides user's HOME ackrc} );
      unlink $ackrc->filename;
  
--    expect_ackrcs [ @global_files, { path => $user_file } ], q{ACKRC doesn't override if it doesn't exist};
++    expect_ackrcs( [ @global_files, { path => $user_file } ], q{ACKRC doesn't override if it doesn't exist} );
  
      touch_ackrc( $ackrc->filename );
      safe_chdir( 'foo' );
--    expect_ackrcs [ @global_files, { path => $ackrc->filename}, { project => 1, path => $user_file } ], q{~/.ackrc should still be found as a project ackrc};
++    expect_ackrcs( [ @global_files, { path => $ackrc->filename}, { project => 1, path => $user_file } ], q{~/.ackrc should still be found as a project ackrc} );
      unlink $ackrc->filename;
  };
  
  safe_chdir( $wd );
  clean_up_globals();
++
++exit 0;
++
++
++sub with_home {
++    my ( $fn ) = @_;
++
++    $fn->();
++
++    return;
++}
++
++
++sub no_home {
++    my ( $fn ) = @_;
++
++    # We have to manually store the value of HOME because localized
++    # delete isn't supported until Perl 5.12.0.
++    my $home_saved = delete $ENV{HOME};
++    $fn->();
++    $ENV{HOME} = $home_saved;
++
++    return;
++}
++
++sub expect_ackrcs {
++    local $Test::Builder::Level = $Test::Builder::Level + 1;
++
++    my $expected = shift;
++    my $name     = shift;
++
++    my @got      = $finder->find_config_files;
++    my @expected = @{$expected};
++
++    foreach my $element (@got, @expected) {
++        $element->{'path'} = realpath($element->{'path'});
++    }
++    is_deeply( \@got, \@expected, $name ) or diag(explain(got=>\@got,expected=>\@expected));
++
++    return;
++}
diff --cc t/config-loader.t
index c461532,c461532..8bae6c5
--- a/t/config-loader.t
+++ b/t/config-loader.t
@@@ -5,10 -5,10 +5,7 @@@ use warnings
  use lib 't';
  use Util;
  
--use Test::More tests => 37;
--
--use Carp qw(croak);
--use File::Temp;
++use Test::More tests => 28;
  
  use App::Ack::Filter::Default;
  use App::Ack::ConfigLoader;
@@@ -16,41 -16,41 +13,39 @@@
  delete @ENV{qw( PAGER ACK_PAGER ACK_PAGER_COLOR ACK_OPTIONS )};
  
  my %defaults = (
--    'break'                   => undef,
--    color                     => undef,
--    column                    => undef,
--    count                     => undef,
--    dont_report_bad_filenames => undef,
--    f                         => undef,
--    files_from                => undef,
--    filters                   => [ App::Ack::Filter::Default->new ],
--    flush                     => undef,
--    follow                    => undef,
--    g                         => undef,
--    h                         => undef,
--    H                         => undef,
--    heading                   => undef,
--    i                         => undef,
--    l                         => undef,
--    L                         => undef,
--    m                         => undef,
--    n                         => undef,
--    output                    => undef,
--    pager                     => undef,
--    passthru                  => undef,
--    print0                    => undef,
--    Q                         => undef,
--    regex                     => undef,
--    show_types                => undef,
--    smart_case                => undef,
--    sort_files                => undef,
--    v                         => undef,
--    w                         => undef,
++    'break'    => undef,
++    color      => undef,
++    column     => undef,
++    count      => undef,
++    f          => undef,
++    files_from => undef,
++    filters    => [ App::Ack::Filter::Default->new ],
++    follow     => undef,
++    g          => undef,
++    h          => undef,
++    H          => undef,
++    heading    => undef,
++    l          => undef,
++    L          => undef,
++    m          => undef,
++    n          => undef,
++    output     => undef,
++    pager      => undef,
++    passthru   => undef,
++    print0     => undef,
++    proximate  => undef,
++    Q          => undef,
++    regex      => undef,
++    s          => undef,
++    show_types => undef,
++    sort_files => undef,
++    u          => undef,
++    v          => undef,
++    w          => undef,
  );
  
  test_loader(
      expected_opts    => { %defaults },
--    expected_targets => [],
      'empty inputs should result in default outputs'
  );
  
@@@ -62,28 -62,28 +57,24 @@@ for my $option ( qw( after_context befo
      test_loader(
          argv             => [ "--$long_arg=15" ],
          expected_opts    => { %defaults, $option => 15 },
--        expected_targets => [],
          "--$long_arg=15 should set $option to 15",
      );
  
      test_loader(
          argv             => [ "--$long_arg=0" ],
          expected_opts    => { %defaults, $option => 0 },
--        expected_targets => [],
          "--$long_arg=0 should set $option to 0",
      );
  
      test_loader(
          argv             => [ "--$long_arg" ],
          expected_opts    => { %defaults, $option => 2 },
--        expected_targets => [],
          "--$long_arg without a value should default $option to 2",
      );
  
      test_loader(
          argv             => [ "--$long_arg=-43" ],
          expected_opts    => { %defaults, $option => 2 },
--        expected_targets => [],
          "--$long_arg with a negative value should default $option to 2",
      );
  
@@@ -91,28 -91,28 +82,24 @@@
      test_loader(
          argv             => [ $short_arg, 15 ],
          expected_opts    => { %defaults, $option => 15 },
--        expected_targets => [],
          "$short_arg 15 should set $option to 15",
      );
  
      test_loader(
          argv             => [ $short_arg, 0 ],
          expected_opts    => { %defaults, $option => 0 },
--        expected_targets => [],
          "$short_arg 0 should set $option to 0",
      );
  
      test_loader(
          argv             => [ $short_arg ],
          expected_opts    => { %defaults, $option => 2 },
--        expected_targets => [],
          "$short_arg without a value should default $option to 2",
      );
  
      test_loader(
          argv             => [ $short_arg, '-43' ],
          expected_opts    => { %defaults, $option => 2 },
--        expected_targets => [],
          "$short_arg with a negative value should default $option to 2",
      );
  }
@@@ -120,107 -120,107 +107,97 @@@
  test_loader(
      argv             => ['-C', 5],
      expected_opts    => { %defaults, after_context => 5, before_context => 5 },
--    expected_targets => [],
      '-C sets both before_context and after_context'
  );
  
  test_loader(
      argv             => ['-C'],
      expected_opts    => { %defaults, after_context => 2, before_context => 2 },
--    expected_targets => [],
      '-C sets both before_context and after_context, with default'
  );
  
  test_loader(
      argv             => ['-C', 0],
      expected_opts    => { %defaults, after_context => 0, before_context => 0 },
--    expected_targets => [],
      '-C sets both before_context and after_context, with zero overriding default'
  );
  
  test_loader(
      argv             => ['-C', -43],
      expected_opts    => { %defaults, after_context => 2, before_context => 2 },
--    expected_targets => [],
      '-C with invalid value sets both before_context and after_context to default'
  );
  
  test_loader(
      argv             => ['--context=5'],
      expected_opts    => { %defaults, after_context => 5, before_context => 5 },
--    expected_targets => [],
      '--context sets both before_context and after_context'
  );
  
  test_loader(
      argv             => ['--context'],
      expected_opts    => { %defaults, after_context => 2, before_context => 2 },
--    expected_targets => [],
      '--context sets both before_context and after_context, with default'
  );
  
  test_loader(
      argv             => ['--context=0'],
      expected_opts    => { %defaults, after_context => 0, before_context => 0 },
--    expected_targets => [],
      '--context sets both before_context and after_context, with zero overriding default'
  );
  
  test_loader(
      argv             => ['--context=-43'],
      expected_opts    => { %defaults, after_context => 2, before_context => 2 },
--    expected_targets => [],
      '--context with invalid value sets both before_context and after_context to default'
  );
  
--# XXX These tests should all be replicated to work off of the ack command line
--#     tools instead of its internal APIs!
--do {
++
++subtest 'ACK_PAGER' => sub {
++    plan tests => 3;
++
      local $ENV{'ACK_PAGER'} = './test-pager --skip=2';
  
      test_loader(
          argv             => [],
          expected_opts    => { %defaults, pager => './test-pager --skip=2' },
--        expected_targets => [],
          'ACK_PAGER should set the default pager',
      );
  
      test_loader(
          argv             => ['--pager=./test-pager'],
          expected_opts    => { %defaults, pager => './test-pager' },
--        expected_targets => [],
          '--pager should override ACK_PAGER',
      );
  
      test_loader(
          argv             => ['--nopager'],
          expected_opts    => { %defaults },
--        expected_targets => [],
          '--nopager should suppress ACK_PAGER',
      );
  };
  
--do {
++
++subtest 'ACK_PAGER_COLOR' => sub {
++    plan tests => 6;
++
      local $ENV{'ACK_PAGER_COLOR'} = './test-pager --skip=2';
  
      test_loader(
          argv             => [],
          expected_opts    => { %defaults, pager => './test-pager --skip=2' },
--        expected_targets => [],
          'ACK_PAGER_COLOR should set the default pager',
      );
  
      test_loader(
          argv             => ['--pager=./test-pager'],
          expected_opts    => { %defaults, pager => './test-pager' },
--        expected_targets => [],
          '--pager should override ACK_PAGER_COLOR',
      );
  
      test_loader(
          argv             => ['--nopager'],
          expected_opts    => { %defaults },
--        expected_targets => [],
          '--nopager should suppress ACK_PAGER_COLOR',
      );
  
@@@ -229,53 -229,53 +206,50 @@@
      test_loader(
          argv             => [],
          expected_opts    => { %defaults, pager => './test-pager --skip=2' },
--        expected_targets => [],
          'ACK_PAGER_COLOR should override ACK_PAGER',
      );
  
      test_loader(
          argv             => ['--pager=./test-pager'],
          expected_opts    => { %defaults, pager => './test-pager' },
--        expected_targets => [],
          '--pager should override ACK_PAGER_COLOR and ACK_PAGER',
      );
  
      test_loader(
          argv             => ['--nopager'],
          expected_opts    => { %defaults },
--        expected_targets => [],
          '--nopager should suppress ACK_PAGER_COLOR and ACK_PAGER',
      );
  };
  
--do {
++
++subtest 'PAGER' => sub {
++    plan tests => 3;
++
      local $ENV{'PAGER'} = './test-pager';
  
      test_loader(
          argv             => [],
          expected_opts    => { %defaults },
--        expected_targets => [],
          q{PAGER doesn't affect ack by default},
      );
  
      test_loader(
          argv             => ['--pager'],
          expected_opts    => { %defaults, pager => './test-pager' },
--        expected_targets => [],
          'PAGER is used if --pager is specified with no argument',
      );
  
      test_loader(
          argv             => ['--pager=./test-pager --skip=2'],
          expected_opts    => { %defaults, pager => './test-pager --skip=2' },
--        expected_targets => [],
          'PAGER is not used if --pager is specified with an argument',
      );
--
--    # XXX what if --pager is specified but PAGER isn't set?
  };
  
--done_testing;
++done_testing();
++
++exit 0;
  
  
  sub test_loader {
@@@ -286,38 -286,38 +260,23 @@@
      my $msg  = pop;
      my %opts = @_;
  
--    return subtest "test_loader( $msg )" => sub {
--        plan tests => 2;
--
--        my ( $env, $argv, $expected_opts, $expected_targets ) =
--            delete @opts{qw/env argv expected_opts expected_targets/};
--
--        $env  = '' unless defined $env;
--        $argv = [] unless defined $argv;
--
--        my @files = map {
--            $opts{$_}
--        } sort {
--            my ( $a_end ) = $a =~ /(\d+)/;
--            my ( $b_end ) = $b =~ /(\d+)/;
++    return subtest subtest_name( $msg, \%opts ) => sub {
++        plan tests => 3;
  
--            $a_end <=> $b_end;
--        } grep { /^file\d+/ } keys %opts;
--        foreach my $contents (@files) {
--            my $file = File::Temp->new;
--            print {$file} $contents;
--            close $file or die $!;
--        }
++        my $env           = delete $opts{env}  // '';
++        my $argv          = delete $opts{argv} // [];
++        my $expected_opts = delete $opts{expected_opts};
  
--        my ( $got_opts, $got_targets );
++        is( scalar keys %opts, 0, 'All the keys are gone' );
  
++        my $got_opts;
++        my $got_targets;
          do {
--            local $ENV{'ACK_OPTIONS'} = $env;
--            local @ARGV;
++            local $ENV{ACK_OPTIONS} = $env;
++            local @ARGV = ();
  
              my @arg_sources = (
                  { name => 'ARGV', contents => $argv },
--                map { +{ name => $_, contents => scalar read_file($_) } } @files,
              );
  
              $got_opts    = App::Ack::ConfigLoader::process_args( @arg_sources );
@@@ -325,6 -325,6 +284,6 @@@
          };
  
          is_deeply( $got_opts, $expected_opts, 'Options match' );
--        is_deeply( $got_targets, $expected_targets, 'Targets match' );
++        is_empty_array( $got_targets, 'Got no targets' );
      };
  }
diff --cc t/context.t
index 2e4b1b6,2e4b1b6..fbccc7a
--- a/t/context.t
+++ b/t/context.t
@@@ -3,7 -3,7 +3,7 @@@
  use warnings;
  use strict;
  
--use Test::More tests => 36;
++use Test::More tests => 19;
  
  use lib 't';
  use Util;
@@@ -77,7 -77,7 +77,7 @@@ HER
  
  # Try context 1.
  CONTEXT_ONE: {
--    my @expected = line_split( <<"HERE" );
++    my @expected = line_split( <<'HERE' );
  
  "For the love of God, Montresor!"
  HERE
@@@ -106,7 -106,7 +106,7 @@@ HER
  
  # -1 must not stop the ending context from displaying.
  CONTEXT_DEFAULT: {
--    my @expected = line_split( <<"HERE" );
++    my @expected = line_split( <<'HERE' );
  or prohibiting the free exercise thereof; or abridging the freedom of
  speech, or of the press; or the right of the people peaceably to assemble,
  and to petition the Government for a redress of grievances.
@@@ -121,7 -121,7 +121,7 @@@ HER
  
  # -C with overlapping contexts (adjacent lines)
  CONTEXT_OVERLAPPING: {
--    my @expected = line_split( <<"HERE" );
++    my @expected = line_split( <<'HERE' );
  This is line 03
  This is line 04
  This is line 05
@@@ -139,7 -139,7 +139,7 @@@ HER
  
  # -C with contexts that touch.
  CONTEXT_ADJACENT: {
--    my @expected = line_split( <<"HERE" );
++    my @expected = line_split( <<'HERE' );
  This is line 01
  This is line 02
  This is line 03
@@@ -161,7 -161,7 +161,7 @@@ HER
  
  # -C with contexts that just don't touch.
  CONTEXT_NONADJACENT: {
--    my @expected = line_split( <<"HERE" );
++    my @expected = line_split( <<'HERE' );
  This is line 01
  This is line 02
  This is line 03
@@@ -245,7 -245,7 +245,7 @@@ HER
  #    even though there is a 4th match in the after context of the third match
  #    ("ratifying" in the last line)
  CONTEXT_MAX_COUNT: {
--    my @expected = line_split( <<"HERE" );
++    my @expected = line_split( <<'HERE' );
  ratified by the Legislatures of three fourths of the several States, or
  by Conventions in three fourths thereof, as the one or the other Mode of
  Ratification may be proposed by the Congress; Provided that no Amendment
@@@ -290,7 -290,7 +290,7 @@@ HER
  # Grouping works with context and multiple files.
  # i.e. a separator line between different matches in the same file and no separator between files
  GROUPING_MULTIPLE_FILES: {
--    my @expected = line_split( <<'HERE' );
++    my @expected = line_split( <<"HERE" );
  t/text/amontillado.txt
  258-As I said these words I busied myself among the pile of bones of
  259:which I have before spoken. Throwing them aside, I soon uncovered
@@@ -341,3 -341,3 +341,7 @@@ HER
  
      ack_lists_match( [ @args, @files ], \@expected, "Looking for $regex" );
  }
++
++done_testing();
++
++exit 0;
diff --cc t/default-filter.t
index 4eedc8e,4eedc8e..f884d36
--- a/t/default-filter.t
+++ b/t/default-filter.t
@@@ -8,6 -8,6 +8,7 @@@ use FilterTest
  use Test::More tests => 1;
  
  use App::Ack::Filter::Default;
++use App::Ack::Filter;
  
  App::Ack::Filter->register_filter('default' => 'App::Ack::Filter::Default');
  
diff --cc t/file-iterator.t
index cadc5d4,cadc5d4..66eb3d6
--- a/t/file-iterator.t
+++ b/t/file-iterator.t
@@@ -4,7 -4,7 +4,8 @@@ use warnings
  use strict;
  
  use Test::More tests => 1;
--use File::Next 0.22;
++
++use File::Next;
  
  use lib 't';
  use Util;
diff --cc t/file-permission.t
index 95a052e,95a052e..838bd13
--- a/t/file-permission.t
+++ b/t/file-permission.t
@@@ -66,9 -66,9 +66,9 @@@ done_testing()
  sub check_with {
      local $Test::Builder::Level = $Test::Builder::Level + 1;
  
--    my ( @args ) = @_;
++    my @args = @_;
  
--    return subtest "check_with( $args[0] )" => sub {
++    return subtest subtest_name( @args ) => sub {
          plan tests => 4;
  
          my $opts = {};
diff --cc t/filetype-detection.t
index 0000000,0000000..8d1afac
new file mode 100644
--- /dev/null
+++ b/t/filetype-detection.t
@@@ -1,0 -1,0 +1,36 @@@
++#!perl -T
++
++use strict;
++use warnings;
++use lib 't';
++use Test::More tests => 2;
++use Util;
++
++prep_environment();
++
++subtest 'Lua shebang' => sub {
++    plan tests => 1;
++
++    ack_sets_match(
++        [ '--lua', '-f', 't/swamp' ],
++        [ 't/swamp/lua-shebang-test' ],
++        'Lua files should be detected by shebang'
++    );
++};
++
++
++subtest 'R extensions' => sub {
++    plan tests => 2;
++
++    my @expected = qw(
++        t/swamp/example.R
++    );
++
++    my @args    = qw( --rr -f );
++    my @results = run_ack( @args );
++
++    sets_match( \@results, \@expected, __FILE__ );
++};
++
++done_testing();
++exit 0;
diff --cc t/filetypes.t
index 18b4dab,18b4dab..04805d2
--- a/t/filetypes.t
+++ b/t/filetypes.t
@@@ -3,21 -3,21 +3,25 @@@
  use warnings;
  use strict;
  
--use Test::More tests => 19;
++use Test::More tests => 20;
  
  use lib 't';
  use Util;
  
--my %types_for_file;
  
  prep_environment();
  
--sets_match( [filetypes( 't/swamp/perl.pod' )], [qw( parrot perl )], 'foo.pod can be multiple things' );
++my %types_for_file;
++populate_filetypes();
++
++
++sets_match( [filetypes( 't/swamp/perl.pod' )], [qw( parrot perl pod )], 'foo.pod can be multiple things' );
  sets_match( [filetypes( 't/swamp/perl.pm' )], [qw( perl )], 't/swamp/perl.pm' );
  sets_match( [filetypes( 't/swamp/Makefile.PL' )], [qw( perl )], 't/swamp/Makefile.PL' );
  sets_match( [filetypes( 'Unknown.wango' )], [], 'Unknown' );
  
  ok(  is_filetype( 't/swamp/perl.pod', 'perl' ), 'foo.pod can be perl' );
++ok(  is_filetype( 't/swamp/perl.pod', 'pod' ), 'foo.pod can be pod' );
  ok(  is_filetype( 't/swamp/perl.pod', 'parrot' ), 'foo.pod can be parrot' );
  ok( !is_filetype( 't/swamp/perl.pod', 'ruby' ), 'foo.pod cannot be ruby' );
  ok(  is_filetype( 't/swamp/perl.handler.pod', 'perl' ), 'perl.handler.pod can be perl' );
@@@ -45,6 -45,6 +49,9 @@@ MATCH_VIA_CONTENT: 
  
  done_testing;
  
++exit 0;
++
++
  sub populate_filetypes {
      my ( $type_lines, undef ) = run_ack_with_stderr('--help-types');
  
@@@ -68,17 -68,17 +75,14 @@@
      return;
  }
  
--# XXX Implement me with --show-types.
++
  sub filetypes {
      my $filename = reslash(shift);
  
--    if ( !%types_for_file ) {
--        populate_filetypes();
--    }
--
      return @{ $types_for_file{$filename} || [] };
  }
  
++
  sub is_filetype {
      my ( $filename, $wanted_type ) = @_;
  
diff --cc t/forbidden-options.t
index 0000000,0000000..8b73914
new file mode 100644
--- /dev/null
+++ b/t/forbidden-options.t
@@@ -1,0 -1,0 +1,144 @@@
++#!perl -T
++
++# XXX This test need to include not-forbidden-options, like --sort and --smart-case.
++
++use strict;
++use warnings;
++
++use Test::More tests => 2;
++
++use File::Spec ();
++use File::Temp ();
++
++use lib 't';
++use Util;
++
++prep_environment();
++
++# Global:
++# /tmp/x/etc/.ackrc
++# /tmp/x/swamp
++
++my $wd = getcwd_clean();
++
++_test_project_ackrc();
++_test_home_ackrc();
++
++exit 0;
++
++# Test project directory
++# ackrc in /tmp/x/project/.ackrc
++sub _test_project_ackrc {
++    local $Test::Builder::Level = $Test::Builder::Level + 1;
++
++    return subtest subtest_name() => sub {
++        plan tests => 3;
++
++        my $base_obj = File::Temp->newdir;
++        my $base = $base_obj->dirname;
++
++        # /tmp/x/project
++        my $projectdir = File::Spec->catdir( $base, 'project' );
++        safe_mkdir( $projectdir );
++
++        # /tmp/x/project/subdir
++        my $projectsubdir = File::Spec->catdir( $projectdir, 'subdir' );
++        safe_mkdir( $projectsubdir );
++
++        # /tmp/x/project/subdir/foo.pl
++        my $projectfile = File::Spec->catfile( $projectsubdir, 'foo.pl' );
++        write_file( $projectfile, '#!/usr/bin/perl' );
++
++        safe_chdir( $projectdir );
++
++        # All three of these options are illegal in a project .ackrc.
++        for my $option ( qw( match output pager ) ) {
++            subtest $option => sub {
++                plan tests => 2;
++
++                # /tmp/x/project/.ackrc
++                my $ackrc = _create_ackrc( $projectdir, "--$option=$option" );
++
++                # Explicitly pass --env or else the test will ignore .ackrc.
++                my ( $stdout, $stderr ) = run_ack_with_stderr( '-f', '--env' );
++
++                is_empty_array( $stdout, 'No output with the errors' );
++                if ( $option eq 'pager' ) {
++                    first_line_like( $stderr, qr/\QOption --$option is forbidden in project .ackrc files/, "$option illegal" );
++                }
++                else {
++                    first_line_like( $stderr, qr/\QOption --$option is forbidden in .ackrc files/, "$option illegal" );
++                }
++            };
++        }
++
++        # Go back to working directory so the temporary directories can get erased.
++        safe_chdir( $wd );
++    };
++}
++
++
++# Test home directory
++# ackrc in  /tmp/x/home/.ackrc
++# search in /tmp/x/swamp
++sub _test_home_ackrc {
++    local $Test::Builder::Level = $Test::Builder::Level + 1;
++
++    return subtest subtest_name() => sub {
++        plan tests => 3;
++
++        my $base_obj = File::Temp->newdir;
++        my $base = $base_obj->dirname;
++
++        # /tmp/x/home
++        my $homedir = File::Spec->catdir( $base, 'home' );
++        safe_mkdir( $homedir );
++
++        # /tmp/x/project
++        my $projectdir = File::Spec->catdir( $base, 'project' );
++        safe_mkdir( $projectdir );
++
++        # /tmp/x/project/foo.pl
++        my $projectfile = File::Spec->catfile( $projectdir, 'foo.pl' );
++        write_file( $projectfile, '#!/usr/bin/perl' );
++
++        safe_chdir( $projectdir );
++
++        # --match and --output are illegal in a home .ackrc, but --pager is ok.
++        for my $option ( qw( match output pager ) ) {
++            subtest $option => sub {
++                plan tests => 2;
++
++                # /tmp/x/home/.ackrc
++                my $ackrc = _create_ackrc( $homedir, "--$option=$option" );
++                local $ENV{HOME} = $homedir;
++
++                # Explicitly pass --env or else the test will ignore .ackrc.
++                my ( $stdout, $stderr ) = run_ack_with_stderr( '-f', '--env' );
++
++                if ( $option eq 'pager' ) {
++                    is_deeply( $stdout, [ 'foo.pl' ], 'Found foo.pl OK' );
++                    is_empty_array( $stderr, '--pager is OK' );
++                }
++                else {
++                    is_empty_array( $stdout, 'No output with the errors' );
++                    first_line_like( $stderr, qr/\QOption --$option is forbidden in .ackrc files/, "$option illegal" );
++                }
++            };
++        }
++
++        # Go back to working directory so the temporary directories can get erased.
++        safe_chdir( $wd );
++    };
++}
++
++
++sub _create_ackrc {
++    my $dir    = shift;
++    my $option = shift;
++
++    my $ackrc = File::Spec->catfile( $dir, '.ackrc' );
++    write_file( $ackrc, join( "\n", '--sort-files', $option, '--smart-case', '' ) );
++
++    return $ackrc;
++}
diff --cc t/illegal-regex.t
index 99d9b5a,99d9b5a..0d7a8a8
--- a/t/illegal-regex.t
+++ b/t/illegal-regex.t
@@@ -10,7 -10,7 +10,7 @@@ use Util
  
  prep_environment();
  
--# test for behavior with illegal regexes
++# Test for behavior with illegal regexes.
  my @tests = (
      [ 'illegal pattern',  '?foo', 't/' ],
      [ 'illegal -g regex', '-g', '?foo', 't/' ],
@@@ -22,11 -22,11 +22,18 @@@ for ( @tests ) 
      test_ack_with( @{$_} );
  }
  
++done_testing();
++
++exit 0;
++
++
  sub test_ack_with {
      my $testcase = shift;
      my @args     = @_;
  
--    return subtest "test_ack_with( $testcase: @args )" => sub {
++    return subtest subtest_name( $testcase, @args ) => sub {
++        plan tests => 4;
++
          my ( $stdout, $stderr ) = run_ack_with_stderr( @args );
  
          is_empty_array( $stdout, "No STDOUT for $testcase" );
diff --cc t/incomplete-last-line.t
index e341c78,e341c78..5150989
--- a/t/incomplete-last-line.t
+++ b/t/incomplete-last-line.t
@@@ -3,7 -3,7 +3,7 @@@
  use warnings;
  use strict;
  
--use Test::More tests => 5;
++use Test::More tests => 4;
  
  use lib 't';
  use Util;
@@@ -27,7 -27,7 +27,7 @@@ VERIFY_LAST_LINE_IS_MISSING_NEWLINE: 
  
  
  INCOMPLETE_LAST_LINE: {
--    my @expected = line_split( <<"HERE" );
++    my @expected = line_split( <<'HERE' );
  but no new line on the last line!
  the last full measure of devotion -- that we here highly resolve that
  HERE
@@@ -39,3 -39,3 +39,5 @@@
  }
  
  done_testing();
++
++exit 0;
diff --cc t/interactive.t
index c90a673,c90a673..658f130
--- a/t/interactive.t
+++ b/t/interactive.t
@@@ -75,7 -75,7 +75,7 @@@ INTERACTIVE_GROUPING_COLOR: 
      my $CM       = color 'black on_yellow';
      my $LINE_END = "\e[0m\e[K";
  
--    my @expected_lines = line_split( <<"HERE" );
++    my @expected_lines = split( /\n/, <<"HERE" );
  ${CFN}t/text/bill-of-rights.txt${CRESET}
  ${CLN}4${CRESET}:or prohibiting the ${CM}free${CRESET} exercise thereof; or abridging the ${CM}free${CRESET}dom of$LINE_END
  ${CLN}10${CRESET}:A well regulated Militia, being necessary to the security of a ${CM}free${CRESET} State,$LINE_END
diff --cc t/invalid-ackrc.t
index cee0902,cee0902..d8c6518
--- a/t/invalid-ackrc.t
+++ b/t/invalid-ackrc.t
@@@ -17,7 -17,7 +17,7 @@@ my @types = 
      ruby   => [qw{.rb Rakefile}],
  );
  
--plan tests => sum(map { ref($_) ? scalar(@$_) : 1 } @types) + 14;
++plan tests => sum(map { ref($_) ? scalar(@{$_}) : 1 } @types) + 14; ## no critic ( BuiltinFunctions::ProhibitUselessTopic )
  
  prep_environment();
  
@@@ -26,7 -26,7 +26,7 @@@ my $wd = getcwd_clean()
  my $tempdir = File::Temp->newdir;
  
  safe_chdir( $tempdir->dirname );
--write_file '.ackrc', "--frobnicate\n";
++write_file( '.ackrc', "--frobnicate\n" );
  
  my $output;
  
@@@ -60,9 -60,9 +60,9 @@@ like $output, qr/Usage: ack/
      ($output, my $stderr) = run_ack_with_stderr( '--env', '--man' );
      # Don't worry if man complains about long lines,
      # or if the terminal doesn't handle Unicode:
--    is( scalar(grep !m{can't\ break\ line
++    is( scalar(grep { !m{can't\ break\ line
                       |Wide\ character\ in\ print
--                     |Unknown\ escape\ E<0x[[:xdigit:]]+>}x, @{$stderr}),
++                     |Unknown\ escape\ E<0x[[:xdigit:]]+>}x } @{$stderr}),
          0,
          'Should have no output to stderr: ack --env --man' )
          or diag( join( "\n", 'STDERR:', @{$stderr} ) );
diff --cc t/issue276.t
index facb916,facb916..0000000
deleted file mode 100644,100644
--- a/t/issue276.t
+++ /dev/null
@@@ -1,33 -1,33 +1,0 @@@
--#!perl -T
--
--# https://github.com/beyondgrep/ack2/issues/276
--
--use strict;
--use warnings;
--
--use lib 't';
--use Util;
--use Test::More;
--
--my @regexes = (
--    '((foo)bar)',
--    '((foo)(bar))',
--);
--
--plan tests => scalar @regexes;
--
--prep_environment();
--
--my $match_start = "\e[30;43m";
--my $match_end   = "\e[0m";
--
--for my $regex ( @regexes ) {
--    subtest $regex => sub {
--        plan tests => 2;
--
--        my ( $stdout, $stderr ) = pipe_into_ack_with_stderr( \'foobar', '--color', $regex );
--
--        is_empty_array( $stderr, 'Verify that no lines are printed to standard error' );
--        is_deeply( $stdout, [ "${match_start}foobar${match_end}" ], 'Verify a single-line output, properly colored' );
--    };
--}
diff --cc t/issue491.t
index baaa5e9,baaa5e9..c31b2b2
--- a/t/issue491.t
+++ b/t/issue491.t
@@@ -12,6 -12,6 +12,7 @@@ prep_environment()
  
  my $dir = File::Temp->newdir;
  my $wd  = getcwd_clean();
++
  safe_chdir( $dir->dirname );
  write_file('space-newline.txt', " \n");
  write_file('space-newline-newline.txt', " \n\n");
@@@ -30,4 -30,4 +31,7 @@@ sets_match(\@results, 
      'space-newline-newline.txt:1',
  ], 'both files should be in -c output with correct counts');
  
--safe_chdir( $wd );
++safe_chdir( $wd );  # Get out of temp directory so it can be cleaned up.
++
++done_testing();
++exit 0;
diff --cc t/issue522.t
index 39c7807,39c7807..84734f9
--- a/t/issue522.t
+++ b/t/issue522.t
@@@ -14,9 -14,9 +14,7 @@@ prep_environment()
  my @stdout;
  
  @stdout = run_ack("use strict;\nuse warnings", 't/swamp');
--
--is scalar(@stdout), 0, 'an embedded newline in the search regex should never match anything';
++is_empty_array( \@stdout, 'An embedded newline in the search regex should never match anything' );
  
  @stdout = run_ack('-A', '1', "use strict;\nuse warnings", 't/swamp');
--
--is scalar(@stdout), 0, 'an embedded newline in the search regex should never match anything, even with context';
++is_empty_array( \@stdout, 'An embedded newline in the search regex should never match anything, even with context' );
diff --cc t/issue562.t
index 36cd7b1,36cd7b1..68f22aa
--- a/t/issue562.t
+++ b/t/issue562.t
@@@ -5,19 -5,19 +5,17 @@@ use warnings
  use lib 't';
  
  use Test::More tests => 2;
--use File::Temp;
  use Util;
  
  prep_environment();
  
--my $tempfile = File::Temp->new();
--print {$tempfile} <<'HERE';
  
++my $tempfile = create_tempfile( ('') x 3 );
  
++my @results = run_ack('^\s\s+$', $tempfile->filename);
  
--HERE
--close $tempfile;
++is_empty_array( \@results, '^\s\s+$ should never match a sequence of empty lines' );
  
--my @results = run_ack('^\s\s+$', $tempfile->filename);
++done_testing();
  
--lists_match(\@results, [], '^\s\s+$ should never match a sequence of empty lines');
++exit 0;
diff --cc t/issue571.t
index de097fb,de097fb..b77f835
--- a/t/issue571.t
+++ b/t/issue571.t
@@@ -5,19 -5,19 +5,15 @@@ use warnings
  use lib 't';
  
  use Test::More tests => 2;
--use File::Temp;
  use Util;
  
  prep_environment();
  
--my $tempfile = File::Temp->new();
--print {$tempfile} <<'HERE';
++my $tempfile = create_tempfile( <<'HERE' );
  fo
  
  oo
  HERE
--close $tempfile;
  
  my @results = run_ack('-l', 'fo\s+oo', $tempfile->filename);
--
--lists_match(\@results, [], '\s+ should never match across line boundaries');
++is_empty_array( \@results, '\s+ should never match across line boundaries' );
diff --cc t/lib/00-coverage.t
index e77dd3b,e77dd3b..0000000
deleted file mode 100644,100644
--- a/t/lib/00-coverage.t
+++ /dev/null
@@@ -1,24 -1,24 +1,0 @@@
--#!perl -T
--
--# Make sure we have a .t file each *.pm.  This prevents a forgetful
--# developer from creating a .pm without making the test, too.
--
--# If you've made this test fail, it's because you need a t/lib/*.t
--# file to test your new module.
--
--use warnings;
--use strict;
--
--use Test::More;
--
--my @pms = glob( '*.pm' );
--
--plan tests => scalar @pms;
--
--for my $pm ( @pms ) {
--    my $t = $pm;
--    $t =~ s/\.pm$/.t/ or die;
--    ok( -r "t/lib/$t", "$pm has a corresponding $t" );
--}
--
--done_testing();
diff --cc t/lib/Ack.t
index 7bef34a,7bef34a..0000000
deleted file mode 100644,100644
--- a/t/lib/Ack.t
+++ /dev/null
@@@ -1,12 -1,12 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--
--use Test::More tests => 1;
--
--use App::Ack;
--
--pass( 'App::Ack loaded with nothing else loaded first' );
--
--done_testing();
diff --cc t/lib/Collection.t
index 74f98da,74f98da..0000000
deleted file mode 100644,100644
--- a/t/lib/Collection.t
+++ /dev/null
@@@ -1,12 -1,12 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--
--use Test::More tests => 1;
--
--use App::Ack::Filter::Collection;
--
--pass( 'App::Ack::Filter::Collection loaded with nothing else loaded first' );
--
--done_testing();
diff --cc t/lib/ConfigDefault.t
index 8801881,8801881..0000000
deleted file mode 100644,100644
--- a/t/lib/ConfigDefault.t
+++ /dev/null
@@@ -1,12 -1,12 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--
--use Test::More tests => 1;
--
--use App::Ack::ConfigDefault;
--
--pass( 'App::Ack::ConfigDefault loaded with nothing else loaded first' );
--
--done_testing();
diff --cc t/lib/ConfigFinder.t
index fcd32c1,fcd32c1..0000000
deleted file mode 100644,100644
--- a/t/lib/ConfigFinder.t
+++ /dev/null
@@@ -1,12 -1,12 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--
--use Test::More tests => 1;
--
--use App::Ack::ConfigFinder;
--
--pass( 'App::Ack::ConfigFinder loaded with nothing else loaded first' );
--
--done_testing();
diff --cc t/lib/ConfigLoader.t
index 5961586,5961586..0000000
deleted file mode 100644,100644
--- a/t/lib/ConfigLoader.t
+++ /dev/null
@@@ -1,12 -1,12 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--
--use Test::More tests => 1;
--
--use App::Ack::ConfigLoader;
--
--pass( 'App::Ack::ConfigLoader loaded with nothing else loaded first' );
--
--done_testing();
diff --cc t/lib/Default.t
index 3b87e19,3b87e19..0000000
deleted file mode 100644,100644
--- a/t/lib/Default.t
+++ /dev/null
@@@ -1,12 -1,12 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--
--use Test::More tests => 1;
--
--use App::Ack::Filter::Default;
--
--pass( 'App::Ack::Filter::Default loaded with nothing else loaded first' );
--
--done_testing();
diff --cc t/lib/Extension.t
index 01113ef,01113ef..0000000
deleted file mode 100644,100644
--- a/t/lib/Extension.t
+++ /dev/null
@@@ -1,12 -1,12 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--
--use Test::More tests => 1;
--
--use App::Ack::Filter::Extension;
--
--pass( 'App::Ack::Filter::Extension loaded with nothing else loaded first' );
--
--done_testing();
diff --cc t/lib/ExtensionGroup.t
index 6e99acd,6e99acd..0000000
deleted file mode 100644,100644
--- a/t/lib/ExtensionGroup.t
+++ /dev/null
@@@ -1,12 -1,12 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--
--use Test::More tests => 1;
--
--use App::Ack::Filter::ExtensionGroup;
--
--pass( 'App::Ack::Filter::ExtensionGroup loaded with nothing else loaded first' );
--
--done_testing();
diff --cc t/lib/Filter.t
index 7a661b0,7a661b0..0000000
deleted file mode 100644,100644
--- a/t/lib/Filter.t
+++ /dev/null
@@@ -1,12 -1,12 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--
--use Test::More tests => 1;
--
--use App::Ack::Filter;
--
--pass( 'App::Ack::Filter loaded with nothing else loaded first' );
--
--done_testing();
diff --cc t/lib/FirstLineMatch.t
index dcfd8fa,dcfd8fa..0000000
deleted file mode 100644,100644
--- a/t/lib/FirstLineMatch.t
+++ /dev/null
@@@ -1,12 -1,12 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--
--use Test::More tests => 1;
--
--use App::Ack::Filter::FirstLineMatch;
--
--pass( 'App::Ack::Filter::FirstLineMatch loaded with nothing else loaded first' );
--
--done_testing();
diff --cc t/lib/Inverse.t
index 6963967,6963967..0000000
deleted file mode 100644,100644
--- a/t/lib/Inverse.t
+++ /dev/null
@@@ -1,12 -1,12 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--
--use Test::More tests => 1;
--
--use App::Ack::Filter::Inverse;
--
--pass( 'App::Ack::Filter::Inverse loaded with nothing else loaded first' );
--
--done_testing();
diff --cc t/lib/Is.t
index 6131be9,6131be9..0000000
deleted file mode 100644,100644
--- a/t/lib/Is.t
+++ /dev/null
@@@ -1,12 -1,12 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--
--use Test::More tests => 1;
--
--use App::Ack::Filter::Is;
--
--pass( 'App::Ack::Filter::Is loaded with nothing else loaded first' );
--
--done_testing();
diff --cc t/lib/IsGroup.t
index d871d7a,d871d7a..0000000
deleted file mode 100644,100644
--- a/t/lib/IsGroup.t
+++ /dev/null
@@@ -1,12 -1,12 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--
--use Test::More tests => 1;
--
--use App::Ack::Filter::IsGroup;
--
--pass( 'App::Ack::Filter::IsGroup loaded with nothing else loaded first' );
--
--done_testing();
diff --cc t/lib/IsPath.t
index 20df477,20df477..0000000
deleted file mode 100644,100644
--- a/t/lib/IsPath.t
+++ /dev/null
@@@ -1,12 -1,12 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--
--use Test::More tests => 1;
--
--use App::Ack::Filter::IsPath;
--
--pass( 'App::Ack::Filter::IsPath loaded with nothing else loaded first' );
--
--done_testing();
diff --cc t/lib/IsPathGroup.t
index cbf6e72,cbf6e72..0000000
deleted file mode 100644,100644
--- a/t/lib/IsPathGroup.t
+++ /dev/null
@@@ -1,12 -1,12 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--
--use Test::More tests => 1;
--
--use App::Ack::Filter::IsPathGroup;
--
--pass( 'App::Ack::Filter::IsPathGroup loaded with nothing else loaded first' );
--
--done_testing();
diff --cc t/lib/Match.t
index 4de608e,4de608e..0000000
deleted file mode 100644,100644
--- a/t/lib/Match.t
+++ /dev/null
@@@ -1,12 -1,12 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--
--use Test::More tests => 1;
--
--use App::Ack::Filter::Match;
--
--pass( 'App::Ack::Filter::Match loaded with nothing else loaded first' );
--
--done_testing();
diff --cc t/lib/MatchGroup.t
index cad1e76,cad1e76..0000000
deleted file mode 100644,100644
--- a/t/lib/MatchGroup.t
+++ /dev/null
@@@ -1,12 -1,12 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--
--use Test::More tests => 1;
--
--use App::Ack::Filter::MatchGroup;
--
--pass( 'App::Ack::Filter::MatchGroup loaded with nothing else loaded first' );
--
--done_testing();
diff --cc t/lib/Resource.t
index db2026b,db2026b..0000000
deleted file mode 100644,100644
--- a/t/lib/Resource.t
+++ /dev/null
@@@ -1,12 -1,12 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--
--use Test::More tests => 1;
--
--use App::Ack::Resource;
--
--pass( 'App::Ack::Resource loaded with nothing else loaded first' );
--
--done_testing();
diff --cc t/lib/Resources.t
index 69d2677,69d2677..0000000
deleted file mode 100644,100644
--- a/t/lib/Resources.t
+++ /dev/null
@@@ -1,12 -1,12 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--
--use Test::More tests => 1;
--
--use App::Ack::Resources;
--
--pass( 'App::Ack::Resources loaded with nothing else loaded first' );
--
--done_testing();
diff --cc t/longopts.t
index 666bd5b,666bd5b..1d3dc3d
--- a/t/longopts.t
+++ b/t/longopts.t
@@@ -13,7 -13,7 +13,7 @@@ use Test::More
  
  # --no-recurse is inconsistent w/--nogroup
  
--plan tests => 38;
++plan tests => 44;
  
  use lib 't';
  use Util;
@@@ -59,23 -59,23 +59,25 @@@ for my $arg ( qw( -i --ignore-case ) ) 
  
  SMART_CASE: {
      my @files = 't/swamp/options.pl';
--    my $opt = '--smart-case';
--    like(
--        +run_ack( $opt, 'upper case', @files ),
--        qr{UPPER CASE},
--        qq{$opt turn on ignore-case when PATTERN has no upper}
--    );
--    unlike(
--        +run_ack( $opt, 'Upper case', @files ),
--        qr{UPPER CASE},
--        qq{$opt does nothing when PATTERN has upper}
--    );
  
--    like(
--        +run_ack( $opt, '-i', 'UpPer CaSe', @files ),
--        qr{UPPER CASE},
--        qq{-i overrides $opt, forcing ignore case, even when PATTERN has upper}
--    );
++    for my $opt ( '-S', '--smart-case' ) {
++        like(
++            +run_ack( $opt, 'upper case', @files ),
++            qr{UPPER CASE},
++            qq{$opt turn on ignore-case when PATTERN has no upper}
++        );
++        unlike(
++            +run_ack( $opt, 'Upper case', @files ),
++            qr{UPPER CASE},
++            qq{$opt does nothing when PATTERN has upper}
++        );
++
++        like(
++            +run_ack( $opt, '-i', 'UpPer CaSe', @files ),
++            qr{UPPER CASE},
++            qq{-i overrides $opt, forcing ignore case, even when PATTERN has upper}
++        );
++    }
  }
  
  # Invert match
@@@ -150,7 -150,7 +152,7 @@@ for my $arg ( qw( -L --files-without-ma
  
  LINE: {
      my @files = 't/swamp/options.pl';
--    my $opt   = '--line=1';
++    my $opt   = '--lines=1';
      my @lines = run_ack( $opt, @files );
  
      is_deeply( \@lines, ['#!/usr/bin/env perl'], 'Only one matching line should be a shebang' );
diff --cc t/lua-shebang.t
index 058d217,058d217..0000000
deleted file mode 100644,100644
--- a/t/lua-shebang.t
+++ /dev/null
@@@ -1,12 -1,12 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--use lib 't';
--use Test::More tests => 1;
--use Util;
--
--prep_environment();
--
--ack_sets_match( [ '--lua', '-f', 't/swamp' ], [ 't/swamp/lua-shebang-test' ],
--    'Lua files should be detected by shebang' );
diff --cc t/mutex-options.t
index 58e7d05,58e7d05..3a8ef6c
--- a/t/mutex-options.t
+++ b/t/mutex-options.t
@@@ -9,75 -9,75 +9,73 @@@ use Util
  
  prep_environment();
  
--## no critic ( ValuesAndExpressions::RequireInterpolationOfMetachars ) Way too many metacharacters in this file
--
  my $file = 't/text/raven.txt';
  my $word = 'nevermore';
  
--# --line
--are_mutually_exclusive('--line', '-l', ['--line=1', '-l', $file]);
--are_mutually_exclusive('--line', '-l', ['--line', 1, '-l', $file]);
--are_mutually_exclusive('--line', '--files-with-matches', ['--line=1', '--files-with-matches', $file]);
--are_mutually_exclusive('--line', '--files-with-matches', ['--line', 1, '--files-with-matches', $file]);
--are_mutually_exclusive('--line', '-L', ['--line=1', '-L', $file]);
--are_mutually_exclusive('--line', '-L', ['--line', 1, '-L', $file]);
--are_mutually_exclusive('--line', '--files-without-matches', ['--line=1', '--files-without-matches', $file]);
--are_mutually_exclusive('--line', '--files-without-matches', ['--line', 1, '--files-without-matches', $file]);
--are_mutually_exclusive('--line', '-o', ['--line=1', '-o', $file]);
--are_mutually_exclusive('--line', '-o', ['--line', 1, '-o', $file]);
--are_mutually_exclusive('--line', '--passthru', ['--line=1', '--passthru', $file]);
--are_mutually_exclusive('--line', '--passthru', ['--line', 1, '--passthru', $file]);
--are_mutually_exclusive('--line', '--match', ['--line=1', '--match', $file]);
--are_mutually_exclusive('--line', '--match', ['--line', 1, '--match', $file]);
--are_mutually_exclusive('--line', '-m', ['--line=1', '-m', 1, $file]);
--are_mutually_exclusive('--line', '-m', ['--line', 1, '-m', 1, $file]);
--are_mutually_exclusive('--line', '-m', ['--line', 1, '-m1', $file]);
--are_mutually_exclusive('--line', '--max-count', ['--line=1', '--max-count', 1, $file]);
--are_mutually_exclusive('--line', '--max-count', ['--line', 1, '--max-count', 1, $file]);
--are_mutually_exclusive('--line', '--max-count', ['--line=1', '--max-count=1', $file]);
--are_mutually_exclusive('--line', '--max-count', ['--line', 1, '--max-count=1', $file]);
--are_mutually_exclusive('--line', '-1', ['--line=1', '-1', $file]);
--are_mutually_exclusive('--line', '-1', ['--line', 1, '-1', $file]);
--are_mutually_exclusive('--line', '-H', ['--line=1', '-H', $file]);
--are_mutually_exclusive('--line', '-H', ['--line', 1, '-H', $file]);
--are_mutually_exclusive('--line', '--with-filename', ['--line=1', '--with-filename', $file]);
--are_mutually_exclusive('--line', '--with-filename', ['--line', 1, '--with-filename', $file]);
--are_mutually_exclusive('--line', '-h', ['--line=1', '-h', $file]);
--are_mutually_exclusive('--line', '-h', ['--line', 1, '-h', $file]);
--are_mutually_exclusive('--line', '--no-filename', ['--line=1', '--no-filename', $file]);
--are_mutually_exclusive('--line', '--no-filename', ['--line', 1, '--no-filename', $file]);
--are_mutually_exclusive('--line', '-c', ['--line=1', '-c', $file]);
--are_mutually_exclusive('--line', '-c', ['--line', 1, '-c', $file]);
--are_mutually_exclusive('--line', '--count', ['--line=1', '--count', $file]);
--are_mutually_exclusive('--line', '--count', ['--line', 1, '--count', $file]);
--are_mutually_exclusive('--line', '--column', ['--line=1', '--column', $file]);
--are_mutually_exclusive('--line', '--column', ['--line', 1, '--column', $file]);
--are_mutually_exclusive('--line', '-A', ['--line=1', '-A', 1, $file]);
--are_mutually_exclusive('--line', '-A', ['--line', 1, '-A', 1, $file]);
--are_mutually_exclusive('--line', '--after-context', ['--line=1', '--after-context', 1, $file]);
--are_mutually_exclusive('--line', '--after-context', ['--line', 1, '--after-context', 1, $file]);
--are_mutually_exclusive('--line', '--after-context', ['--line=1', '--after-context=1', $file]);
--are_mutually_exclusive('--line', '--after-context', ['--line', 1, '--after-context=1', $file]);
--are_mutually_exclusive('--line', '-B', ['--line=1', '-B', 1, $file]);
--are_mutually_exclusive('--line', '-B', ['--line', 1, '-B', 1, $file]);
--are_mutually_exclusive('--line', '--before-context', ['--line=1', '--before-context', 1, $file]);
--are_mutually_exclusive('--line', '--before-context', ['--line', 1, '--before-context', 1, $file]);
--are_mutually_exclusive('--line', '--before-context', ['--line=1', '--before-context=1', $file]);
--are_mutually_exclusive('--line', '--before-context', ['--line', 1, '--before-context=1', $file]);
--are_mutually_exclusive('--line', '-C', ['--line=1', '-C', 1, $file]);
--are_mutually_exclusive('--line', '-C', ['--line', 1, '-C', 1, $file]);
--are_mutually_exclusive('--line', '--context', ['--line=1', '--context', 1, $file]);
--are_mutually_exclusive('--line', '--context', ['--line', 1, '--context', 1, $file]);
--are_mutually_exclusive('--line', '--context', ['--line=1', '--context=1', $file]);
--are_mutually_exclusive('--line', '--context', ['--line', 1, '--context=1', $file]);
--are_mutually_exclusive('--line', '--print0', ['--line=1', '--print0', $file]);
--are_mutually_exclusive('--line', '--print0', ['--line', 1, '--print0', $file]);
--are_mutually_exclusive('--line', '-f', ['--line=1', '-f', $file]);
--are_mutually_exclusive('--line', '-f', ['--line', 1, '-f', $file]);
--are_mutually_exclusive('--line', '-g', ['--line=1', '-g', $file]);
--are_mutually_exclusive('--line', '-g', ['--line', 1, '-g', $file]);
--are_mutually_exclusive('--line', '--show-types', ['--line=1', '--show-types', $file]);
--are_mutually_exclusive('--line', '--show-types', ['--line', 1, '--show-types', $file]);
++# --lines
++are_mutually_exclusive('--lines', '-l', ['--lines=1', '-l', $file]);
++are_mutually_exclusive('--lines', '-l', ['--lines', 1, '-l', $file]);
++are_mutually_exclusive('--lines', '--files-with-matches', ['--lines=1', '--files-with-matches', $file]);
++are_mutually_exclusive('--lines', '--files-with-matches', ['--lines', 1, '--files-with-matches', $file]);
++are_mutually_exclusive('--lines', '-L', ['--lines=1', '-L', $file]);
++are_mutually_exclusive('--lines', '-L', ['--lines', 1, '-L', $file]);
++are_mutually_exclusive('--lines', '--files-without-matches', ['--lines=1', '--files-without-matches', $file]);
++are_mutually_exclusive('--lines', '--files-without-matches', ['--lines', 1, '--files-without-matches', $file]);
++are_mutually_exclusive('--lines', '-o', ['--lines=1', '-o', $file]);
++are_mutually_exclusive('--lines', '-o', ['--lines', 1, '-o', $file]);
++are_mutually_exclusive('--lines', '--passthru', ['--lines=1', '--passthru', $file]);
++are_mutually_exclusive('--lines', '--passthru', ['--lines', 1, '--passthru', $file]);
++are_mutually_exclusive('--lines', '--match', ['--lines=1', '--match', $file]);
++are_mutually_exclusive('--lines', '--match', ['--lines', 1, '--match', $file]);
++are_mutually_exclusive('--lines', '-m', ['--lines=1', '-m', 1, $file]);
++are_mutually_exclusive('--lines', '-m', ['--lines', 1, '-m', 1, $file]);
++are_mutually_exclusive('--lines', '-m', ['--lines', 1, '-m1', $file]);
++are_mutually_exclusive('--lines', '--max-count', ['--lines=1', '--max-count', 1, $file]);
++are_mutually_exclusive('--lines', '--max-count', ['--lines', 1, '--max-count', 1, $file]);
++are_mutually_exclusive('--lines', '--max-count', ['--lines=1', '--max-count=1', $file]);
++are_mutually_exclusive('--lines', '--max-count', ['--lines', 1, '--max-count=1', $file]);
++are_mutually_exclusive('--lines', '-1', ['--lines=1', '-1', $file]);
++are_mutually_exclusive('--lines', '-1', ['--lines', 1, '-1', $file]);
++are_mutually_exclusive('--lines', '-H', ['--lines=1', '-H', $file]);
++are_mutually_exclusive('--lines', '-H', ['--lines', 1, '-H', $file]);
++are_mutually_exclusive('--lines', '--with-filename', ['--lines=1', '--with-filename', $file]);
++are_mutually_exclusive('--lines', '--with-filename', ['--lines', 1, '--with-filename', $file]);
++are_mutually_exclusive('--lines', '-h', ['--lines=1', '-h', $file]);
++are_mutually_exclusive('--lines', '-h', ['--lines', 1, '-h', $file]);
++are_mutually_exclusive('--lines', '--no-filename', ['--lines=1', '--no-filename', $file]);
++are_mutually_exclusive('--lines', '--no-filename', ['--lines', 1, '--no-filename', $file]);
++are_mutually_exclusive('--lines', '-c', ['--lines=1', '-c', $file]);
++are_mutually_exclusive('--lines', '-c', ['--lines', 1, '-c', $file]);
++are_mutually_exclusive('--lines', '--count', ['--lines=1', '--count', $file]);
++are_mutually_exclusive('--lines', '--count', ['--lines', 1, '--count', $file]);
++are_mutually_exclusive('--lines', '--column', ['--lines=1', '--column', $file]);
++are_mutually_exclusive('--lines', '--column', ['--lines', 1, '--column', $file]);
++are_mutually_exclusive('--lines', '-A', ['--lines=1', '-A', 1, $file]);
++are_mutually_exclusive('--lines', '-A', ['--lines', 1, '-A', 1, $file]);
++are_mutually_exclusive('--lines', '--after-context', ['--lines=1', '--after-context', 1, $file]);
++are_mutually_exclusive('--lines', '--after-context', ['--lines', 1, '--after-context', 1, $file]);
++are_mutually_exclusive('--lines', '--after-context', ['--lines=1', '--after-context=1', $file]);
++are_mutually_exclusive('--lines', '--after-context', ['--lines', 1, '--after-context=1', $file]);
++are_mutually_exclusive('--lines', '-B', ['--lines=1', '-B', 1, $file]);
++are_mutually_exclusive('--lines', '-B', ['--lines', 1, '-B', 1, $file]);
++are_mutually_exclusive('--lines', '--before-context', ['--lines=1', '--before-context', 1, $file]);
++are_mutually_exclusive('--lines', '--before-context', ['--lines', 1, '--before-context', 1, $file]);
++are_mutually_exclusive('--lines', '--before-context', ['--lines=1', '--before-context=1', $file]);
++are_mutually_exclusive('--lines', '--before-context', ['--lines', 1, '--before-context=1', $file]);
++are_mutually_exclusive('--lines', '-C', ['--lines=1', '-C', 1, $file]);
++are_mutually_exclusive('--lines', '-C', ['--lines', 1, '-C', 1, $file]);
++are_mutually_exclusive('--lines', '--context', ['--lines=1', '--context', 1, $file]);
++are_mutually_exclusive('--lines', '--context', ['--lines', 1, '--context', 1, $file]);
++are_mutually_exclusive('--lines', '--context', ['--lines=1', '--context=1', $file]);
++are_mutually_exclusive('--lines', '--context', ['--lines', 1, '--context=1', $file]);
++are_mutually_exclusive('--lines', '--print0', ['--lines=1', '--print0', $file]);
++are_mutually_exclusive('--lines', '--print0', ['--lines', 1, '--print0', $file]);
++are_mutually_exclusive('--lines', '-f', ['--lines=1', '-f', $file]);
++are_mutually_exclusive('--lines', '-f', ['--lines', 1, '-f', $file]);
++are_mutually_exclusive('--lines', '-g', ['--lines=1', '-g', $file]);
++are_mutually_exclusive('--lines', '-g', ['--lines', 1, '-g', $file]);
++are_mutually_exclusive('--lines', '--show-types', ['--lines=1', '--show-types', $file]);
++are_mutually_exclusive('--lines', '--show-types', ['--lines', 1, '--show-types', $file]);
  
  # -l/--files-with-matches
  are_mutually_exclusive('-l', '-L', ['-l', '-L', $word, $file]);
@@@ -324,7 -324,7 +322,7 @@@ sub are_mutually_exclusive 
  
      my ( $stdout, $stderr ) = run_ack_with_stderr(@args);
  
--    return subtest "are_mutually_exclusive( $opt1, $opt2, @args )" => sub {
++    return subtest subtest_name( $opt1, $opt2, $args ) => sub {
          plan tests => 4;
  
          isnt( get_rc(), 0, 'The ack command should fail' );
diff --cc t/named-pipes.t
index 0000000,0000000..bef97f9
new file mode 100644
--- /dev/null
+++ b/t/named-pipes.t
@@@ -1,0 -1,0 +1,55 @@@
++#!perl -T
++
++use strict;
++use warnings;
++use lib 't';
++
++use File::Temp;
++use Test::More;
++use Util;
++use POSIX ();
++
++MAIN: {
++    local $SIG{'ALRM'} = sub {
++        fail 'Timeout';
++        exit;
++    };
++
++    prep_environment();
++
++    my $tempdir = File::Temp->newdir;
++    safe_mkdir( "$tempdir/foo" );
++    my $rc = eval { POSIX::mkfifo( "$tempdir/foo/test.pipe", oct(660) ) };
++    if ( !$rc ) {
++        dir_cleanup( $tempdir );
++        plan skip_all => $@ ? $@ : q{I can't run a mkfifo, so cannot run this test.};
++    }
++
++    plan tests => 2;
++
++    touch( "$tempdir/foo/bar.txt" );
++
++    alarm 5; # Should be plenty of time.
++
++    my @results = run_ack( '-f', $tempdir );
++
++    is_deeply( \@results, [
++        "$tempdir/foo/bar.txt",
++    ], 'Acking should not find the fifo' );
++
++    dir_cleanup( $tempdir );
++
++    done_testing();
++}
++
++exit 0;
++
++sub dir_cleanup {
++    my $tempdir = shift;
++
++    unlink "$tempdir/foo/bar.txt";
++    rmdir "$tempdir/foo";
++    rmdir $tempdir;
++
++    return;
++}
diff --cc t/noenv.t
index b8bbf3f,b8bbf3f..34ff055
--- a/t/noenv.t
+++ b/t/noenv.t
@@@ -3,7 -3,7 +3,7 @@@
  use strict;
  use warnings;
  
--use Test::More tests => 5;
++use Test::More tests => 3;
  
  use lib 't';
  use Util;
@@@ -48,6 -48,6 +48,8 @@@ write_file( '.ackrc', <<'ACKRC' )
  ACKRC
  
  subtest 'without --noenv' => sub {
++    plan tests => 1;
++
      local @ARGV = ('-f', 'lib/');
      local $ENV{'ACK_OPTIONS'} = '--perl';
  
@@@ -59,6 -59,6 +61,7 @@@
              name     => File::Spec->canonpath(realpath(File::Spec->catfile($tempdir->dirname, '.ackrc'))),
              contents => [ '--type-add=perl:ext:pl,t,pm' ],
              project  => 1,
++            is_ackrc => 1,
          },
          {
              name     => 'ACK_OPTIONS',
@@@ -72,6 -72,6 +75,8 @@@
  };
  
  subtest 'with --noenv' => sub {
++    plan tests => 1;
++
      local @ARGV = ('--noenv', '-f', 'lib/');
      local $ENV{'ACK_OPTIONS'} = '--perl';
  
@@@ -86,7 -86,7 +91,9 @@@
      ], 'Short list comes back because of --noenv' );
  };
  
--NOENV_IN_CONFIG: {
++subtest '--noenv in config' => sub {
++    plan tests => 3;
++
      append_file( '.ackrc', "--noenv\n" );
  
      local $ENV{'ACK_OPTIONS'} = '--perl';
@@@ -95,6 -95,6 +102,6 @@@
      is_empty_array( $stdout );
      is( @{$stderr}, 1 );
      like( $stderr->[0], qr/--noenv found in (?:.*)[.]ackrc/ ) or diag(explain($stderr));
--}
++};
  
  safe_chdir( $wd ); # Go back to the original directory to avoid warnings
diff --cc t/process-substitution.t
index 86424eb,86424eb..f4d0d4f
--- a/t/process-substitution.t
+++ b/t/process-substitution.t
@@@ -36,7 -36,7 +36,7 @@@ my @output
  
  if ( $pid ) {
      close $write;
--    while(<$read>) {
++    while ( <$read> ) {
          chomp;
          push @output, $_;
      }
@@@ -48,7 -48,7 +48,6 @@@ else 
      open STDERR, '>&', $write or die "Can't open: $!";
  
      my @args = build_ack_invocation( qw( --noenv --nocolor --smart-case this ) );
--    # XXX doing this by hand here eq '=('
      my $perl = caret_X();
  
      if ( $ENV{'ACK_TEST_STANDALONE'} ) {
diff --cc t/r-lang-ext.t
index decbcbd,decbcbd..0000000
deleted file mode 100644,100644
--- a/t/r-lang-ext.t
+++ /dev/null
@@@ -1,19 -1,19 +1,0 @@@
--#!perl -T
--
--use strict;
--use warnings;
--use lib 't';
--
--use Test::More tests => 2;
--use Util;
--
--prep_environment();
--
--my @expected = qw(
--    t/swamp/example.R
--);
--
--my @args    = qw( --rr -f );
--my @results = run_ack( @args );
--
--sets_match( \@results, \@expected, __FILE__ );
diff --cc t/swamp/fresh.css
index 760faee,760faee..7e4991b
--- a/t/swamp/fresh.css
+++ b/t/swamp/fresh.css
@@@ -1,1 -1,1 +1,1 @@@
--html,.wp-dialog{background-color:#fff;}* html input,* html .widget{border-color:#dfdfdf;}textarea,input[type="text"],input[type="password"],input[type="file"],input[type="button"],input[type="submit"],input[type="reset"],select{border-color:#dfdfdf;background-color:#fff;}kbd,code{background:#eaeaea;}input[readonly]{background-color:#eee;}.find-box-search{border-color:#dfdfdf;background-color:#f1f1f1;}.find-box{background-color:#f1f1f1;}.find-box-inside{background-color:#fff;}a.page-numb [...]
++html,.wp-dialog{background-color:#fff;}* html input,* html .widget{border-color:#dfdfdf;}
diff --cc t/swamp/fresh.css.min
index 760faee,760faee..7e4991b
--- a/t/swamp/fresh.css.min
+++ b/t/swamp/fresh.css.min
@@@ -1,1 -1,1 +1,1 @@@
--html,.wp-dialog{background-color:#fff;}* html input,* html .widget{border-color:#dfdfdf;}textarea,input[type="text"],input[type="password"],input[type="file"],input[type="button"],input[type="submit"],input[type="reset"],select{border-color:#dfdfdf;background-color:#fff;}kbd,code{background:#eaeaea;}input[readonly]{background-color:#eee;}.find-box-search{border-color:#dfdfdf;background-color:#f1f1f1;}.find-box{background-color:#f1f1f1;}.find-box-inside{background-color:#fff;}a.page-numb [...]
++html,.wp-dialog{background-color:#fff;}* html input,* html .widget{border-color:#dfdfdf;}
diff --cc t/swamp/fresh.min.css
index 760faee,760faee..7e4991b
--- a/t/swamp/fresh.min.css
+++ b/t/swamp/fresh.min.css
@@@ -1,1 -1,1 +1,1 @@@
--html,.wp-dialog{background-color:#fff;}* html input,* html .widget{border-color:#dfdfdf;}textarea,input[type="text"],input[type="password"],input[type="file"],input[type="button"],input[type="submit"],input[type="reset"],select{border-color:#dfdfdf;background-color:#fff;}kbd,code{background:#eaeaea;}input[readonly]{background-color:#eee;}.find-box-search{border-color:#dfdfdf;background-color:#f1f1f1;}.find-box{background-color:#f1f1f1;}.find-box-inside{background-color:#fff;}a.page-numb [...]
++html,.wp-dialog{background-color:#fff;}* html input,* html .widget{border-color:#dfdfdf;}
diff --cc t/trailing-whitespace.t
index 0000000,0000000..0708d2a
new file mode 100644
--- /dev/null
+++ b/t/trailing-whitespace.t
@@@ -1,0 -1,0 +1,94 @@@
++#!perl -T
++
++use warnings;
++use strict;
++
++use Test::More;
++
++use lib 't';
++use Util;
++
++plan tests => 4;
++
++prep_environment();
++
++subtest 'whitespace+dollar normal' => sub {
++    plan tests => 4;
++
++    my @expected = ();
++
++    my @files = qw( t/text/ );
++
++    for my $context ( undef, '-A', '-B', '-C' ) {
++        my @args = qw( \s$ );
++
++        push( @args, $context ) if $context;
++
++        ack_sets_match( [ @args, @files ], \@expected, '\s$ should not match the newlines at the end of a line' );
++    }
++};
++
++subtest 'whitespace+dollar with -l' => sub {
++    plan tests => 1;
++
++    my @expected = ();
++
++    my @files = qw( t/text/ );
++    my @args = qw( \s$ -l );
++
++    ack_sets_match( [ @args, @files ], \@expected, '\s$ should not match the newlines at the end of a line' );
++};
++
++subtest 'whitespace+dollar with -L' => sub {
++    plan tests => 1;
++
++    my @expected = line_split( <<'HERE' );
++t/text/amontillado.txt
++t/text/bill-of-rights.txt
++t/text/constitution.txt
++t/text/gettysburg.txt
++t/text/number.txt
++t/text/numbered-text.txt
++t/text/ozymandias.txt
++t/text/raven.txt
++HERE
++
++    my @files = qw( t/text/ );
++    my @args = qw( \s$ -L --sort );
++
++    ack_sets_match( [ @args, @files ], \@expected, '\s$ should not match the newlines at the end of a line' );
++};
++
++subtest 'whitespace+dollar with -v' => sub {
++    plan tests => 4;
++
++    my @expected = line_split( <<'HERE' );
++I met a traveller from an antique land
++Who said: Two vast and trunkless legs of stone
++Stand in the desert... Near them, on the sand,
++Half sunk, a shattered visage lies, whose frown,
++And wrinkled lip, and sneer of cold command,
++Tell that its sculptor well those passions read
++Which yet survive, stamped on these lifeless things,
++The hand that mocked them, and the heart that fed:
++And on the pedestal these words appear:
++'My name is Ozymandias, king of kings:
++Look on my works, ye Mighty, and despair!'
++Nothing beside remains. Round the decay
++Of that colossal wreck, boundless and bare
++The lone and level sands stretch far away.
++HERE
++
++    my @files = qw( t/text/ozymandias.txt );
++
++    for my $context ( undef, '-A', '-B', '-C' ) {
++        my @args = qw( \s$ -v );
++
++        push( @args, $context ) if $context;
++
++        ack_sets_match( [ @args, @files ], \@expected, '\s$ should not match the newlines at the end of a line' );
++    }
++};
++
++done_testing();
++exit 0;
diff --cc xt/coding-standards.t
index 0000000,ec25e76..2b38a14
mode 000000,100644..100644
--- a/xt/coding-standards.t
+++ b/xt/coding-standards.t
@@@ -1,0 -1,35 +1,56 @@@
 -#!perl -Tw
++#!perl -T
+ 
+ use warnings;
+ use strict;
+ 
+ use lib 't';
++use File::Next;
+ use Util;
+ 
+ use Test::More;
+ 
 -my @files = ( qw( ack ), glob( '*.pm' ), glob( 't/*.t' ) );
++
++# Get all the ack component files.
++my @files = ( 'ack' );
++my $libs = File::Next::files( { descend_filter => sub { !/\Q.git/ }, file_filter => sub { /\.pm$/ } }, 'lib' );
++while ( my $file = $libs->() ) {
++    push @files, $file;
++}
++ at files == 23 or die 'I should have exactly 22 modules + ack';
++
++# Get all the test files.
++for my $spec ( 't/*.t', 'xt/*.t' ) {
++    my @these_files = glob( $spec ) or die "Couldn't find any $spec";
++    push( @files, @these_files );
++}
+ 
+ plan tests => scalar @files;
+ 
+ for my $file ( @files ) {
+     subtest $file => sub {
 -        plan tests => 2;
++        plan tests => 3;
+ 
+         my @lines = read_file( $file );
+         my $text = join( '', @lines );
+ 
+         chomp @lines;
+         my $ok = 1;
++        my $lineno = 0;
+         for my $line ( @lines ) {
++            ++$lineno;
+             if ( $line =~ /[^ -~]/ ) {
+                 my $col = $-[0] + 1;
 -                diag( "$file has hi-bit characters at $.:$col" );
++                diag( "$file has hi-bit characters at $lineno:$col" );
++                $ok = 0;
++            }
++            if ( $line =~ /\s+$/ ) {
++                diag( "$file has trailing whitespace on line $lineno" );
+                 $ok = 0;
+             }
+         }
 -        ok( $ok, 'No hi-bit characters found' );
++        ok( $ok, "$file: No hi-bit characters found and no trailing whitespace" );
++        ok( $lines[-1] ne '', "$file: Doesn't end with an empty line" );
+ 
+         is( index($text, "\t"), -1, "$file should have no embedded tabs" );
+     }
+ }
diff --cc xt/coresubs.t
index 0000000,0000000..2263439
new file mode 100644
--- /dev/null
+++ b/xt/coresubs.t
@@@ -1,0 -1,0 +1,53 @@@
++#!perl -T
++
++# Checks that we are not using core C<die> or C<warn> except in very specific places.
++# Same with C<mkdir> and C<chdir>.
++# Ignore the App::Ack::Docs modules since they're nothing but text.
++
++## no critic ( Bangs::ProhibitDebuggingModules )
++
++use warnings;
++use strict;
++
++use Test::More tests => 4;
++
++use lib 't';
++use Util;
++
++prep_environment();
++
++my $ack_pm = quotemeta( reslash( 'blib/lib/App/Ack.pm' ) );
++
++my @exclusions = qw(
++    --ignore-dir=Docs
++    --ignore-file=is:ack-standalone
++);
++
++for my $word ( qw( warn die ) ) {
++    subtest "Finding $word" => sub {
++        plan tests => 4;
++
++        my @args = ( '(?<!:)\b' . $word . '\b', '-I', 'blib/lib', @exclusions );
++        my @results = run_ack( @args );
++
++        like( $results[0], qr/^$ack_pm:\d+:=head2 $word/, 'POD' );
++        like( $results[1], qr/^$ack_pm:\d+:sub $word/, 'sub' );
++        is( scalar @results, 2, 'Exactly two hits' );
++    };
++}
++
++# Don't use C<chdir> or C<mkdir>.  Use C<safe_chdir> and C<safe_mkdir>.
++my $util_pm = quotemeta( reslash( 't/Util.pm' ) );
++for my $word ( qw( chdir mkdir ) ) {
++    subtest "Finding $word" => sub {
++        plan tests => 3;
++
++        my @args = ( '-w', '--ignore-file=is:coresubs.t', @exclusions, $word );
++        my @results = run_ack( @args );
++
++        is( scalar @results, 1, 'Exactly one hit...' ) or do { require Data::Dumper; warn Data::Dumper::Dumper( \@results ) };
++        like( $results[0], qr/^$util_pm.+CORE::$word/, '... and it is in the function in Util.pm that wraps the core call' );
++    };
++}
++
++exit 0;
diff --cc xt/man.t
index 62d54dd,62d54dd..53ac39b
--- a/xt/man.t
+++ b/xt/man.t
@@@ -1,5 -1,5 +1,7 @@@
  #!perl
  
++## no critic ( Bangs::ProhibitDebuggingModules )
++
  use strict;
  use warnings;
  use lib 't';
@@@ -20,7 -20,7 +22,18 @@@ sub strip_special_chars 
      my $man_options;
  
      sub _populate_man_options {
--        my ( $man_output, undef ) = run_ack_with_stderr( '--man' );
++        my ( $man_output, $man_stderr ) = run_ack_with_stderr( '--man' );
++
++        if ( !@{$man_stderr} ) {
++            pass( 'Nothing in stderr' );
++        }
++        elsif ( @{$man_stderr} > 1 ) {
++            fail( 'I have more than two lines in stderr' );
++            diag explain $man_stderr;
++        }
++        else {
++            is( $man_stderr->[0], 'stty: standard input: Inappropriate ioctl for device', 'The one warning is one we can ignore' );
++        }
  
          my $in_options_section;
  
@@@ -88,27 -88,27 +101,27 @@@
  }
  
  sub check_for_option_in_man_output {
--    my ( $expected_option ) = @_;
--
      local $Test::Builder::Level = $Test::Builder::Level + 1;
  
--    my @options = get_man_options();
++    my $expected_option = shift;
  
--    my $found;
++    my @options = get_man_options();
  
      foreach my $option ( @options ) {
          if ( $option eq $expected_option ) {
--            $found = 1;
--            last;
++            return pass( "Found $expected_option in --man output" );
          }
      }
  
--    return ok( $found, "Option '$expected_option' found in --man output" );
++    require Data::Dumper;
++    diag Data::Dumper::Dumper( 'Returned options' => \@options );
++
++    return fail( "Option '$expected_option' not found in --man output" );
  }
  
--my @options = get_options();
++my @options = get_expected_options();
  
--plan tests => scalar(@options);
++plan tests => scalar(@options) + 1;
  
  prep_environment();
  
diff --cc xt/pod.t
index f4ee001,f4ee001..d393816
--- a/xt/pod.t
+++ b/xt/pod.t
@@@ -1,13 -1,13 +1,9 @@@
--#!perl -Tw
++#!perl -T
  
  use warnings;
  use strict;
  
  use Test::More;
  
--if ( eval 'use Test::Pod 1.14; 1;' ) { ## no critic (ProhibitStringyEval)
--    all_pod_files_ok();
--}
--else {
--    plan skip_all => 'Test::Pod 1.14 required for testing POD';
--}
++use Test::Pod 1.14;
++all_pod_files_ok();

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-perl/packages/ack.git



More information about the Pkg-perl-cvs-commits mailing list