Imported Upstream version 0.31 upstream/0.31
authorTizenOpenSource <tizenopensrc@samsung.com>
Wed, 14 Feb 2024 04:51:18 +0000 (13:51 +0900)
committerTizenOpenSource <tizenopensrc@samsung.com>
Wed, 14 Feb 2024 04:51:18 +0000 (13:51 +0900)
60 files changed:
CODE_OF_CONDUCT.md [new file with mode: 0644]
CONTRIBUTING.md [new file with mode: 0644]
Changes [new file with mode: 0644]
INSTALL [new file with mode: 0644]
LICENSE [new file with mode: 0644]
MANIFEST [new file with mode: 0644]
META.json [new file with mode: 0644]
META.yml [new file with mode: 0644]
Makefile.PL [new file with mode: 0644]
README.md [new file with mode: 0644]
azure-pipelines.yml [new file with mode: 0644]
cpanfile [new file with mode: 0644]
dev-bin/install-xt-tools.sh [new file with mode: 0755]
dist.ini [new file with mode: 0644]
eg/bench-named.pl [new file with mode: 0644]
eg/bench-pos.pl [new file with mode: 0644]
git/hooks/pre-commit.sh [new file with mode: 0755]
git/setup.pl [new file with mode: 0755]
lib/Params/ValidationCompiler.pm [new file with mode: 0644]
lib/Params/ValidationCompiler/Compiler.pm [new file with mode: 0644]
lib/Params/ValidationCompiler/Exceptions.pm [new file with mode: 0644]
perlcriticrc [new file with mode: 0644]
perltidyrc [new file with mode: 0644]
precious.toml [new file with mode: 0644]
t/00-report-prereqs.dd [new file with mode: 0644]
t/00-report-prereqs.t [new file with mode: 0644]
t/default.t [new file with mode: 0644]
t/exceptions.t [new file with mode: 0644]
t/moose.t [new file with mode: 0644]
t/name-fails.t [new file with mode: 0644]
t/name.t [new file with mode: 0644]
t/named/args-check.t [new file with mode: 0644]
t/named/const-hash.t [new file with mode: 0644]
t/named/locked-hash.t [new file with mode: 0644]
t/named/required.t [new file with mode: 0644]
t/named/return-object.t [new file with mode: 0644]
t/named/slurpy.t [new file with mode: 0644]
t/pairs-to-value-list.t [new file with mode: 0644]
t/positional/default.t [new file with mode: 0644]
t/positional/required.t [new file with mode: 0644]
t/positional/slurpy.t [new file with mode: 0644]
t/self-check.t [new file with mode: 0644]
t/source_for.t [new file with mode: 0644]
t/specio.t [new file with mode: 0644]
t/type-tiny.t [new file with mode: 0644]
test-matrix.als [new file with mode: 0644]
xt/author/00-compile.t [new file with mode: 0644]
xt/author/eol.t [new file with mode: 0644]
xt/author/matrix.t [new file with mode: 0644]
xt/author/mojibake.t [new file with mode: 0644]
xt/author/no-tabs.t [new file with mode: 0644]
xt/author/pod-coverage.t [new file with mode: 0644]
xt/author/pod-spell.t [new file with mode: 0644]
xt/author/pod-syntax.t [new file with mode: 0644]
xt/author/portability.t [new file with mode: 0644]
xt/author/precious.t [new file with mode: 0644]
xt/author/synopsis.t [new file with mode: 0644]
xt/author/test-version.t [new file with mode: 0644]
xt/release/cpan-changes.t [new file with mode: 0644]
xt/release/meta-json.t [new file with mode: 0644]

diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644 (file)
index 0000000..ac28652
--- /dev/null
@@ -0,0 +1,75 @@
+# Contributor Covenant Code of Conduct
+## Our Pledge
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age,
+body size, disability, ethnicity, gender identity and expression, level of
+experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+## Our Standards
+Examples of behavior that contributes to creating a positive environment
+include:
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+Examples of unacceptable behavior by participants include:
+* The use of sexualized language or imagery and unwelcome sexual attention or
+  advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+  address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+  professional setting
+## Our Responsibilities
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+## Scope
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an
+appointed representative at an online or offline event. Representation of a
+project may be further defined and clarified by project maintainers.
+## Enforcement
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at autarch@urth.org. All complaints
+will be reviewed and investigated and will result in a response that is deemed
+necessary and appropriate to the circumstances. The project team is obligated
+to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+## Attribution
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 1.4, available at
+https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+[homepage]: https://www.contributor-covenant.org
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644 (file)
index 0000000..dfa1f44
--- /dev/null
@@ -0,0 +1,115 @@
+# CONTRIBUTING
+
+Thank you for considering contributing to this distribution. This file
+contains instructions that will help you work with the source code.
+
+Please note that if you have any questions or difficulties, you can reach the
+maintainer(s) through the bug queue described later in this document
+(preferred), or by emailing the releaser directly. You are not required to
+follow any of the steps in this document to submit a patch or bug report;
+these are just recommendations, intended to help you (and help us help you
+faster).
+
+The distribution is managed with
+[Dist::Zilla](https://metacpan.org/release/Dist-Zilla).
+
+However, you can still compile and test the code with the
+`Makefile.PL`
+in the repository:
+
+    perl Makefile.PL
+    make
+    make test
+
+
+You may need to satisfy some dependencies. The easiest way to satisfy
+dependencies is to install the last release. This is available at
+https://metacpan.org/release/Params-ValidationCompiler
+
+You can use [`cpanminus`](https://metacpan.org/pod/App::cpanminus) to do this
+without downloading the tarball first:
+
+    $> cpanm --reinstall --installdeps --with-recommends Params::ValidationCompiler
+
+[`Dist::Zilla`](https://metacpan.org/pod/Dist::Zilla) is a very powerful
+authoring tool, but requires a number of author-specific plugins. If you would
+like to use it for contributing, install it from CPAN, then the following
+command to install the needed distros:
+
+    $> dzil authordeps --missing | cpanm
+
+There may also be additional requirements not needed by the dzil build which
+are needed for tests or other development:
+
+    $> dzil listdeps --author --missing | cpanm
+
+Or, you can use the 'dzil stale' command to install all requirements at once:
+
+    $> cpanm Dist::Zilla::App::Command::stale
+    $> dzil stale --all | cpanm
+
+You can also do this via cpanm directly:
+
+    $> cpanm --reinstall --installdeps --with-develop --with-recommends Params::ValidationCompiler
+
+Once installed, here are some dzil commands you might try:
+
+    $> dzil build
+    $> dzil test
+    $> dzil test --release
+    $> dzil xtest
+    $> dzil listdeps --json
+    $> dzil build --notgz
+
+You can learn more about Dist::Zilla at http://dzil.org/.
+
+The code for this distribution is [hosted on GitHub](https://github.com/houseabsolute/Params-ValidationCompiler).
+
+You can submit code changes by forking the repository, pushing your code
+changes to your clone, and then submitting a pull request. Please update the
+Changes file with a user-facing description of your changes as part of your
+work. See the GitHub documentation for [detailed instructions on pull
+requests](https://help.github.com/articles/creating-a-pull-request)
+
+If you have found a bug, but do not have an accompanying patch to fix it, you
+can submit an issue report [via the web](https://github.com/houseabsolute/Params-ValidationCompiler/issues).
+
+## Continuous Integration
+
+All pull requests for this distribution will be automatically tested using
+[Azure Pipelines](https://dev.azure.com/houseabsolute/houseabsolute/_build).
+
+All CI results will be visible in the pull request on GitHub. Follow the
+appropriate links for details when tests fail. PRs cannot be merged until tests
+pass.
+
+## Precious
+
+This distribution uses [precious](https://github.com/houseabsolute/precious)
+to enforce a uniform coding style. This is tested as part of the author
+testing suite. You can install this and any other necessary non-Perl tools by
+running `./dev-bin/install-xt-tools.sh`.
+
+Then you can use `precious` to tidy and lint your code:
+
+    $> precious tidy -a
+    $> precious lint -a
+
+Please run `precious tidy -a` and `precious lint -a` before committing your
+changes and address any issues that it reports.
+
+You can also set up a git pre-commit hook that checks all changed files for
+linting issues by running `./git/setup.pl`.
+
+## Contributor Names
+
+If you send a patch or pull request, your name and email address will be
+included in the documentation as a contributor (using the attribution on the
+commit or patch), unless you specifically request for it not to be. If you
+wish to be listed under a different name or address, you should submit a pull
+request to the `.mailmap` file to contain the correct mapping.
+
+## Generated By
+
+This file was generated via Dist::Zilla::Plugin::GenerateFile::FromShareDir 0.015 from a
+template file originating in Dist-Zilla-PluginBundle-DROLSKY-1.22.
diff --git a/Changes b/Changes
new file mode 100644 (file)
index 0000000..e67b80d
--- /dev/null
+++ b/Changes
@@ -0,0 +1,241 @@
+0.31     2023-01-06
+
+- Require Class::XSAccessor 1.17+ when trying to load it. Earlier versions
+  cause test failures. Reported by David Cantrell. GH #27.
+
+
+0.30     2018-07-31
+
+- Tweaked the POD formatting so that the table of contents generated by
+  MetaCPAN is more useful.
+
+
+0.29     2018-07-31
+
+- Make Class::XSAccessor a recommended dep, not a required one.
+
+
+0.28     2018-07-31
+
+- Added a new option for named params, "return_object". This causes the
+  validation sub to return an object with methods for each param rather than a
+  hashref. This is a great way to avoid typos in hash keys. This idea (and
+  some of the implementation) was shamelessly stolen from Toby Inkster's
+  Type::Params module.
+
+
+0.27     2018-02-11
+
+- Fixed a bug with inlining Moose types. If a type's parent needed environment
+  variables those would not get closed over. Reported by Mark Fowler. GH #22.
+
+- Added a debug option to dump the source of the subroutine before it is
+  eval'd.
+
+
+0.26     2017-11-28
+
+- The exceptions.t test would fail if Sub::Util was not installed. Reported by
+  Paul Howarth. GH #19.
+
+- Fix test failures on Windows. GH #20.
+
+
+0.25     2017-11-23
+
+- All exceptions now include a stack trace by default when treated as a
+  string. This makes finding where validation failed a lot easier. Fixes GH
+  #18.
+
+- The name for a subroutine is now used in some exception messages, even if
+  Sub::Util cannot be installed.
+
+
+0.24     2017-04-08
+
+- The source_for() exported by Params::ValidationCompiler did not work at
+  all. Reported by Diab Jerius. GH #16.
+
+
+0.23     2017-01-23
+
+- Trying to create a validator for positional parameters where a required
+  param came after one with a default did not throw an exception.
+
+- A positional params validator with a slurpy type which had coercions did not
+  return the coerced values. It returned the original values instead.
+
+
+0.22     2016-12-31
+
+- Explicitly load the B module. Previously the code relied on this already
+  being loaded by something else. Fixed by Tomasz Konojacki. PR #11.
+
+- Removed the alpha warning from the docs.
+
+
+0.21     2016-12-06
+
+- Switched to using GitHub issues.
+
+
+0.20     2016-12-05
+
+- The keys for parameter specifications are now validated. If an unknown key
+  is seen then an exception will be thrown. This will help you catch typos in
+  your parameter specification. Implemented by Greg Oschwald. PR #8.
+
+
+0.19     2016-11-21
+
+- Non-inlinable Specio types caused a syntax error when used with positional
+  params.
+
+- Positional params with coercions and defaults did not work properly. The
+  coerced value and the default would simply not be returned in any case.
+
+
+0.18     2016-11-13
+
+- Using coercions with positional parameters could cause a "Modification of a
+  read-only value attempted" exception when the generated code tried to assign
+  to elements of @_. This is now fixed by making a copy if any of the types
+  have a coercion.
+
+- Using Moose types with coercions in a positional params check would cause
+  invalid code to be generated. This could also happen with Type::Tiny if
+  either the type or a coercion could not be inlined.
+
+
+0.17     2016-11-04
+
+- When using positional parameters, parameters with a default are now
+  optional. For named parameters, this was already the case.
+
+
+0.16     2016-11-03
+
+- Moose and Specio types (and coercions) which provide variables to close over
+  when being inlined did not always compile properly. Most notable, this was
+  not being handled at all for Moose types, and not completely handled for
+  Specio coercions.
+
+
+0.15     2016-11-03
+
+- Previously, using a default with a positional parameter would result in an
+  error when compiling the validator subroutine. Defaults now work with
+  positional parameters. Implemented by Greg Oschwald. Based on PR #5.
+
+
+0.14     2016-11-02
+
+- Added a "named_to_list" option to support returning only the parameter
+  values from a named parameter validator rather than the key-value
+  pairs. Implemented by Greg Oschwald. Based on PR #4.
+
+- Errors from calls to validation_for() now use croak so as to show up at the
+  call site, rather than in the internals
+
+
+0.13     2016-09-16
+
+- Small fixes to make sure that you can pass both readonly and locked hashes
+  to both validation_for and the subroutine it creates for you. Locked hashes
+  work and readonly hashes sort of work on some Perls.
+
+- Added a new parameter, "name_is_optional". When this is true, the "name"
+  parameter is simply ignored when Sub::Util is not available, rather than
+  causing an exception.
+
+- Removed List::SomeUtils as a prereq.
+
+
+0.12     2016-08-16
+
+- Require Specio for tests instead of Type::Tiny. Type::Tiny does not work
+  with blead and the maintainer has not responded to bug reports for a while.
+
+
+0.11     2016-08-14
+
+- Use Sub::Util instead of Sub::Name as our optional sub-naming module, since
+  Sub::Util is part of core as of 5.22.
+
+
+0.10     2016-08-10
+
+- The parameters you pass when creating a validator are now validated.
+
+- The $e->message returned when a Moose type fails now includes the parameter
+  name or position. Adding these for other type systems will come in a future
+  release.
+
+
+0.09     2016-07-04
+
+- Really make Sub::Name optional.
+
+
+0.08     2016-07-03
+
+- Renamed from Params-CheckCompiler to Params-ValidationCompiler.
+
+- Made Sub::Name optional. If you try to set the name of a generation
+  validation sub without Sub::Name installed, you will get a fatal error.
+
+
+0.07     2016-06-18
+
+- Make the compiled sub for checking named params die if given a single object
+  as an argument, even if the object is implemented using a hashref. However,
+  if the object overloads hash dereferencing then the overloading is
+  used. Reported by Mark Fowler. GitHub #3.
+
+- Renamed compile() to validation_for(). The latter is not a very specific
+  name. Requested by Mark Fowler. GitHub #1.
+
+
+0.06     2016-06-18
+
+- Require Type::Tiny for tests. Reported by Slave Rezic. RT #115413.
+
+- Fix tests when Moose is installed but Devel::PartialDump is not. Reported by
+  Slave Rezic. RT #115413.
+
+
+0.05     2016-06-18
+
+- Removed all remaining uses of Moo.
+
+
+0.04     2016-06-17
+
+- Removed more modules from test prereqs that are only used in optional tests.
+
+- Replace Throwable with Exception::Class.
+
+
+0.03     2016-06-17
+
+- Remove Moose from test prereqs. This is only used for an optional test.
+
+- When generating the source for named params checking, sort the parameters so
+  that the order in which keys are checked is consistent.
+
+- You can now pass a name parameter when creating a check subroutine. This
+  will be used to name the generated subroutine using Sub::Name.
+
+
+0.02     2016-05-28
+
+- Add support for positional parameters.
+
+- Add support for type checking extra parameters.
+
+- Renamed allow_extra to slurpy.
+
+
+0.01     2016-05-24
+
+- First release upon an unsuspecting world.
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..4d76f7a
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,75 @@
+This is the Perl distribution Params-ValidationCompiler.
+
+Installing Params-ValidationCompiler is straightforward.
+
+## Installation with cpanm
+
+If you have cpanm, you only need one line:
+
+    % cpanm Params::ValidationCompiler
+
+If it does not have permission to install modules to the current perl, cpanm
+will automatically set up and install to a local::lib in your home directory.
+See the local::lib documentation (https://metacpan.org/pod/local::lib) for
+details on enabling it in your environment.
+
+## Installing with the CPAN shell
+
+Alternatively, if your CPAN shell is set up, you should just be able to do:
+
+    % cpan Params::ValidationCompiler
+
+## Manual installation
+
+As a last resort, you can manually install it. If you have not already
+downloaded the release tarball, you can find the download link on the module's
+MetaCPAN page: https://metacpan.org/pod/Params::ValidationCompiler
+
+Untar the tarball, install configure prerequisites (see below), then build it:
+
+    % perl Makefile.PL
+    % make && make test
+
+Then install it:
+
+    % make install
+
+On Windows platforms, you should use `dmake` or `nmake`, instead of `make`.
+
+If your perl is system-managed, you can create a local::lib in your home
+directory to install modules to. For details, see the local::lib documentation:
+https://metacpan.org/pod/local::lib
+
+The prerequisites of this distribution will also have to be installed manually. The
+prerequisites are listed in one of the files: `MYMETA.yml` or `MYMETA.json` generated
+by running the manual build process described above.
+
+## Configure Prerequisites
+
+This distribution requires other modules to be installed before this
+distribution's installer can be run.  They can be found under the
+"configure_requires" key of META.yml or the
+"{prereqs}{configure}{requires}" key of META.json.
+
+## Other Prerequisites
+
+This distribution may require additional modules to be installed after running
+Makefile.PL.
+Look for prerequisites in the following phases:
+
+* to run make, PHASE = build
+* to use the module code itself, PHASE = runtime
+* to run tests, PHASE = test
+
+They can all be found in the "PHASE_requires" key of MYMETA.yml or the
+"{prereqs}{PHASE}{requires}" key of MYMETA.json.
+
+## Documentation
+
+Params-ValidationCompiler documentation is available as POD.
+You can run `perldoc` from a shell to read the documentation:
+
+    % perldoc Params::ValidationCompiler
+
+For more information on installing Perl modules via CPAN, please see:
+https://www.cpan.org/modules/INSTALL.html
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..971eefa
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,207 @@
+This software is Copyright (c) 2016 - 2023 by Dave Rolsky.
+
+This is free software, licensed under:
+
+  The Artistic License 2.0 (GPL Compatible)
+
+                      The Artistic License 2.0
+
+           Copyright (c) 2000-2006, The Perl Foundation.
+
+     Everyone is permitted to copy and distribute verbatim copies
+      of this license document, but changing it is not allowed.
+
+Preamble
+
+This license establishes the terms under which a given free software
+Package may be copied, modified, distributed, and/or redistributed.
+The intent is that the Copyright Holder maintains some artistic
+control over the development of that Package while still keeping the
+Package available as open source and free software.
+
+You are always permitted to make arrangements wholly outside of this
+license directly with the Copyright Holder of a given Package.  If the
+terms of this license do not permit the full use that you propose to
+make of the Package, you should contact the Copyright Holder and seek
+a different licensing arrangement. 
+
+Definitions
+
+    "Copyright Holder" means the individual(s) or organization(s)
+    named in the copyright notice for the entire Package.
+
+    "Contributor" means any party that has contributed code or other
+    material to the Package, in accordance with the Copyright Holder's
+    procedures.
+
+    "You" and "your" means any person who would like to copy,
+    distribute, or modify the Package.
+
+    "Package" means the collection of files distributed by the
+    Copyright Holder, and derivatives of that collection and/or of
+    those files. A given Package may consist of either the Standard
+    Version, or a Modified Version.
+
+    "Distribute" means providing a copy of the Package or making it
+    accessible to anyone else, or in the case of a company or
+    organization, to others outside of your company or organization.
+
+    "Distributor Fee" means any fee that you charge for Distributing
+    this Package or providing support for this Package to another
+    party.  It does not mean licensing fees.
+
+    "Standard Version" refers to the Package if it has not been
+    modified, or has been modified only in ways explicitly requested
+    by the Copyright Holder.
+
+    "Modified Version" means the Package, if it has been changed, and
+    such changes were not explicitly requested by the Copyright
+    Holder. 
+
+    "Original License" means this Artistic License as Distributed with
+    the Standard Version of the Package, in its current version or as
+    it may be modified by The Perl Foundation in the future.
+
+    "Source" form means the source code, documentation source, and
+    configuration files for the Package.
+
+    "Compiled" form means the compiled bytecode, object code, binary,
+    or any other form resulting from mechanical transformation or
+    translation of the Source form.
+
+
+Permission for Use and Modification Without Distribution
+
+(1)  You are permitted to use the Standard Version and create and use
+Modified Versions for any purpose without restriction, provided that
+you do not Distribute the Modified Version.
+
+
+Permissions for Redistribution of the Standard Version
+
+(2)  You may Distribute verbatim copies of the Source form of the
+Standard Version of this Package in any medium without restriction,
+either gratis or for a Distributor Fee, provided that you duplicate
+all of the original copyright notices and associated disclaimers.  At
+your discretion, such verbatim copies may or may not include a
+Compiled form of the Package.
+
+(3)  You may apply any bug fixes, portability changes, and other
+modifications made available from the Copyright Holder.  The resulting
+Package will still be considered the Standard Version, and as such
+will be subject to the Original License.
+
+
+Distribution of Modified Versions of the Package as Source 
+
+(4)  You may Distribute your Modified Version as Source (either gratis
+or for a Distributor Fee, and with or without a Compiled form of the
+Modified Version) provided that you clearly document how it differs
+from the Standard Version, including, but not limited to, documenting
+any non-standard features, executables, or modules, and provided that
+you do at least ONE of the following:
+
+    (a)  make the Modified Version available to the Copyright Holder
+    of the Standard Version, under the Original License, so that the
+    Copyright Holder may include your modifications in the Standard
+    Version.
+
+    (b)  ensure that installation of your Modified Version does not
+    prevent the user installing or running the Standard Version. In
+    addition, the Modified Version must bear a name that is different
+    from the name of the Standard Version.
+
+    (c)  allow anyone who receives a copy of the Modified Version to
+    make the Source form of the Modified Version available to others
+    under
+               
+       (i)  the Original License or
+
+       (ii)  a license that permits the licensee to freely copy,
+       modify and redistribute the Modified Version using the same
+       licensing terms that apply to the copy that the licensee
+       received, and requires that the Source form of the Modified
+       Version, and of any works derived from it, be made freely
+       available in that license fees are prohibited but Distributor
+       Fees are allowed.
+
+
+Distribution of Compiled Forms of the Standard Version 
+or Modified Versions without the Source
+
+(5)  You may Distribute Compiled forms of the Standard Version without
+the Source, provided that you include complete instructions on how to
+get the Source of the Standard Version.  Such instructions must be
+valid at the time of your distribution.  If these instructions, at any
+time while you are carrying out such distribution, become invalid, you
+must provide new instructions on demand or cease further distribution.
+If you provide valid instructions or cease distribution within thirty
+days after you become aware that the instructions are invalid, then
+you do not forfeit any of your rights under this license.
+
+(6)  You may Distribute a Modified Version in Compiled form without
+the Source, provided that you comply with Section 4 with respect to
+the Source of the Modified Version.
+
+
+Aggregating or Linking the Package 
+
+(7)  You may aggregate the Package (either the Standard Version or
+Modified Version) with other packages and Distribute the resulting
+aggregation provided that you do not charge a licensing fee for the
+Package.  Distributor Fees are permitted, and licensing fees for other
+components in the aggregation are permitted. The terms of this license
+apply to the use and Distribution of the Standard or Modified Versions
+as included in the aggregation.
+
+(8) You are permitted to link Modified and Standard Versions with
+other works, to embed the Package in a larger work of your own, or to
+build stand-alone binary or bytecode versions of applications that
+include the Package, and Distribute the result without restriction,
+provided the result does not expose a direct interface to the Package.
+
+
+Items That are Not Considered Part of a Modified Version 
+
+(9) Works (including, but not limited to, modules and scripts) that
+merely extend or make use of the Package, do not, by themselves, cause
+the Package to be a Modified Version.  In addition, such works are not
+considered parts of the Package itself, and are not subject to the
+terms of this license.
+
+
+General Provisions
+
+(10)  Any use, modification, and distribution of the Standard or
+Modified Versions is governed by this Artistic License. By using,
+modifying or distributing the Package, you accept this license. Do not
+use, modify, or distribute the Package, if you do not accept this
+license.
+
+(11)  If your Modified Version has been derived from a Modified
+Version made by someone other than you, you are nevertheless required
+to ensure that your Modified Version complies with the requirements of
+this license.
+
+(12)  This license does not grant you the right to use any trademark,
+service mark, tradename, or logo of the Copyright Holder.
+
+(13)  This license includes the non-exclusive, worldwide,
+free-of-charge patent license to make, have made, use, offer to sell,
+sell, import and otherwise transfer the Package with respect to any
+patent claims licensable by the Copyright Holder that are necessarily
+infringed by the Package. If you institute patent litigation
+(including a cross-claim or counterclaim) against any party alleging
+that the Package constitutes direct or contributory patent
+infringement, then this Artistic License to you shall terminate on the
+date that such litigation is filed.
+
+(14)  Disclaimer of Warranty:
+THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
+IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
+NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
+LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/MANIFEST b/MANIFEST
new file mode 100644 (file)
index 0000000..f4a9652
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,61 @@
+# This file was automatically generated by Dist::Zilla::Plugin::Manifest v6.029.
+CODE_OF_CONDUCT.md
+CONTRIBUTING.md
+Changes
+INSTALL
+LICENSE
+MANIFEST
+META.json
+META.yml
+Makefile.PL
+README.md
+azure-pipelines.yml
+cpanfile
+dev-bin/install-xt-tools.sh
+dist.ini
+eg/bench-named.pl
+eg/bench-pos.pl
+git/hooks/pre-commit.sh
+git/setup.pl
+lib/Params/ValidationCompiler.pm
+lib/Params/ValidationCompiler/Compiler.pm
+lib/Params/ValidationCompiler/Exceptions.pm
+perlcriticrc
+perltidyrc
+precious.toml
+t/00-report-prereqs.dd
+t/00-report-prereqs.t
+t/default.t
+t/exceptions.t
+t/moose.t
+t/name-fails.t
+t/name.t
+t/named/args-check.t
+t/named/const-hash.t
+t/named/locked-hash.t
+t/named/required.t
+t/named/return-object.t
+t/named/slurpy.t
+t/pairs-to-value-list.t
+t/positional/default.t
+t/positional/required.t
+t/positional/slurpy.t
+t/self-check.t
+t/source_for.t
+t/specio.t
+t/type-tiny.t
+test-matrix.als
+xt/author/00-compile.t
+xt/author/eol.t
+xt/author/matrix.t
+xt/author/mojibake.t
+xt/author/no-tabs.t
+xt/author/pod-coverage.t
+xt/author/pod-spell.t
+xt/author/pod-syntax.t
+xt/author/portability.t
+xt/author/precious.t
+xt/author/synopsis.t
+xt/author/test-version.t
+xt/release/cpan-changes.t
+xt/release/meta-json.t
diff --git a/META.json b/META.json
new file mode 100644 (file)
index 0000000..c8e8a70
--- /dev/null
+++ b/META.json
@@ -0,0 +1,1138 @@
+{
+   "abstract" : "Build an optimized subroutine parameter validator once, use it forever",
+   "author" : [
+      "Dave Rolsky <autarch@urth.org>"
+   ],
+   "dynamic_config" : 0,
+   "generated_by" : "Dist::Zilla version 6.029, CPAN::Meta::Converter version 2.150010",
+   "license" : [
+      "artistic_2"
+   ],
+   "meta-spec" : {
+      "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
+      "version" : 2
+   },
+   "name" : "Params-ValidationCompiler",
+   "prereqs" : {
+      "configure" : {
+         "requires" : {
+            "ExtUtils::MakeMaker" : "0"
+         }
+      },
+      "develop" : {
+         "requires" : {
+            "Capture::Tiny" : "0",
+            "Class::XSAccessor" : "1.17",
+            "Const::Fast" : "0.014",
+            "Encode" : "0",
+            "File::Spec" : "0",
+            "FindBin" : "0",
+            "Hash::Merge" : "0",
+            "Hash::Util" : "0",
+            "IO::Handle" : "0",
+            "IPC::Open3" : "0",
+            "List::AllUtils" : "0",
+            "Moose" : "2.0000",
+            "Perl::Critic" : "1.138",
+            "Perl::Critic::Moose" : "1.05",
+            "Perl::Tidy" : "20210111",
+            "Pod::Checker" : "1.74",
+            "Pod::Coverage::TrustPod" : "0",
+            "Pod::Tidy" : "0.10",
+            "Pod::Wordlist" : "0",
+            "Set::Scalar" : "0",
+            "Specio" : "0.14",
+            "Sub::Util" : "1.40",
+            "Test2::Bundle::Extended" : "0",
+            "Test2::Plugin::NoWarnings" : "0",
+            "Test2::Require::Perl" : "0",
+            "Test::CPAN::Changes" : "0.19",
+            "Test::CPAN::Meta::JSON" : "0.16",
+            "Test::EOL" : "0",
+            "Test::Mojibake" : "0",
+            "Test::More" : "0.96",
+            "Test::NoTabs" : "0",
+            "Test::Pod" : "1.41",
+            "Test::Pod::Coverage" : "1.08",
+            "Test::Portability::Files" : "0",
+            "Test::Spelling" : "0.12",
+            "Test::Synopsis" : "0",
+            "Test::Version" : "2.05",
+            "Type::Tiny" : "0",
+            "Type::Utils" : "0",
+            "perl" : "5.006"
+         }
+      },
+      "runtime" : {
+         "recommends" : {
+            "Class::XSAccessor" : "1.17",
+            "Sub::Util" : "1.40"
+         },
+         "requires" : {
+            "B" : "0",
+            "Carp" : "0",
+            "Eval::Closure" : "0",
+            "Exception::Class" : "0",
+            "Exporter" : "0",
+            "List::Util" : "1.29",
+            "Scalar::Util" : "0",
+            "overload" : "0",
+            "strict" : "0",
+            "warnings" : "0"
+         }
+      },
+      "test" : {
+         "recommends" : {
+            "CPAN::Meta" : "2.120900"
+         },
+         "requires" : {
+            "ExtUtils::MakeMaker" : "0",
+            "File::Spec" : "0",
+            "Hash::Util" : "0",
+            "Specio" : "0.14",
+            "Test2::Plugin::NoWarnings" : "0",
+            "Test2::Require::Module" : "0",
+            "Test2::V0" : "0",
+            "Test::More" : "1.302015",
+            "Test::Without::Module" : "0"
+         }
+      }
+   },
+   "provides" : {
+      "Params::ValidationCompiler" : {
+         "file" : "lib/Params/ValidationCompiler.pm",
+         "version" : "0.31"
+      },
+      "Params::ValidationCompiler::Compiler" : {
+         "file" : "lib/Params/ValidationCompiler/Compiler.pm",
+         "version" : "0.31"
+      },
+      "Params::ValidationCompiler::Exceptions" : {
+         "file" : "lib/Params/ValidationCompiler/Exceptions.pm",
+         "version" : "0.31"
+      }
+   },
+   "release_status" : "stable",
+   "resources" : {
+      "bugtracker" : {
+         "web" : "https://github.com/houseabsolute/Params-ValidationCompiler/issues"
+      },
+      "homepage" : "https://metacpan.org/release/Params-ValidationCompiler",
+      "repository" : {
+         "type" : "git",
+         "url" : "git://github.com/houseabsolute/Params-ValidationCompiler.git",
+         "web" : "https://github.com/houseabsolute/Params-ValidationCompiler"
+      }
+   },
+   "version" : "0.31",
+   "x_Dist_Zilla" : {
+      "perl" : {
+         "version" : "5.032001"
+      },
+      "plugins" : [
+         {
+            "class" : "Dist::Zilla::Plugin::DROLSKY::BundleAuthordep",
+            "name" : "@DROLSKY/DROLSKY::BundleAuthordep",
+            "version" : "1.22"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Git::GatherDir",
+            "config" : {
+               "Dist::Zilla::Plugin::GatherDir" : {
+                  "exclude_filename" : [
+                     "CODE_OF_CONDUCT.md",
+                     "CONTRIBUTING.md",
+                     "LICENSE",
+                     "Makefile.PL",
+                     "README.md",
+                     "cpanfile"
+                  ],
+                  "exclude_match" : [],
+                  "follow_symlinks" : 0,
+                  "include_dotfiles" : 0,
+                  "prefix" : "",
+                  "prune_directory" : [],
+                  "root" : "."
+               },
+               "Dist::Zilla::Plugin::Git::GatherDir" : {
+                  "include_untracked" : 0
+               }
+            },
+            "name" : "@DROLSKY/Git::GatherDir",
+            "version" : "2.048"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::ManifestSkip",
+            "name" : "@DROLSKY/ManifestSkip",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::License",
+            "name" : "@DROLSKY/License",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::ExecDir",
+            "name" : "@DROLSKY/ExecDir",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::ShareDir",
+            "name" : "@DROLSKY/ShareDir",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Manifest",
+            "name" : "@DROLSKY/Manifest",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::CheckVersionIncrement",
+            "name" : "@DROLSKY/CheckVersionIncrement",
+            "version" : "0.121750"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::TestRelease",
+            "name" : "@DROLSKY/TestRelease",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::ConfirmRelease",
+            "name" : "@DROLSKY/ConfirmRelease",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::UploadToCPAN",
+            "name" : "@DROLSKY/UploadToCPAN",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::VersionFromMainModule",
+            "config" : {
+               "Dist::Zilla::Role::ModuleMetadata" : {
+                  "Module::Metadata" : "1.000037",
+                  "version" : "0.006"
+               }
+            },
+            "name" : "@DROLSKY/VersionFromMainModule",
+            "version" : "0.04"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Authority",
+            "name" : "@DROLSKY/Authority",
+            "version" : "1.009"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::AutoPrereqs",
+            "name" : "@DROLSKY/AutoPrereqs",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::CopyFilesFromBuild",
+            "name" : "@DROLSKY/CopyFilesFromBuild",
+            "version" : "0.170880"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::GitHub::Meta",
+            "name" : "@DROLSKY/GitHub::Meta",
+            "version" : "0.48"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::GitHub::Update",
+            "config" : {
+               "Dist::Zilla::Plugin::GitHub::Update" : {
+                  "metacpan" : 1
+               }
+            },
+            "name" : "@DROLSKY/GitHub::Update",
+            "version" : "0.48"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::MetaResources",
+            "name" : "@DROLSKY/MetaResources",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::MetaProvides::Package",
+            "config" : {
+               "Dist::Zilla::Plugin::MetaProvides::Package" : {
+                  "finder_objects" : [
+                     {
+                        "class" : "Dist::Zilla::Plugin::FinderCode",
+                        "name" : "@DROLSKY/MetaProvides::Package/AUTOVIV/:InstallModulesPM",
+                        "version" : "6.029"
+                     }
+                  ],
+                  "include_underscores" : 0
+               },
+               "Dist::Zilla::Role::MetaProvider::Provider" : {
+                  "$Dist::Zilla::Role::MetaProvider::Provider::VERSION" : "2.002004",
+                  "inherit_missing" : 1,
+                  "inherit_version" : 1,
+                  "meta_noindex" : 1
+               },
+               "Dist::Zilla::Role::ModuleMetadata" : {
+                  "Module::Metadata" : "1.000037",
+                  "version" : "0.006"
+               }
+            },
+            "name" : "@DROLSKY/MetaProvides::Package",
+            "version" : "2.004003"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Meta::Contributors",
+            "name" : "@DROLSKY/Meta::Contributors",
+            "version" : "0.003"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::MetaConfig",
+            "name" : "@DROLSKY/MetaConfig",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::MetaJSON",
+            "name" : "@DROLSKY/MetaJSON",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::MetaYAML",
+            "name" : "@DROLSKY/MetaYAML",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::NextRelease",
+            "name" : "@DROLSKY/NextRelease",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Prereqs",
+            "config" : {
+               "Dist::Zilla::Plugin::Prereqs" : {
+                  "phase" : "test",
+                  "type" : "requires"
+               }
+            },
+            "name" : "@DROLSKY/Test::More with Test2",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Prereqs",
+            "config" : {
+               "Dist::Zilla::Plugin::Prereqs" : {
+                  "phase" : "develop",
+                  "type" : "requires"
+               }
+            },
+            "name" : "@DROLSKY/Tools for use with precious",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Prereqs",
+            "config" : {
+               "Dist::Zilla::Plugin::Prereqs" : {
+                  "phase" : "develop",
+                  "type" : "requires"
+               }
+            },
+            "name" : "@DROLSKY/Test::Version which fixes https://github.com/plicease/Test-Version/issues/7",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::PromptIfStale",
+            "config" : {
+               "Dist::Zilla::Plugin::PromptIfStale" : {
+                  "check_all_plugins" : 0,
+                  "check_all_prereqs" : 0,
+                  "modules" : [
+                     "Dist::Zilla::PluginBundle::DROLSKY"
+                  ],
+                  "phase" : "build",
+                  "run_under_travis" : 0,
+                  "skip" : []
+               }
+            },
+            "name" : "@DROLSKY/Dist::Zilla::PluginBundle::DROLSKY",
+            "version" : "0.057"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::PromptIfStale",
+            "config" : {
+               "Dist::Zilla::Plugin::PromptIfStale" : {
+                  "check_all_plugins" : 1,
+                  "check_all_prereqs" : 1,
+                  "modules" : [],
+                  "phase" : "release",
+                  "run_under_travis" : 0,
+                  "skip" : [
+                     "Dist::Zilla::Plugin::DROLSKY::BundleAuthordep",
+                     "Dist::Zilla::Plugin::DROLSKY::Contributors",
+                     "Dist::Zilla::Plugin::DROLSKY::Git::CheckFor::CorrectBranch",
+                     "Dist::Zilla::Plugin::DROLSKY::License",
+                     "Dist::Zilla::Plugin::DROLSKY::MakeMaker",
+                     "Dist::Zilla::Plugin::DROLSKY::PerlLinterConfigFiles",
+                     "Dist::Zilla::Plugin::DROLSKY::Precious",
+                     "Dist::Zilla::Plugin::DROLSKY::Test::Precious",
+                     "Dist::Zilla::Plugin::DROLSKY::WeaverConfig",
+                     "Pod::Weaver::PluginBundle::DROLSKY"
+                  ]
+               }
+            },
+            "name" : "@DROLSKY/PromptIfStale",
+            "version" : "0.057"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Test::Pod::Coverage::Configurable",
+            "name" : "@DROLSKY/Test::Pod::Coverage::Configurable",
+            "version" : "0.07"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Test::PodSpelling",
+            "config" : {
+               "Dist::Zilla::Plugin::Test::PodSpelling" : {
+                  "directories" : [
+                     "bin",
+                     "lib"
+                  ],
+                  "spell_cmd" : "",
+                  "stopwords" : [
+                     "DROLSKY",
+                     "DROLSKY's",
+                     "PayPal",
+                     "Rolsky",
+                     "Rolsky's",
+                     "drolsky",
+                     "getter",
+                     "params",
+                     "slurpy",
+                     "uncompromised",
+                     "validator",
+                     "validators"
+                  ],
+                  "wordlist" : "Pod::Wordlist"
+               }
+            },
+            "name" : "@DROLSKY/Test::PodSpelling",
+            "version" : "2.007005"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::PodSyntaxTests",
+            "name" : "@DROLSKY/PodSyntaxTests",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::MojibakeTests",
+            "name" : "@DROLSKY/MojibakeTests",
+            "version" : "0.8"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::RunExtraTests",
+            "config" : {
+               "Dist::Zilla::Role::TestRunner" : {
+                  "default_jobs" : "12"
+               }
+            },
+            "name" : "@DROLSKY/RunExtraTests",
+            "version" : "0.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Test::CPAN::Changes",
+            "config" : {
+               "Dist::Zilla::Plugin::Test::CPAN::Changes" : {
+                  "changelog" : "Changes"
+               }
+            },
+            "name" : "@DROLSKY/Test::CPAN::Changes",
+            "version" : "0.012"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Test::CPAN::Meta::JSON",
+            "name" : "@DROLSKY/Test::CPAN::Meta::JSON",
+            "version" : "0.004"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Test::EOL",
+            "config" : {
+               "Dist::Zilla::Plugin::Test::EOL" : {
+                  "filename" : "xt/author/eol.t",
+                  "finder" : [
+                     ":ExecFiles",
+                     ":InstallModules",
+                     ":TestFiles"
+                  ],
+                  "trailing_whitespace" : 1
+               }
+            },
+            "name" : "@DROLSKY/Test::EOL",
+            "version" : "0.19"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Test::NoTabs",
+            "config" : {
+               "Dist::Zilla::Plugin::Test::NoTabs" : {
+                  "filename" : "xt/author/no-tabs.t",
+                  "finder" : [
+                     ":InstallModules",
+                     ":ExecFiles",
+                     ":TestFiles"
+                  ]
+               }
+            },
+            "name" : "@DROLSKY/Test::NoTabs",
+            "version" : "0.15"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Test::Portability",
+            "config" : {
+               "Dist::Zilla::Plugin::Test::Portability" : {
+                  "options" : ""
+               }
+            },
+            "name" : "@DROLSKY/Test::Portability",
+            "version" : "2.001001"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Test::Synopsis",
+            "name" : "@DROLSKY/Test::Synopsis",
+            "version" : "2.000007"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Test::Compile",
+            "config" : {
+               "Dist::Zilla::Plugin::Test::Compile" : {
+                  "bail_out_on_fail" : 0,
+                  "fail_on_warning" : "author",
+                  "fake_home" : 0,
+                  "filename" : "xt/author/00-compile.t",
+                  "module_finder" : [
+                     ":InstallModules"
+                  ],
+                  "needs_display" : 0,
+                  "phase" : "develop",
+                  "script_finder" : [
+                     ":PerlExecFiles"
+                  ],
+                  "skips" : [],
+                  "switch" : []
+               }
+            },
+            "name" : "@DROLSKY/Test::Compile",
+            "version" : "2.058"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Test::ReportPrereqs",
+            "name" : "@DROLSKY/Test::ReportPrereqs",
+            "version" : "0.028"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Test::Version",
+            "name" : "@DROLSKY/Test::Version",
+            "version" : "1.09"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::DROLSKY::Test::Precious",
+            "name" : "@DROLSKY/DROLSKY::Test::Precious",
+            "version" : "1.22"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::DROLSKY::Contributors",
+            "name" : "@DROLSKY/DROLSKY::Contributors",
+            "version" : "1.22"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Git::Contributors",
+            "config" : {
+               "Dist::Zilla::Plugin::Git::Contributors" : {
+                  "git_version" : "2.39.0",
+                  "include_authors" : 0,
+                  "include_releaser" : 1,
+                  "order_by" : "name",
+                  "paths" : []
+               }
+            },
+            "name" : "@DROLSKY/Git::Contributors",
+            "version" : "0.036"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::SurgicalPodWeaver",
+            "config" : {
+               "Dist::Zilla::Plugin::PodWeaver" : {
+                  "config_plugins" : [
+                     "@DROLSKY"
+                  ],
+                  "finder" : [
+                     ":InstallModules",
+                     ":ExecFiles"
+                  ],
+                  "plugins" : [
+                     {
+                        "class" : "Pod::Weaver::Plugin::EnsurePod5",
+                        "name" : "@CorePrep/EnsurePod5",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Plugin::H1Nester",
+                        "name" : "@CorePrep/H1Nester",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Plugin::SingleEncoding",
+                        "name" : "@DROLSKY/SingleEncoding",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Plugin::Transformer",
+                        "name" : "@DROLSKY/List",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Plugin::Transformer",
+                        "name" : "@DROLSKY/Verbatim",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::Region",
+                        "name" : "@DROLSKY/header",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::Name",
+                        "name" : "@DROLSKY/Name",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::Version",
+                        "name" : "@DROLSKY/Version",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::Region",
+                        "name" : "@DROLSKY/prelude",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::Generic",
+                        "name" : "SYNOPSIS",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::Generic",
+                        "name" : "DESCRIPTION",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::Generic",
+                        "name" : "OVERVIEW",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::Collect",
+                        "name" : "ATTRIBUTES",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::Collect",
+                        "name" : "METHODS",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::Collect",
+                        "name" : "FUNCTIONS",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::Collect",
+                        "name" : "TYPES",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::Leftovers",
+                        "name" : "@DROLSKY/Leftovers",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::Region",
+                        "name" : "@DROLSKY/postlude",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::GenerateSection",
+                        "name" : "@DROLSKY/generate SUPPORT",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::AllowOverride",
+                        "name" : "@DROLSKY/allow override SUPPORT",
+                        "version" : "0.05"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::GenerateSection",
+                        "name" : "@DROLSKY/generate SOURCE",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::GenerateSection",
+                        "name" : "@DROLSKY/generate DONATIONS",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::Authors",
+                        "name" : "@DROLSKY/Authors",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::Contributors",
+                        "name" : "@DROLSKY/Contributors",
+                        "version" : "0.009"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::Legal",
+                        "name" : "@DROLSKY/Legal",
+                        "version" : "4.018"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::AllowOverride",
+                        "name" : "@DROLSKY/allow override Legal",
+                        "version" : "0.05"
+                     },
+                     {
+                        "class" : "Pod::Weaver::Section::Region",
+                        "name" : "@DROLSKY/footer",
+                        "version" : "4.018"
+                     }
+                  ]
+               }
+            },
+            "name" : "@DROLSKY/SurgicalPodWeaver",
+            "version" : "0.0023"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::DROLSKY::WeaverConfig",
+            "name" : "@DROLSKY/DROLSKY::WeaverConfig",
+            "version" : "1.22"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::ReadmeAnyFromPod",
+            "config" : {
+               "Dist::Zilla::Role::FileWatcher" : {
+                  "version" : "0.006"
+               }
+            },
+            "name" : "@DROLSKY/README.md in build",
+            "version" : "0.163250"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::GenerateFile::FromShareDir",
+            "config" : {
+               "Dist::Zilla::Plugin::GenerateFile::FromShareDir" : {
+                  "destination_filename" : "CONTRIBUTING.md",
+                  "dist" : "Dist-Zilla-PluginBundle-DROLSKY",
+                  "encoding" : "UTF-8",
+                  "has_xs" : 0,
+                  "location" : "build",
+                  "source_filename" : "CONTRIBUTING.md"
+               },
+               "Dist::Zilla::Role::RepoFileInjector" : {
+                  "allow_overwrite" : 1,
+                  "repo_root" : ".",
+                  "version" : "0.009"
+               }
+            },
+            "name" : "@DROLSKY/Generate CONTRIBUTING.md",
+            "version" : "0.015"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::GenerateFile::FromShareDir",
+            "config" : {
+               "Dist::Zilla::Plugin::GenerateFile::FromShareDir" : {
+                  "destination_filename" : "CODE_OF_CONDUCT.md",
+                  "dist" : "Dist-Zilla-PluginBundle-DROLSKY",
+                  "encoding" : "UTF-8",
+                  "has_xs" : 0,
+                  "location" : "build",
+                  "source_filename" : "CODE_OF_CONDUCT.md"
+               },
+               "Dist::Zilla::Role::RepoFileInjector" : {
+                  "allow_overwrite" : 1,
+                  "repo_root" : ".",
+                  "version" : "0.009"
+               }
+            },
+            "name" : "@DROLSKY/Generate CODE_OF_CONDUCT.md",
+            "version" : "0.015"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::InstallGuide",
+            "config" : {
+               "Dist::Zilla::Role::ModuleMetadata" : {
+                  "Module::Metadata" : "1.000037",
+                  "version" : "0.006"
+               }
+            },
+            "name" : "@DROLSKY/InstallGuide",
+            "version" : "1.200014"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::CPANFile",
+            "name" : "@DROLSKY/CPANFile",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::DROLSKY::License",
+            "name" : "@DROLSKY/DROLSKY::License",
+            "version" : "1.22"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::CheckStrictVersion",
+            "name" : "@DROLSKY/CheckStrictVersion",
+            "version" : "0.001"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::CheckSelfDependency",
+            "config" : {
+               "Dist::Zilla::Plugin::CheckSelfDependency" : {
+                  "finder" : [
+                     ":InstallModules"
+                  ]
+               },
+               "Dist::Zilla::Role::ModuleMetadata" : {
+                  "Module::Metadata" : "1.000037",
+                  "version" : "0.006"
+               }
+            },
+            "name" : "@DROLSKY/CheckSelfDependency",
+            "version" : "0.011"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::CheckPrereqsIndexed",
+            "name" : "@DROLSKY/CheckPrereqsIndexed",
+            "version" : "0.021"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::DROLSKY::Git::CheckFor::CorrectBranch",
+            "config" : {
+               "Dist::Zilla::Role::Git::Repo" : {
+                  "git_version" : "2.39.0",
+                  "repo_root" : "."
+               }
+            },
+            "name" : "@DROLSKY/DROLSKY::Git::CheckFor::CorrectBranch",
+            "version" : "1.22"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::EnsureChangesHasContent",
+            "name" : "@DROLSKY/EnsureChangesHasContent",
+            "version" : "0.02"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Git::CheckFor::MergeConflicts",
+            "config" : {
+               "Dist::Zilla::Role::Git::Repo" : {
+                  "git_version" : "2.39.0",
+                  "repo_root" : "."
+               }
+            },
+            "name" : "@DROLSKY/Git::CheckFor::MergeConflicts",
+            "version" : "0.014"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::DROLSKY::PerlLinterConfigFiles",
+            "name" : "@DROLSKY/DROLSKY::PerlLinterConfigFiles",
+            "version" : "1.22"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::DROLSKY::DevTools",
+            "name" : "@DROLSKY/DROLSKY::DevTools",
+            "version" : "1.22"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::DROLSKY::Precious",
+            "name" : "@DROLSKY/DROLSKY::Precious",
+            "version" : "1.22"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Git::Check",
+            "config" : {
+               "Dist::Zilla::Plugin::Git::Check" : {
+                  "untracked_files" : "die"
+               },
+               "Dist::Zilla::Role::Git::DirtyFiles" : {
+                  "allow_dirty" : [
+                     "CODE_OF_CONDUCT.md",
+                     "CONTRIBUTING.md",
+                     "Changes",
+                     "LICENSE",
+                     "Makefile.PL",
+                     "README.md",
+                     "cpanfile",
+                     "precious.toml"
+                  ],
+                  "allow_dirty_match" : [],
+                  "changelog" : "Changes"
+               },
+               "Dist::Zilla::Role::Git::Repo" : {
+                  "git_version" : "2.39.0",
+                  "repo_root" : "."
+               }
+            },
+            "name" : "@DROLSKY/Git::Check",
+            "version" : "2.048"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Git::Commit",
+            "config" : {
+               "Dist::Zilla::Plugin::Git::Commit" : {
+                  "add_files_in" : [],
+                  "commit_msg" : "v%V%n%n%c",
+                  "signoff" : 0
+               },
+               "Dist::Zilla::Role::Git::DirtyFiles" : {
+                  "allow_dirty" : [
+                     "CODE_OF_CONDUCT.md",
+                     "CONTRIBUTING.md",
+                     "Changes",
+                     "LICENSE",
+                     "Makefile.PL",
+                     "README.md",
+                     "cpanfile",
+                     "precious.toml"
+                  ],
+                  "allow_dirty_match" : [],
+                  "changelog" : "Changes"
+               },
+               "Dist::Zilla::Role::Git::Repo" : {
+                  "git_version" : "2.39.0",
+                  "repo_root" : "."
+               },
+               "Dist::Zilla::Role::Git::StringFormatter" : {
+                  "time_zone" : "local"
+               }
+            },
+            "name" : "@DROLSKY/Commit generated files",
+            "version" : "2.048"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Git::Tag",
+            "config" : {
+               "Dist::Zilla::Plugin::Git::Tag" : {
+                  "branch" : null,
+                  "changelog" : "Changes",
+                  "signed" : 0,
+                  "tag" : "v0.31",
+                  "tag_format" : "v%V",
+                  "tag_message" : "v%V"
+               },
+               "Dist::Zilla::Role::Git::Repo" : {
+                  "git_version" : "2.39.0",
+                  "repo_root" : "."
+               },
+               "Dist::Zilla::Role::Git::StringFormatter" : {
+                  "time_zone" : "local"
+               }
+            },
+            "name" : "@DROLSKY/Git::Tag",
+            "version" : "2.048"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Git::Push",
+            "config" : {
+               "Dist::Zilla::Plugin::Git::Push" : {
+                  "push_to" : [
+                     "origin"
+                  ],
+                  "remotes_must_exist" : 1
+               },
+               "Dist::Zilla::Role::Git::Repo" : {
+                  "git_version" : "2.39.0",
+                  "repo_root" : "."
+               }
+            },
+            "name" : "@DROLSKY/Git::Push",
+            "version" : "2.048"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::BumpVersionAfterRelease",
+            "config" : {
+               "Dist::Zilla::Plugin::BumpVersionAfterRelease" : {
+                  "finders" : [
+                     ":ExecFiles",
+                     ":InstallModules"
+                  ],
+                  "global" : 0,
+                  "munge_makefile_pl" : 1
+               }
+            },
+            "name" : "@DROLSKY/BumpVersionAfterRelease",
+            "version" : "0.018"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Git::Commit",
+            "config" : {
+               "Dist::Zilla::Plugin::Git::Commit" : {
+                  "add_files_in" : [],
+                  "commit_msg" : "Bump version after release",
+                  "signoff" : 0
+               },
+               "Dist::Zilla::Role::Git::DirtyFiles" : {
+                  "allow_dirty" : [
+                     "Changes",
+                     "dist.ini"
+                  ],
+                  "allow_dirty_match" : [
+                     "(?^:.+)"
+                  ],
+                  "changelog" : "Changes"
+               },
+               "Dist::Zilla::Role::Git::Repo" : {
+                  "git_version" : "2.39.0",
+                  "repo_root" : "."
+               },
+               "Dist::Zilla::Role::Git::StringFormatter" : {
+                  "time_zone" : "local"
+               }
+            },
+            "name" : "@DROLSKY/Commit version bump",
+            "version" : "2.048"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Git::Push",
+            "config" : {
+               "Dist::Zilla::Plugin::Git::Push" : {
+                  "push_to" : [
+                     "origin"
+                  ],
+                  "remotes_must_exist" : 1
+               },
+               "Dist::Zilla::Role::Git::Repo" : {
+                  "git_version" : "2.39.0",
+                  "repo_root" : "."
+               }
+            },
+            "name" : "@DROLSKY/Push version bump",
+            "version" : "2.048"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::DROLSKY::MakeMaker",
+            "config" : {
+               "Dist::Zilla::Plugin::MakeMaker" : {
+                  "make_path" : "make",
+                  "version" : "6.029"
+               },
+               "Dist::Zilla::Plugin::MakeMaker::Awesome" : {
+                  "version" : "0.49"
+               },
+               "Dist::Zilla::Role::TestRunner" : {
+                  "default_jobs" : "12",
+                  "version" : "6.029"
+               }
+            },
+            "name" : "@DROLSKY/DROLSKY::MakeMaker",
+            "version" : "1.22"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Prereqs",
+            "config" : {
+               "Dist::Zilla::Plugin::Prereqs" : {
+                  "phase" : "develop",
+                  "type" : "requires"
+               }
+            },
+            "name" : "DevelopRequires",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Prereqs",
+            "config" : {
+               "Dist::Zilla::Plugin::Prereqs" : {
+                  "phase" : "runtime",
+                  "type" : "recommends"
+               }
+            },
+            "name" : "Recommends",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::Prereqs",
+            "config" : {
+               "Dist::Zilla::Plugin::Prereqs" : {
+                  "phase" : "test",
+                  "type" : "requires"
+               }
+            },
+            "name" : "TestRequires",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::FinderCode",
+            "name" : ":InstallModules",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::FinderCode",
+            "name" : ":IncModules",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::FinderCode",
+            "name" : ":TestFiles",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::FinderCode",
+            "name" : ":ExtraTestFiles",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::FinderCode",
+            "name" : ":ExecFiles",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::FinderCode",
+            "name" : ":PerlExecFiles",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::FinderCode",
+            "name" : ":ShareFiles",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::FinderCode",
+            "name" : ":MainModule",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::FinderCode",
+            "name" : ":AllFiles",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::FinderCode",
+            "name" : ":NoFiles",
+            "version" : "6.029"
+         },
+         {
+            "class" : "Dist::Zilla::Plugin::FinderCode",
+            "name" : "@DROLSKY/MetaProvides::Package/AUTOVIV/:InstallModulesPM",
+            "version" : "6.029"
+         }
+      ],
+      "zilla" : {
+         "class" : "Dist::Zilla::Dist::Builder",
+         "config" : {
+            "is_trial" : 0
+         },
+         "version" : "6.029"
+      }
+   },
+   "x_authority" : "cpan:DROLSKY",
+   "x_contributors" : [
+      "Gregory Oschwald <goschwald@maxmind.com>",
+      "Gregory Oschwald <oschwald@gmail.com>",
+      "Tomasz Konojacki <me@xenu.pl>"
+   ],
+   "x_generated_by_perl" : "v5.32.1",
+   "x_serialization_backend" : "Cpanel::JSON::XS version 4.26",
+   "x_spdx_expression" : "Artistic-2.0"
+}
+
diff --git a/META.yml b/META.yml
new file mode 100644 (file)
index 0000000..2500d2e
--- /dev/null
+++ b/META.yml
@@ -0,0 +1,829 @@
+---
+abstract: 'Build an optimized subroutine parameter validator once, use it forever'
+author:
+  - 'Dave Rolsky <autarch@urth.org>'
+build_requires:
+  ExtUtils::MakeMaker: '0'
+  File::Spec: '0'
+  Hash::Util: '0'
+  Specio: '0.14'
+  Test2::Plugin::NoWarnings: '0'
+  Test2::Require::Module: '0'
+  Test2::V0: '0'
+  Test::More: '1.302015'
+  Test::Without::Module: '0'
+configure_requires:
+  ExtUtils::MakeMaker: '0'
+dynamic_config: 0
+generated_by: 'Dist::Zilla version 6.029, 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: Params-ValidationCompiler
+provides:
+  Params::ValidationCompiler:
+    file: lib/Params/ValidationCompiler.pm
+    version: '0.31'
+  Params::ValidationCompiler::Compiler:
+    file: lib/Params/ValidationCompiler/Compiler.pm
+    version: '0.31'
+  Params::ValidationCompiler::Exceptions:
+    file: lib/Params/ValidationCompiler/Exceptions.pm
+    version: '0.31'
+recommends:
+  Class::XSAccessor: '1.17'
+  Sub::Util: '1.40'
+requires:
+  B: '0'
+  Carp: '0'
+  Eval::Closure: '0'
+  Exception::Class: '0'
+  Exporter: '0'
+  List::Util: '1.29'
+  Scalar::Util: '0'
+  overload: '0'
+  strict: '0'
+  warnings: '0'
+resources:
+  bugtracker: https://github.com/houseabsolute/Params-ValidationCompiler/issues
+  homepage: https://metacpan.org/release/Params-ValidationCompiler
+  repository: git://github.com/houseabsolute/Params-ValidationCompiler.git
+version: '0.31'
+x_Dist_Zilla:
+  perl:
+    version: '5.032001'
+  plugins:
+    -
+      class: Dist::Zilla::Plugin::DROLSKY::BundleAuthordep
+      name: '@DROLSKY/DROLSKY::BundleAuthordep'
+      version: '1.22'
+    -
+      class: Dist::Zilla::Plugin::Git::GatherDir
+      config:
+        Dist::Zilla::Plugin::GatherDir:
+          exclude_filename:
+            - CODE_OF_CONDUCT.md
+            - CONTRIBUTING.md
+            - LICENSE
+            - Makefile.PL
+            - README.md
+            - cpanfile
+          exclude_match: []
+          follow_symlinks: 0
+          include_dotfiles: 0
+          prefix: ''
+          prune_directory: []
+          root: .
+        Dist::Zilla::Plugin::Git::GatherDir:
+          include_untracked: 0
+      name: '@DROLSKY/Git::GatherDir'
+      version: '2.048'
+    -
+      class: Dist::Zilla::Plugin::ManifestSkip
+      name: '@DROLSKY/ManifestSkip'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::License
+      name: '@DROLSKY/License'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::ExecDir
+      name: '@DROLSKY/ExecDir'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::ShareDir
+      name: '@DROLSKY/ShareDir'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::Manifest
+      name: '@DROLSKY/Manifest'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::CheckVersionIncrement
+      name: '@DROLSKY/CheckVersionIncrement'
+      version: '0.121750'
+    -
+      class: Dist::Zilla::Plugin::TestRelease
+      name: '@DROLSKY/TestRelease'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::ConfirmRelease
+      name: '@DROLSKY/ConfirmRelease'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::UploadToCPAN
+      name: '@DROLSKY/UploadToCPAN'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::VersionFromMainModule
+      config:
+        Dist::Zilla::Role::ModuleMetadata:
+          Module::Metadata: '1.000037'
+          version: '0.006'
+      name: '@DROLSKY/VersionFromMainModule'
+      version: '0.04'
+    -
+      class: Dist::Zilla::Plugin::Authority
+      name: '@DROLSKY/Authority'
+      version: '1.009'
+    -
+      class: Dist::Zilla::Plugin::AutoPrereqs
+      name: '@DROLSKY/AutoPrereqs'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::CopyFilesFromBuild
+      name: '@DROLSKY/CopyFilesFromBuild'
+      version: '0.170880'
+    -
+      class: Dist::Zilla::Plugin::GitHub::Meta
+      name: '@DROLSKY/GitHub::Meta'
+      version: '0.48'
+    -
+      class: Dist::Zilla::Plugin::GitHub::Update
+      config:
+        Dist::Zilla::Plugin::GitHub::Update:
+          metacpan: 1
+      name: '@DROLSKY/GitHub::Update'
+      version: '0.48'
+    -
+      class: Dist::Zilla::Plugin::MetaResources
+      name: '@DROLSKY/MetaResources'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::MetaProvides::Package
+      config:
+        Dist::Zilla::Plugin::MetaProvides::Package:
+          finder_objects:
+            -
+              class: Dist::Zilla::Plugin::FinderCode
+              name: '@DROLSKY/MetaProvides::Package/AUTOVIV/:InstallModulesPM'
+              version: '6.029'
+          include_underscores: 0
+        Dist::Zilla::Role::MetaProvider::Provider:
+          $Dist::Zilla::Role::MetaProvider::Provider::VERSION: '2.002004'
+          inherit_missing: '1'
+          inherit_version: '1'
+          meta_noindex: '1'
+        Dist::Zilla::Role::ModuleMetadata:
+          Module::Metadata: '1.000037'
+          version: '0.006'
+      name: '@DROLSKY/MetaProvides::Package'
+      version: '2.004003'
+    -
+      class: Dist::Zilla::Plugin::Meta::Contributors
+      name: '@DROLSKY/Meta::Contributors'
+      version: '0.003'
+    -
+      class: Dist::Zilla::Plugin::MetaConfig
+      name: '@DROLSKY/MetaConfig'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::MetaJSON
+      name: '@DROLSKY/MetaJSON'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::MetaYAML
+      name: '@DROLSKY/MetaYAML'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::NextRelease
+      name: '@DROLSKY/NextRelease'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::Prereqs
+      config:
+        Dist::Zilla::Plugin::Prereqs:
+          phase: test
+          type: requires
+      name: '@DROLSKY/Test::More with Test2'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::Prereqs
+      config:
+        Dist::Zilla::Plugin::Prereqs:
+          phase: develop
+          type: requires
+      name: '@DROLSKY/Tools for use with precious'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::Prereqs
+      config:
+        Dist::Zilla::Plugin::Prereqs:
+          phase: develop
+          type: requires
+      name: '@DROLSKY/Test::Version which fixes https://github.com/plicease/Test-Version/issues/7'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::PromptIfStale
+      config:
+        Dist::Zilla::Plugin::PromptIfStale:
+          check_all_plugins: 0
+          check_all_prereqs: 0
+          modules:
+            - Dist::Zilla::PluginBundle::DROLSKY
+          phase: build
+          run_under_travis: 0
+          skip: []
+      name: '@DROLSKY/Dist::Zilla::PluginBundle::DROLSKY'
+      version: '0.057'
+    -
+      class: Dist::Zilla::Plugin::PromptIfStale
+      config:
+        Dist::Zilla::Plugin::PromptIfStale:
+          check_all_plugins: 1
+          check_all_prereqs: 1
+          modules: []
+          phase: release
+          run_under_travis: 0
+          skip:
+            - Dist::Zilla::Plugin::DROLSKY::BundleAuthordep
+            - Dist::Zilla::Plugin::DROLSKY::Contributors
+            - Dist::Zilla::Plugin::DROLSKY::Git::CheckFor::CorrectBranch
+            - Dist::Zilla::Plugin::DROLSKY::License
+            - Dist::Zilla::Plugin::DROLSKY::MakeMaker
+            - Dist::Zilla::Plugin::DROLSKY::PerlLinterConfigFiles
+            - Dist::Zilla::Plugin::DROLSKY::Precious
+            - Dist::Zilla::Plugin::DROLSKY::Test::Precious
+            - Dist::Zilla::Plugin::DROLSKY::WeaverConfig
+            - Pod::Weaver::PluginBundle::DROLSKY
+      name: '@DROLSKY/PromptIfStale'
+      version: '0.057'
+    -
+      class: Dist::Zilla::Plugin::Test::Pod::Coverage::Configurable
+      name: '@DROLSKY/Test::Pod::Coverage::Configurable'
+      version: '0.07'
+    -
+      class: Dist::Zilla::Plugin::Test::PodSpelling
+      config:
+        Dist::Zilla::Plugin::Test::PodSpelling:
+          directories:
+            - bin
+            - lib
+          spell_cmd: ''
+          stopwords:
+            - DROLSKY
+            - "DROLSKY's"
+            - PayPal
+            - Rolsky
+            - "Rolsky's"
+            - drolsky
+            - getter
+            - params
+            - slurpy
+            - uncompromised
+            - validator
+            - validators
+          wordlist: Pod::Wordlist
+      name: '@DROLSKY/Test::PodSpelling'
+      version: '2.007005'
+    -
+      class: Dist::Zilla::Plugin::PodSyntaxTests
+      name: '@DROLSKY/PodSyntaxTests'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::MojibakeTests
+      name: '@DROLSKY/MojibakeTests'
+      version: '0.8'
+    -
+      class: Dist::Zilla::Plugin::RunExtraTests
+      config:
+        Dist::Zilla::Role::TestRunner:
+          default_jobs: '12'
+      name: '@DROLSKY/RunExtraTests'
+      version: '0.029'
+    -
+      class: Dist::Zilla::Plugin::Test::CPAN::Changes
+      config:
+        Dist::Zilla::Plugin::Test::CPAN::Changes:
+          changelog: Changes
+      name: '@DROLSKY/Test::CPAN::Changes'
+      version: '0.012'
+    -
+      class: Dist::Zilla::Plugin::Test::CPAN::Meta::JSON
+      name: '@DROLSKY/Test::CPAN::Meta::JSON'
+      version: '0.004'
+    -
+      class: Dist::Zilla::Plugin::Test::EOL
+      config:
+        Dist::Zilla::Plugin::Test::EOL:
+          filename: xt/author/eol.t
+          finder:
+            - ':ExecFiles'
+            - ':InstallModules'
+            - ':TestFiles'
+          trailing_whitespace: 1
+      name: '@DROLSKY/Test::EOL'
+      version: '0.19'
+    -
+      class: Dist::Zilla::Plugin::Test::NoTabs
+      config:
+        Dist::Zilla::Plugin::Test::NoTabs:
+          filename: xt/author/no-tabs.t
+          finder:
+            - ':InstallModules'
+            - ':ExecFiles'
+            - ':TestFiles'
+      name: '@DROLSKY/Test::NoTabs'
+      version: '0.15'
+    -
+      class: Dist::Zilla::Plugin::Test::Portability
+      config:
+        Dist::Zilla::Plugin::Test::Portability:
+          options: ''
+      name: '@DROLSKY/Test::Portability'
+      version: '2.001001'
+    -
+      class: Dist::Zilla::Plugin::Test::Synopsis
+      name: '@DROLSKY/Test::Synopsis'
+      version: '2.000007'
+    -
+      class: Dist::Zilla::Plugin::Test::Compile
+      config:
+        Dist::Zilla::Plugin::Test::Compile:
+          bail_out_on_fail: '0'
+          fail_on_warning: author
+          fake_home: 0
+          filename: xt/author/00-compile.t
+          module_finder:
+            - ':InstallModules'
+          needs_display: 0
+          phase: develop
+          script_finder:
+            - ':PerlExecFiles'
+          skips: []
+          switch: []
+      name: '@DROLSKY/Test::Compile'
+      version: '2.058'
+    -
+      class: Dist::Zilla::Plugin::Test::ReportPrereqs
+      name: '@DROLSKY/Test::ReportPrereqs'
+      version: '0.028'
+    -
+      class: Dist::Zilla::Plugin::Test::Version
+      name: '@DROLSKY/Test::Version'
+      version: '1.09'
+    -
+      class: Dist::Zilla::Plugin::DROLSKY::Test::Precious
+      name: '@DROLSKY/DROLSKY::Test::Precious'
+      version: '1.22'
+    -
+      class: Dist::Zilla::Plugin::DROLSKY::Contributors
+      name: '@DROLSKY/DROLSKY::Contributors'
+      version: '1.22'
+    -
+      class: Dist::Zilla::Plugin::Git::Contributors
+      config:
+        Dist::Zilla::Plugin::Git::Contributors:
+          git_version: 2.39.0
+          include_authors: 0
+          include_releaser: 1
+          order_by: name
+          paths: []
+      name: '@DROLSKY/Git::Contributors'
+      version: '0.036'
+    -
+      class: Dist::Zilla::Plugin::SurgicalPodWeaver
+      config:
+        Dist::Zilla::Plugin::PodWeaver:
+          config_plugins:
+            - '@DROLSKY'
+          finder:
+            - ':InstallModules'
+            - ':ExecFiles'
+          plugins:
+            -
+              class: Pod::Weaver::Plugin::EnsurePod5
+              name: '@CorePrep/EnsurePod5'
+              version: '4.018'
+            -
+              class: Pod::Weaver::Plugin::H1Nester
+              name: '@CorePrep/H1Nester'
+              version: '4.018'
+            -
+              class: Pod::Weaver::Plugin::SingleEncoding
+              name: '@DROLSKY/SingleEncoding'
+              version: '4.018'
+            -
+              class: Pod::Weaver::Plugin::Transformer
+              name: '@DROLSKY/List'
+              version: '4.018'
+            -
+              class: Pod::Weaver::Plugin::Transformer
+              name: '@DROLSKY/Verbatim'
+              version: '4.018'
+            -
+              class: Pod::Weaver::Section::Region
+              name: '@DROLSKY/header'
+              version: '4.018'
+            -
+              class: Pod::Weaver::Section::Name
+              name: '@DROLSKY/Name'
+              version: '4.018'
+            -
+              class: Pod::Weaver::Section::Version
+              name: '@DROLSKY/Version'
+              version: '4.018'
+            -
+              class: Pod::Weaver::Section::Region
+              name: '@DROLSKY/prelude'
+              version: '4.018'
+            -
+              class: Pod::Weaver::Section::Generic
+              name: SYNOPSIS
+              version: '4.018'
+            -
+              class: Pod::Weaver::Section::Generic
+              name: DESCRIPTION
+              version: '4.018'
+            -
+              class: Pod::Weaver::Section::Generic
+              name: OVERVIEW
+              version: '4.018'
+            -
+              class: Pod::Weaver::Section::Collect
+              name: ATTRIBUTES
+              version: '4.018'
+            -
+              class: Pod::Weaver::Section::Collect
+              name: METHODS
+              version: '4.018'
+            -
+              class: Pod::Weaver::Section::Collect
+              name: FUNCTIONS
+              version: '4.018'
+            -
+              class: Pod::Weaver::Section::Collect
+              name: TYPES
+              version: '4.018'
+            -
+              class: Pod::Weaver::Section::Leftovers
+              name: '@DROLSKY/Leftovers'
+              version: '4.018'
+            -
+              class: Pod::Weaver::Section::Region
+              name: '@DROLSKY/postlude'
+              version: '4.018'
+            -
+              class: Pod::Weaver::Section::GenerateSection
+              name: '@DROLSKY/generate SUPPORT'
+              version: '4.018'
+            -
+              class: Pod::Weaver::Section::AllowOverride
+              name: '@DROLSKY/allow override SUPPORT'
+              version: '0.05'
+            -
+              class: Pod::Weaver::Section::GenerateSection
+              name: '@DROLSKY/generate SOURCE'
+              version: '4.018'
+            -
+              class: Pod::Weaver::Section::GenerateSection
+              name: '@DROLSKY/generate DONATIONS'
+              version: '4.018'
+            -
+              class: Pod::Weaver::Section::Authors
+              name: '@DROLSKY/Authors'
+              version: '4.018'
+            -
+              class: Pod::Weaver::Section::Contributors
+              name: '@DROLSKY/Contributors'
+              version: '0.009'
+            -
+              class: Pod::Weaver::Section::Legal
+              name: '@DROLSKY/Legal'
+              version: '4.018'
+            -
+              class: Pod::Weaver::Section::AllowOverride
+              name: '@DROLSKY/allow override Legal'
+              version: '0.05'
+            -
+              class: Pod::Weaver::Section::Region
+              name: '@DROLSKY/footer'
+              version: '4.018'
+      name: '@DROLSKY/SurgicalPodWeaver'
+      version: '0.0023'
+    -
+      class: Dist::Zilla::Plugin::DROLSKY::WeaverConfig
+      name: '@DROLSKY/DROLSKY::WeaverConfig'
+      version: '1.22'
+    -
+      class: Dist::Zilla::Plugin::ReadmeAnyFromPod
+      config:
+        Dist::Zilla::Role::FileWatcher:
+          version: '0.006'
+      name: '@DROLSKY/README.md in build'
+      version: '0.163250'
+    -
+      class: Dist::Zilla::Plugin::GenerateFile::FromShareDir
+      config:
+        Dist::Zilla::Plugin::GenerateFile::FromShareDir:
+          destination_filename: CONTRIBUTING.md
+          dist: Dist-Zilla-PluginBundle-DROLSKY
+          encoding: UTF-8
+          has_xs: '0'
+          location: build
+          source_filename: CONTRIBUTING.md
+        Dist::Zilla::Role::RepoFileInjector:
+          allow_overwrite: 1
+          repo_root: .
+          version: '0.009'
+      name: '@DROLSKY/Generate CONTRIBUTING.md'
+      version: '0.015'
+    -
+      class: Dist::Zilla::Plugin::GenerateFile::FromShareDir
+      config:
+        Dist::Zilla::Plugin::GenerateFile::FromShareDir:
+          destination_filename: CODE_OF_CONDUCT.md
+          dist: Dist-Zilla-PluginBundle-DROLSKY
+          encoding: UTF-8
+          has_xs: '0'
+          location: build
+          source_filename: CODE_OF_CONDUCT.md
+        Dist::Zilla::Role::RepoFileInjector:
+          allow_overwrite: 1
+          repo_root: .
+          version: '0.009'
+      name: '@DROLSKY/Generate CODE_OF_CONDUCT.md'
+      version: '0.015'
+    -
+      class: Dist::Zilla::Plugin::InstallGuide
+      config:
+        Dist::Zilla::Role::ModuleMetadata:
+          Module::Metadata: '1.000037'
+          version: '0.006'
+      name: '@DROLSKY/InstallGuide'
+      version: '1.200014'
+    -
+      class: Dist::Zilla::Plugin::CPANFile
+      name: '@DROLSKY/CPANFile'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::DROLSKY::License
+      name: '@DROLSKY/DROLSKY::License'
+      version: '1.22'
+    -
+      class: Dist::Zilla::Plugin::CheckStrictVersion
+      name: '@DROLSKY/CheckStrictVersion'
+      version: '0.001'
+    -
+      class: Dist::Zilla::Plugin::CheckSelfDependency
+      config:
+        Dist::Zilla::Plugin::CheckSelfDependency:
+          finder:
+            - ':InstallModules'
+        Dist::Zilla::Role::ModuleMetadata:
+          Module::Metadata: '1.000037'
+          version: '0.006'
+      name: '@DROLSKY/CheckSelfDependency'
+      version: '0.011'
+    -
+      class: Dist::Zilla::Plugin::CheckPrereqsIndexed
+      name: '@DROLSKY/CheckPrereqsIndexed'
+      version: '0.021'
+    -
+      class: Dist::Zilla::Plugin::DROLSKY::Git::CheckFor::CorrectBranch
+      config:
+        Dist::Zilla::Role::Git::Repo:
+          git_version: 2.39.0
+          repo_root: .
+      name: '@DROLSKY/DROLSKY::Git::CheckFor::CorrectBranch'
+      version: '1.22'
+    -
+      class: Dist::Zilla::Plugin::EnsureChangesHasContent
+      name: '@DROLSKY/EnsureChangesHasContent'
+      version: '0.02'
+    -
+      class: Dist::Zilla::Plugin::Git::CheckFor::MergeConflicts
+      config:
+        Dist::Zilla::Role::Git::Repo:
+          git_version: 2.39.0
+          repo_root: .
+      name: '@DROLSKY/Git::CheckFor::MergeConflicts'
+      version: '0.014'
+    -
+      class: Dist::Zilla::Plugin::DROLSKY::PerlLinterConfigFiles
+      name: '@DROLSKY/DROLSKY::PerlLinterConfigFiles'
+      version: '1.22'
+    -
+      class: Dist::Zilla::Plugin::DROLSKY::DevTools
+      name: '@DROLSKY/DROLSKY::DevTools'
+      version: '1.22'
+    -
+      class: Dist::Zilla::Plugin::DROLSKY::Precious
+      name: '@DROLSKY/DROLSKY::Precious'
+      version: '1.22'
+    -
+      class: Dist::Zilla::Plugin::Git::Check
+      config:
+        Dist::Zilla::Plugin::Git::Check:
+          untracked_files: die
+        Dist::Zilla::Role::Git::DirtyFiles:
+          allow_dirty:
+            - CODE_OF_CONDUCT.md
+            - CONTRIBUTING.md
+            - Changes
+            - LICENSE
+            - Makefile.PL
+            - README.md
+            - cpanfile
+            - precious.toml
+          allow_dirty_match: []
+          changelog: Changes
+        Dist::Zilla::Role::Git::Repo:
+          git_version: 2.39.0
+          repo_root: .
+      name: '@DROLSKY/Git::Check'
+      version: '2.048'
+    -
+      class: Dist::Zilla::Plugin::Git::Commit
+      config:
+        Dist::Zilla::Plugin::Git::Commit:
+          add_files_in: []
+          commit_msg: v%V%n%n%c
+          signoff: 0
+        Dist::Zilla::Role::Git::DirtyFiles:
+          allow_dirty:
+            - CODE_OF_CONDUCT.md
+            - CONTRIBUTING.md
+            - Changes
+            - LICENSE
+            - Makefile.PL
+            - README.md
+            - cpanfile
+            - precious.toml
+          allow_dirty_match: []
+          changelog: Changes
+        Dist::Zilla::Role::Git::Repo:
+          git_version: 2.39.0
+          repo_root: .
+        Dist::Zilla::Role::Git::StringFormatter:
+          time_zone: local
+      name: '@DROLSKY/Commit generated files'
+      version: '2.048'
+    -
+      class: Dist::Zilla::Plugin::Git::Tag
+      config:
+        Dist::Zilla::Plugin::Git::Tag:
+          branch: ~
+          changelog: Changes
+          signed: 0
+          tag: v0.31
+          tag_format: v%V
+          tag_message: v%V
+        Dist::Zilla::Role::Git::Repo:
+          git_version: 2.39.0
+          repo_root: .
+        Dist::Zilla::Role::Git::StringFormatter:
+          time_zone: local
+      name: '@DROLSKY/Git::Tag'
+      version: '2.048'
+    -
+      class: Dist::Zilla::Plugin::Git::Push
+      config:
+        Dist::Zilla::Plugin::Git::Push:
+          push_to:
+            - origin
+          remotes_must_exist: 1
+        Dist::Zilla::Role::Git::Repo:
+          git_version: 2.39.0
+          repo_root: .
+      name: '@DROLSKY/Git::Push'
+      version: '2.048'
+    -
+      class: Dist::Zilla::Plugin::BumpVersionAfterRelease
+      config:
+        Dist::Zilla::Plugin::BumpVersionAfterRelease:
+          finders:
+            - ':ExecFiles'
+            - ':InstallModules'
+          global: 0
+          munge_makefile_pl: 1
+      name: '@DROLSKY/BumpVersionAfterRelease'
+      version: '0.018'
+    -
+      class: Dist::Zilla::Plugin::Git::Commit
+      config:
+        Dist::Zilla::Plugin::Git::Commit:
+          add_files_in: []
+          commit_msg: 'Bump version after release'
+          signoff: 0
+        Dist::Zilla::Role::Git::DirtyFiles:
+          allow_dirty:
+            - Changes
+            - dist.ini
+          allow_dirty_match:
+            - (?^:.+)
+          changelog: Changes
+        Dist::Zilla::Role::Git::Repo:
+          git_version: 2.39.0
+          repo_root: .
+        Dist::Zilla::Role::Git::StringFormatter:
+          time_zone: local
+      name: '@DROLSKY/Commit version bump'
+      version: '2.048'
+    -
+      class: Dist::Zilla::Plugin::Git::Push
+      config:
+        Dist::Zilla::Plugin::Git::Push:
+          push_to:
+            - origin
+          remotes_must_exist: 1
+        Dist::Zilla::Role::Git::Repo:
+          git_version: 2.39.0
+          repo_root: .
+      name: '@DROLSKY/Push version bump'
+      version: '2.048'
+    -
+      class: Dist::Zilla::Plugin::DROLSKY::MakeMaker
+      config:
+        Dist::Zilla::Plugin::MakeMaker:
+          make_path: make
+          version: '6.029'
+        Dist::Zilla::Plugin::MakeMaker::Awesome:
+          version: '0.49'
+        Dist::Zilla::Role::TestRunner:
+          default_jobs: '12'
+          version: '6.029'
+      name: '@DROLSKY/DROLSKY::MakeMaker'
+      version: '1.22'
+    -
+      class: Dist::Zilla::Plugin::Prereqs
+      config:
+        Dist::Zilla::Plugin::Prereqs:
+          phase: develop
+          type: requires
+      name: DevelopRequires
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::Prereqs
+      config:
+        Dist::Zilla::Plugin::Prereqs:
+          phase: runtime
+          type: recommends
+      name: Recommends
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::Prereqs
+      config:
+        Dist::Zilla::Plugin::Prereqs:
+          phase: test
+          type: requires
+      name: TestRequires
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::FinderCode
+      name: ':InstallModules'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::FinderCode
+      name: ':IncModules'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::FinderCode
+      name: ':TestFiles'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::FinderCode
+      name: ':ExtraTestFiles'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::FinderCode
+      name: ':ExecFiles'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::FinderCode
+      name: ':PerlExecFiles'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::FinderCode
+      name: ':ShareFiles'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::FinderCode
+      name: ':MainModule'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::FinderCode
+      name: ':AllFiles'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::FinderCode
+      name: ':NoFiles'
+      version: '6.029'
+    -
+      class: Dist::Zilla::Plugin::FinderCode
+      name: '@DROLSKY/MetaProvides::Package/AUTOVIV/:InstallModulesPM'
+      version: '6.029'
+  zilla:
+    class: Dist::Zilla::Dist::Builder
+    config:
+      is_trial: '0'
+    version: '6.029'
+x_authority: cpan:DROLSKY
+x_contributors:
+  - 'Gregory Oschwald <goschwald@maxmind.com>'
+  - 'Gregory Oschwald <oschwald@gmail.com>'
+  - 'Tomasz Konojacki <me@xenu.pl>'
+x_generated_by_perl: v5.32.1
+x_serialization_backend: 'YAML::Tiny version 1.73'
+x_spdx_expression: Artistic-2.0
diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644 (file)
index 0000000..ad2d3de
--- /dev/null
@@ -0,0 +1,80 @@
+# This Makefile.PL for Params-ValidationCompiler was generated by
+# Dist::Zilla::Plugin::DROLSKY::MakeMaker 1.22
+# and Dist::Zilla::Plugin::MakeMaker::Awesome 0.49.
+# Don't edit it but the dist.ini and plugins used to construct it.
+
+use strict;
+use warnings;
+
+use ExtUtils::MakeMaker;
+
+my %WriteMakefileArgs = (
+  "ABSTRACT" => "Build an optimized subroutine parameter validator once, use it forever",
+  "AUTHOR" => "Dave Rolsky <autarch\@urth.org>",
+  "CONFIGURE_REQUIRES" => {
+    "ExtUtils::MakeMaker" => 0
+  },
+  "DISTNAME" => "Params-ValidationCompiler",
+  "LICENSE" => "artistic_2",
+  "NAME" => "Params::ValidationCompiler",
+  "PREREQ_PM" => {
+    "B" => 0,
+    "Carp" => 0,
+    "Eval::Closure" => 0,
+    "Exception::Class" => 0,
+    "Exporter" => 0,
+    "List::Util" => "1.29",
+    "Scalar::Util" => 0,
+    "overload" => 0,
+    "strict" => 0,
+    "warnings" => 0
+  },
+  "TEST_REQUIRES" => {
+    "ExtUtils::MakeMaker" => 0,
+    "File::Spec" => 0,
+    "Hash::Util" => 0,
+    "Specio" => "0.14",
+    "Test2::Plugin::NoWarnings" => 0,
+    "Test2::Require::Module" => 0,
+    "Test2::V0" => 0,
+    "Test::More" => "1.302015",
+    "Test::Without::Module" => 0
+  },
+  "VERSION" => "0.31",
+  "test" => {
+    "TESTS" => "t/*.t t/named/*.t t/positional/*.t"
+  }
+);
+
+my %FallbackPrereqs = (
+  "B" => 0,
+  "Carp" => 0,
+  "Eval::Closure" => 0,
+  "Exception::Class" => 0,
+  "Exporter" => 0,
+  "ExtUtils::MakeMaker" => 0,
+  "File::Spec" => 0,
+  "Hash::Util" => 0,
+  "List::Util" => "1.29",
+  "Scalar::Util" => 0,
+  "Specio" => "0.14",
+  "Test2::Plugin::NoWarnings" => 0,
+  "Test2::Require::Module" => 0,
+  "Test2::V0" => 0,
+  "Test::More" => "1.302015",
+  "Test::Without::Module" => 0,
+  "overload" => 0,
+  "strict" => 0,
+  "warnings" => 0
+);
+
+unless ( eval { ExtUtils::MakeMaker->VERSION('6.63_03') } ) {
+  delete $WriteMakefileArgs{TEST_REQUIRES};
+  delete $WriteMakefileArgs{BUILD_REQUIRES};
+  $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs;
+}
+
+delete $WriteMakefileArgs{CONFIGURE_REQUIRES}
+  unless eval { ExtUtils::MakeMaker->VERSION(6.52) };
+
+WriteMakefile(%WriteMakefileArgs);
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..8f99832
--- /dev/null
+++ b/README.md
@@ -0,0 +1,272 @@
+# NAME
+
+Params::ValidationCompiler - Build an optimized subroutine parameter validator once, use it forever
+
+# VERSION
+
+version 0.31
+
+# SYNOPSIS
+
+    use Types::Standard qw( Int Str );
+    use Params::ValidationCompiler qw( validation_for );
+
+    {
+        my $validator = validation_for(
+            params => {
+                foo => { type => Int },
+                bar => {
+                    type     => Str,
+                    optional => 1,
+                },
+                baz => {
+                    type    => Int,
+                    default => 42,
+                },
+            },
+        );
+
+        sub foo {
+            my %args = $validator->(@_);
+        }
+    }
+
+    {
+        my $validator = validation_for(
+            params => [
+                { type => Int },
+                {
+                    type     => Str,
+                    optional => 1,
+                },
+            ],
+        );
+
+        sub bar {
+            my ( $int, $str ) = $validator->(@_);
+        }
+    }
+
+    {
+        my $validator = validation_for(
+            params => [
+                foo => { type => Int },
+                bar => {
+                    type     => Str,
+                    optional => 1,
+                },
+            ],
+            named_to_list => 1,
+        );
+
+        sub baz {
+            my ( $foo, $bar ) = $validator->(@_);
+        }
+    }
+
+# DESCRIPTION
+
+This module creates a customized, highly efficient parameter checking
+subroutine. It can handle named or positional parameters, and can return the
+parameters as key/value pairs or a list of values.
+
+In addition to type checks, it also supports parameter defaults, optional
+parameters, and extra "slurpy" parameters.
+
+# PARAMETERS
+
+This module has two options exports, `validation_for` and `source_for`. Both
+of these subs accept the same options:
+
+## params
+
+An arrayref or hashref containing a parameter specification.
+
+If you pass a hashref then the generated validator sub will expect named
+parameters. The `params` value should be a hashref where the parameter names
+are keys and the specs are the values.
+
+If you pass an arrayref and `named_to_list` is false, the validator will
+expect positional params. Each element of the `params` arrayref should be a
+parameter spec.
+
+If you pass an arrayref and `named_to_list` is true, the validator will expect
+named params, but will return a list of values. In this case the arrayref
+should contain a _list_ of key/value pairs, where parameter names are the keys
+and the specs are the values.
+
+Each spec can contain either a boolean or hashref. If the spec is a boolean,
+this indicates required (true) or optional (false).
+
+The spec hashref accepts the following keys:
+
+- type
+
+    A type object. This can be a [Moose](https://metacpan.org/pod/Moose) type (from [Moose](https://metacpan.org/pod/Moose) or [MooseX::Types](https://metacpan.org/pod/MooseX%3A%3ATypes)),
+    a [Type::Tiny](https://metacpan.org/pod/Type%3A%3ATiny) type, or a [Specio](https://metacpan.org/pod/Specio) type.
+
+    If the type has coercions, those will always be used.
+
+- default
+
+    This can either be a simple (non-reference) scalar or a subroutine reference.
+    The sub ref will be called without any arguments (for now).
+
+- optional
+
+    A boolean indicating whether or not the parameter is optional. By default,
+    parameters are required unless you provide a default.
+
+## slurpy
+
+If this is a simple true value, then the generated subroutine accepts
+additional arguments not specified in `params`. By default, extra arguments
+cause an exception.
+
+You can also pass a type constraint here, in which case all extra arguments
+must be values of the specified type.
+
+## named\_to\_list
+
+If this is true, the generated subroutine will expect a list of key-value pairs
+or a hashref and it will return a list containing only values. The `params`
+you pass must be a arrayref of key-value pairs. The order of these pairs
+determines the order in which values are returned.
+
+You cannot combine `slurpy` with `named_to_list` as there is no way to know
+how to order the extra return values.
+
+## return\_object
+
+If this is true, the generated subroutine will return an object instead of a
+hashref. You cannot set this option to true if you set either or `slurpy` or
+`named_to_list`.
+
+The object's methods correspond to the parameter names passed to the
+subroutine. While calling methods on an object is slower than accessing a
+hashref, the advantage is that if you typo a parameter name you'll get a
+helpful error.
+
+If you have [Class::XSAccessor](https://metacpan.org/pod/Class%3A%3AXSAccessor) installed then this will be used to create the
+class's methods, which makes it fairly fast.
+
+The returned object is in a generated class. Do not rely on this class name
+being anything in specific, and don't check this object using `isa`, `DOES`,
+or anything similar.
+
+When `return_object` is true, the parameter spec hashref also accepts to the
+following additional keys:
+
+- getter
+
+    Use this to set an explicit getter method name for the parameter. By default
+    the method name will be the same as the parameter name. Note that if the
+    parameter name is not a valid sub name, then you will get an error compiling
+    the validation sub unless you specify a getter for the parameter.
+
+- predicate
+
+    Use this to ask for a predicate method to be created for this parameter. The
+    predicate method returns true if the parameter was passed and false if it
+    wasn't. Note that this is only useful for optional parameters, but you can ask
+    for a predicate for any parameter.
+
+# EXPORTS
+
+The exported subs are:
+
+## validation\_for(...)
+
+This returns a subroutine that implements the specific parameter checking. This
+subroutine expects to be given the parameters to validate in `@_`. If all the
+parameters are valid, it will return the validated parameters (with defaults as
+appropriate), either as a list of key-value pairs or as a list of just values.
+If any of the parameters are invalid it will throw an exception.
+
+For validators expected named params, the generated subroutine accepts either a
+list of key-value pairs or a single hashref. Otherwise the validator expects a
+list of values.
+
+For now, you must shift off the invocant yourself.
+
+This subroutine accepts the following additional parameters:
+
+- name
+
+    If this is given, then the generated subroutine will be named using
+    [Sub::Util](https://metacpan.org/pod/Sub%3A%3AUtil). This is strongly recommended as it makes it possible to
+    distinguish different check subroutines when profiling or in stack traces.
+
+    This name will also be used in some exception messages, even if [Sub::Util](https://metacpan.org/pod/Sub%3A%3AUtil) is
+    not available.
+
+    Note that you must install [Sub::Util](https://metacpan.org/pod/Sub%3A%3AUtil) yourself separately, as it is not
+    required by this distribution, in order to avoid requiring a compiler.
+
+- name\_is\_optional
+
+    If this is true, then the name is ignored when `Sub::Util` is not installed.
+    If this is false, then passing a name when [Sub::Util](https://metacpan.org/pod/Sub%3A%3AUtil) cannot be loaded causes
+    an exception.
+
+    This is useful for CPAN modules where you want to set a name if you can, but
+    you do not want to add a prerequisite on [Sub::Util](https://metacpan.org/pod/Sub%3A%3AUtil).
+
+- debug
+
+    Sets the `EVAL_CLOSURE_PRINT_SOURCE` environment variable to true before
+    calling `Eval::Closure::eval_closure()`. This causes the source of the
+    subroutine to be printed before it's `eval`'d.
+
+## source\_for(...)
+
+This returns a two element list. The first is a string containing the source
+code for the generated sub. The second is a hashref of "environment" variables
+to be used when generating the subroutine. These are the arguments that are
+passed to [Eval::Closure](https://metacpan.org/pod/Eval%3A%3AClosure).
+
+# SUPPORT
+
+Bugs may be submitted at [https://github.com/houseabsolute/Params-ValidationCompiler/issues](https://github.com/houseabsolute/Params-ValidationCompiler/issues).
+
+# SOURCE
+
+The source code repository for Params-ValidationCompiler can be found at [https://github.com/houseabsolute/Params-ValidationCompiler](https://github.com/houseabsolute/Params-ValidationCompiler).
+
+# DONATIONS
+
+If you'd like to thank me for the work I've done on this module, please
+consider making a "donation" to me via PayPal. I spend a lot of free time
+creating free software, and would appreciate any support you'd care to offer.
+
+Please note that **I am not suggesting that you must do this** in order for me
+to continue working on this particular software. I will continue to do so,
+inasmuch as I have in the past, for as long as it interests me.
+
+Similarly, a donation made in this way will probably not make me work on this
+software much more, unless I get so many donations that I can consider working
+on free software full time (let's all have a chuckle at that together).
+
+To donate, log into PayPal and send money to autarch@urth.org, or use the
+button at [https://houseabsolute.com/foss-donations/](https://houseabsolute.com/foss-donations/).
+
+# AUTHOR
+
+Dave Rolsky <autarch@urth.org>
+
+# CONTRIBUTORS
+
+- Gregory Oschwald <goschwald@maxmind.com>
+- Gregory Oschwald <oschwald@gmail.com>
+- Tomasz Konojacki <me@xenu.pl>
+
+# COPYRIGHT AND LICENSE
+
+This software is Copyright (c) 2016 - 2023 by Dave Rolsky.
+
+This is free software, licensed under:
+
+    The Artistic License 2.0 (GPL Compatible)
+
+The full text of the license can be found in the
+`LICENSE` file included with this distribution.
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
new file mode 100644 (file)
index 0000000..4c64519
--- /dev/null
@@ -0,0 +1,28 @@
+resources:
+  repositories:
+    - repository: ci-perl-helpers
+      type: github
+      name: houseabsolute/ci-perl-helpers
+      endpoint: houseabsolute
+
+stages:
+  - template: templates/helpers/build.yml@ci-perl-helpers
+    parameters:
+      debug: true
+
+  - template: templates/helpers/linux.yml@ci-perl-helpers
+    parameters:
+      coverage: codecov
+      debug: true
+      test_xt: true
+      use_default_perls: true
+
+  - template: templates/helpers/macos.yml@ci-perl-helpers
+    parameters:
+      debug: true
+      use_default_perls: true
+
+  - template: templates/helpers/windows.yml@ci-perl-helpers
+    parameters:
+      debug: true
+      use_default_perls: true
diff --git a/cpanfile b/cpanfile
new file mode 100644 (file)
index 0000000..98d1992
--- /dev/null
+++ b/cpanfile
@@ -0,0 +1,78 @@
+# This file is generated by Dist::Zilla::Plugin::CPANFile v6.029
+# Do not edit this file directly. To change prereqs, edit the `dist.ini` file.
+
+requires "B" => "0";
+requires "Carp" => "0";
+requires "Eval::Closure" => "0";
+requires "Exception::Class" => "0";
+requires "Exporter" => "0";
+requires "List::Util" => "1.29";
+requires "Scalar::Util" => "0";
+requires "overload" => "0";
+requires "strict" => "0";
+requires "warnings" => "0";
+recommends "Class::XSAccessor" => "1.17";
+recommends "Sub::Util" => "1.40";
+
+on 'test' => sub {
+  requires "ExtUtils::MakeMaker" => "0";
+  requires "File::Spec" => "0";
+  requires "Hash::Util" => "0";
+  requires "Specio" => "0.14";
+  requires "Test2::Plugin::NoWarnings" => "0";
+  requires "Test2::Require::Module" => "0";
+  requires "Test2::V0" => "0";
+  requires "Test::More" => "1.302015";
+  requires "Test::Without::Module" => "0";
+};
+
+on 'test' => sub {
+  recommends "CPAN::Meta" => "2.120900";
+};
+
+on 'configure' => sub {
+  requires "ExtUtils::MakeMaker" => "0";
+};
+
+on 'develop' => sub {
+  requires "Capture::Tiny" => "0";
+  requires "Class::XSAccessor" => "1.17";
+  requires "Const::Fast" => "0.014";
+  requires "Encode" => "0";
+  requires "File::Spec" => "0";
+  requires "FindBin" => "0";
+  requires "Hash::Merge" => "0";
+  requires "Hash::Util" => "0";
+  requires "IO::Handle" => "0";
+  requires "IPC::Open3" => "0";
+  requires "List::AllUtils" => "0";
+  requires "Moose" => "2.0000";
+  requires "Perl::Critic" => "1.138";
+  requires "Perl::Critic::Moose" => "1.05";
+  requires "Perl::Tidy" => "20210111";
+  requires "Pod::Checker" => "1.74";
+  requires "Pod::Coverage::TrustPod" => "0";
+  requires "Pod::Tidy" => "0.10";
+  requires "Pod::Wordlist" => "0";
+  requires "Set::Scalar" => "0";
+  requires "Specio" => "0.14";
+  requires "Sub::Util" => "1.40";
+  requires "Test2::Bundle::Extended" => "0";
+  requires "Test2::Plugin::NoWarnings" => "0";
+  requires "Test2::Require::Perl" => "0";
+  requires "Test::CPAN::Changes" => "0.19";
+  requires "Test::CPAN::Meta::JSON" => "0.16";
+  requires "Test::EOL" => "0";
+  requires "Test::Mojibake" => "0";
+  requires "Test::More" => "0.96";
+  requires "Test::NoTabs" => "0";
+  requires "Test::Pod" => "1.41";
+  requires "Test::Pod::Coverage" => "1.08";
+  requires "Test::Portability::Files" => "0";
+  requires "Test::Spelling" => "0.12";
+  requires "Test::Synopsis" => "0";
+  requires "Test::Version" => "2.05";
+  requires "Type::Tiny" => "0";
+  requires "Type::Utils" => "0";
+  requires "perl" => "5.006";
+};
diff --git a/dev-bin/install-xt-tools.sh b/dev-bin/install-xt-tools.sh
new file mode 100755 (executable)
index 0000000..0f3e7b6
--- /dev/null
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+set -e
+
+TARGET="$HOME/bin"
+if [ $(id -u) -eq 0 ]; then
+    TARGET="/usr/local/bin"
+fi
+echo "Installing dev tools to $TARGET"
+
+mkdir -p $TARGET
+curl --silent --location \
+       https://raw.githubusercontent.com/houseabsolute/ubi/master/bootstrap/bootstrap-ubi.sh |
+       sh
+
+"$TARGET/ubi" --project houseabsolute/precious --in "$TARGET"
+"$TARGET/ubi" --project houseabsolute/omegasort --in "$TARGET"
+
+echo "Add $TARGET to your PATH in order to use precious for linting and tidying"
diff --git a/dist.ini b/dist.ini
new file mode 100644 (file)
index 0000000..71a9441
--- /dev/null
+++ b/dist.ini
@@ -0,0 +1,39 @@
+name    = Params-ValidationCompiler
+author  = Dave Rolsky <autarch@urth.org>
+license = Artistic_2_0
+copyright_holder = Dave Rolsky
+copyright_year   = 2016
+
+; authordep Dist::Zilla::PluginBundle::DROLSKY = 1.22
+[@DROLSKY]
+dist = Params-ValidationCompiler
+prereqs_skip = Class::XSAccessor
+prereqs_skip = Const::Fast
+prereqs_skip = Moose::Util::TypeConstraints
+prereqs_skip = Specio
+prereqs_skip = Sub::Util
+prereqs_skip = Types::Standard
+stopwords = getter
+stopwords = params
+stopwords = slurpy
+stopwords = uncompromised
+stopwords = validator
+stopwords = validators
+use_github_issues = 1
+-remove = Test::CleanNamespaces
+
+[Prereqs / DevelopRequires]
+Class::XSAccessor = 1.17
+Const::Fast       = 0.014
+Hash::Util        = 0
+Moose             = 2.0000
+Specio            = 0.14
+Sub::Util         = 1.40
+Type::Tiny        = 0
+
+[Prereqs / Recommends]
+Class::XSAccessor = 1.17
+Sub::Util         = 1.40
+
+[Prereqs / TestRequires]
+Specio = 0.14
diff --git a/eg/bench-named.pl b/eg/bench-named.pl
new file mode 100644 (file)
index 0000000..233e994
--- /dev/null
@@ -0,0 +1,312 @@
+## no critic (Moose::RequireCleanNamespace, ErrorHandling::RequireCheckingReturnValueOfEval)
+use strict;
+use warnings;
+
+use Benchmark qw( cmpthese );
+
+use DateTime;
+use Moose::Util::TypeConstraints qw( class_type find_type_constraint );
+use MooseX::Params::Validate;
+use Params::Validate           qw( validate SCALAR ARRAYREF );
+use Params::ValidationCompiler ();
+use Specio::Declare;
+use Specio::Library::Builtins;
+use Test2::V0;
+use Test2::Plugin::DieOnFail;
+use Type::Params    ();
+use Types::Standard qw( ArrayRef Dict InstanceOf Int Optional slurpy );
+
+my $dt = DateTime->new( year => 2016 );
+
+{
+    my $pvc_moose = Params::ValidationCompiler::validation_for(
+        params => {
+            foo => { type => find_type_constraint('Int') },
+            bar => { type => find_type_constraint('ArrayRef') },
+            baz => { type => class_type('DateTime'), optional => 1 },
+        }
+    );
+
+    sub pvc_moose {
+        return $pvc_moose->(@_);
+    }
+}
+
+{
+    is(
+        dies {
+            pvc_moose( foo => 42, bar => [ 1, 2, 3 ], baz => $dt );
+        },
+        undef,
+    );
+    is(
+        dies {
+            pvc_moose( foo => 42, bar => [ 1, 2, 3 ] );
+        },
+        undef,
+    );
+    ok(
+        dies {
+            pvc_moose(
+                foo => 42, bar => [ 1, 2, 3 ],
+                baz => { year => 2016 }
+            );
+        }
+    );
+}
+
+sub call_pvc_moose_lives {
+    pvc_moose( foo => 42, bar => [ 1, 2, 3 ], baz => $dt );
+    pvc_moose( foo => 42, bar => [ 1, 2, 3 ] );
+}
+
+sub call_pvc_moose_dies {
+    eval {
+        pvc_moose( foo => 42, bar => [ 1, 2, 3 ], baz => { year => 2016 } );
+    };
+}
+
+{
+    my $pvc_tt = Params::ValidationCompiler::validation_for(
+        params => {
+            foo => { type => Int },
+            bar => { type => ArrayRef },
+            baz => { type => InstanceOf ['DateTime'], optional => 1 },
+        }
+    );
+
+    sub pvc_tt {
+        return $pvc_tt->(@_);
+    }
+}
+
+{
+    is(
+        dies {
+            pvc_tt( foo => 42, bar => [ 1, 2, 3 ], baz => $dt );
+        },
+        undef,
+    );
+    is(
+        dies {
+            pvc_tt( foo => 42, bar => [ 1, 2, 3 ] );
+        },
+        undef,
+    );
+    ok(
+        dies {
+            pvc_tt( foo => 42, bar => [ 1, 2, 3 ], baz => { year => 2016 } );
+        }
+    );
+}
+
+sub call_pvc_tt_lives {
+    pvc_tt( foo => 42, bar => [ 1, 2, 3 ], baz => $dt );
+    pvc_tt( foo => 42, bar => [ 1, 2, 3 ] );
+}
+
+sub call_pvc_tt_dies {
+    eval { pvc_tt( foo => 42, bar => [ 1, 2, 3 ], baz => { year => 2016 } ) };
+}
+
+{
+    my $pvc_specio = Params::ValidationCompiler::validation_for(
+        params => {
+            foo => { type => t('Int') },
+            bar => { type => t('ArrayRef') },
+            baz => { type => object_isa_type('DateTime'), optional => 1 },
+        }
+    );
+
+    sub pvc_specio {
+        return $pvc_specio->(@_);
+    }
+}
+
+{
+    is(
+        dies {
+            pvc_specio( foo => 42, bar => [ 1, 2, 3 ], baz => $dt );
+        },
+        undef,
+    );
+    is(
+        dies {
+            pvc_specio( foo => 42, bar => [ 1, 2, 3 ] );
+        },
+        undef,
+    );
+    ok(
+        dies {
+            pvc_specio(
+                foo => 42, bar => [ 1, 2, 3 ],
+                baz => { year => 2016 }
+            );
+        }
+    );
+}
+
+sub call_pvc_specio_lives {
+    pvc_specio( foo => 42, bar => [ 1, 2, 3 ], baz => $dt );
+    pvc_specio( foo => 42, bar => [ 1, 2, 3 ] );
+}
+
+sub call_pvc_specio_dies {
+    eval {
+        pvc_specio( foo => 42, bar => [ 1, 2, 3 ], baz => { year => 2016 } );
+    };
+}
+
+{
+    my %spec = (
+        foo => { isa => find_type_constraint('Int') },
+        bar => { isa => find_type_constraint('ArrayRef') },
+        baz => { isa => class_type('DateTime'), optional => 1 },
+    );
+
+    sub mxpv {
+        return validated_hash( \@_, %spec );
+    }
+}
+
+{
+    is(
+        dies {
+            mxpv( foo => 42, bar => [ 1, 2, 3 ], baz => $dt );
+        },
+        undef,
+    );
+    is(
+        dies {
+            mxpv( foo => 42, bar => [ 1, 2, 3 ] );
+        },
+        undef,
+    );
+    ok(
+        dies {
+            mxpv( foo => 42, bar => [ 1, 2, 3 ], baz => { year => 2016 } );
+        }
+    );
+}
+
+sub call_mxpv_lives {
+    mxpv( foo => 42, bar => [ 1, 2, 3 ], baz => $dt );
+    mxpv( foo => 42, bar => [ 1, 2, 3 ] );
+}
+
+sub call_mxpv_dies {
+    eval { mxpv( foo => 42, bar => [ 1, 2, 3 ], baz => { year => 2016 } ) };
+}
+
+{
+    my $tp = Type::Params::compile_named(
+        foo => Int,
+        bar => ArrayRef,
+        baz => Optional [ InstanceOf ['DateTime'] ],
+    );
+
+    sub tp {
+        return $tp->(@_);
+    }
+}
+
+{
+    is(
+        dies {
+            tp( foo => 42, bar => [ 1, 2, 3 ], baz => $dt );
+        },
+        undef,
+    );
+    is(
+        dies {
+            tp( foo => 42, bar => [ 1, 2, 3 ] );
+        },
+        undef,
+    );
+    ok(
+        dies {
+            tp( foo => 42, bar => [ 1, 2, 3 ], baz => { year => 2016 } );
+        }
+    );
+}
+
+sub call_tp_lives {
+    tp( foo => 42, bar => [ 1, 2, 3 ], baz => $dt );
+    tp( foo => 42, bar => [ 1, 2, 3 ] );
+}
+
+sub call_tp_dies {
+    eval { tp( foo => 42, bar => [ 1, 2, 3 ], baz => { year => 2016 } ) };
+}
+
+sub pv {
+    validate(
+        @_,
+        {
+            foo => {
+                type  => SCALAR,
+                regex => qr/^\d+$/a,
+            },
+            bar => { type => ARRAYREF },
+            baz => {
+                isa      => 'DateTime',
+                optional => 1,
+            },
+        },
+    );
+}
+
+{
+    is(
+        dies {
+            pv( foo => 42, bar => [ 1, 2, 3 ], baz => $dt );
+        },
+        undef,
+    );
+    is(
+        dies {
+            pv( foo => 42, bar => [ 1, 2, 3 ] );
+        },
+        undef,
+    );
+    ok(
+        dies {
+            pv( foo => 42, bar => [ 1, 2, 3 ], baz => { year => 2016 } );
+        }
+    );
+}
+
+sub call_pv_lives {
+    pv( foo => 42, bar => [ 1, 2, 3 ], baz => $dt );
+    pv( foo => 42, bar => [ 1, 2, 3 ] );
+}
+
+sub call_pv_dies {
+    eval { pv( foo => 42, bar => [ 1, 2, 3 ], baz => { year => 2016 } ) };
+}
+
+done_testing();
+
+cmpthese(
+    100000, {
+        pvc_moose_lives  => \&call_pvc_moose_lives,
+        pvc_tt_lives     => \&call_pvc_tt_lives,
+        pvc_specio_lives => \&call_pvc_specio_lives,
+        mxpv_lives       => \&call_mxpv_lives,
+        tp_lives         => \&call_tp_lives,
+        pv_lives         => \&call_pv_lives,
+    }
+);
+
+print "\n" or die $!;
+
+cmpthese(
+    50000, {
+        pvc_moose_dies  => \&call_pvc_moose_dies,
+        pvc_tt_dies     => \&call_pvc_tt_dies,
+        pvc_specio_dies => \&call_pvc_specio_dies,
+        mxpv_dies       => \&call_mxpv_dies,
+        tp_dies         => \&call_tp_dies,
+        pv_dies         => \&call_pv_dies,
+    },
+);
diff --git a/eg/bench-pos.pl b/eg/bench-pos.pl
new file mode 100644 (file)
index 0000000..45dd5d2
--- /dev/null
@@ -0,0 +1,304 @@
+## no critic (Moose::RequireCleanNamespace, ErrorHandling::RequireCheckingReturnValueOfEval)
+use strict;
+use warnings;
+
+use Benchmark qw( cmpthese );
+
+use DateTime;
+use Moose::Util::TypeConstraints qw( class_type find_type_constraint );
+use MooseX::Params::Validate;
+use Params::Validate           qw( validate_pos SCALAR ARRAYREF );
+use Params::ValidationCompiler ();
+use Specio::Declare;
+use Specio::Library::Builtins;
+use Test2::V0;
+use Test2::Plugin::DieOnFail;
+use Type::Params    ();
+use Types::Standard qw( ArrayRef Dict InstanceOf Int Optional slurpy );
+
+my $dt = DateTime->new( year => 2016 );
+
+{
+    my $pvc_moose = Params::ValidationCompiler::validation_for(
+        params => [
+            { type => find_type_constraint('Int') },
+            { type => find_type_constraint('ArrayRef') },
+            { type => class_type('DateTime'), optional => 1 },
+        ],
+    );
+
+    sub pvc_moose {
+        return $pvc_moose->(@_);
+    }
+}
+
+{
+    is(
+        dies {
+            pvc_moose( 42, [ 1, 2, 3 ], $dt );
+        },
+        undef,
+    );
+    is(
+        dies {
+            pvc_moose( 42, [ 1, 2, 3 ] );
+        },
+        undef,
+    );
+    ok(
+        dies {
+            pvc_moose(
+                42,
+                [ 1, 2, 3 ],
+                { year => 2016 }
+            );
+        }
+    );
+}
+
+sub call_pvc_moose_lives {
+    pvc_moose( 42, [ 1, 2, 3 ], $dt );
+    pvc_moose( 42, [ 1, 2, 3 ] );
+}
+
+sub call_pvc_moose_dies {
+    eval { pvc_moose( 42, [ 1, 2, 3 ], { year => 2016 } ); };
+}
+
+{
+    my $pvc_tt = Params::ValidationCompiler::validation_for(
+        params => [
+            { type => Int },
+            { type => ArrayRef },
+            { type => InstanceOf ['DateTime'], optional => 1 },
+        ],
+    );
+
+    sub pvc_tt {
+        return $pvc_tt->(@_);
+    }
+}
+
+{
+    is(
+        dies {
+            pvc_tt( 42, [ 1, 2, 3 ], $dt );
+        },
+        undef,
+    );
+    is(
+        dies {
+            pvc_tt( 42, [ 1, 2, 3 ] );
+        },
+        undef,
+    );
+    ok(
+        dies {
+            pvc_tt( 42, [ 1, 2, 3 ], { year => 2016 } );
+        }
+    );
+}
+
+sub call_pvc_tt_lives {
+    pvc_tt( 42, [ 1, 2, 3 ], $dt );
+    pvc_tt( 42, [ 1, 2, 3 ] );
+}
+
+sub call_pvc_tt_dies {
+    eval { pvc_tt( 42, [ 1, 2, 3 ], { year => 2016 } ) };
+}
+
+{
+    my $pvc_specio = Params::ValidationCompiler::validation_for(
+        params => [
+            { type => t('Int') },
+            { type => t('ArrayRef') },
+            { type => object_isa_type('DateTime'), optional => 1 },
+        ],
+    );
+
+    sub pvc_specio {
+        return $pvc_specio->(@_);
+    }
+}
+
+{
+    is(
+        dies {
+            pvc_specio( 42, [ 1, 2, 3 ], $dt );
+        },
+        undef,
+    );
+    is(
+        dies {
+            pvc_specio( 42, [ 1, 2, 3 ] );
+        },
+        undef,
+    );
+    ok(
+        dies {
+            pvc_specio(
+                42, [ 1, 2, 3 ],
+                { year => 2016 }
+            );
+        }
+    );
+}
+
+sub call_pvc_specio_lives {
+    pvc_specio( 42, [ 1, 2, 3 ], $dt );
+    pvc_specio( 42, [ 1, 2, 3 ] );
+}
+
+sub call_pvc_specio_dies {
+    eval { pvc_specio( 42, [ 1, 2, 3 ], { year => 2016 } ); };
+}
+
+{
+    my @spec = (
+        { isa => find_type_constraint('Int') },
+        { isa => find_type_constraint('ArrayRef') },
+        { isa => class_type('DateTime'), optional => 1 },
+    );
+
+    sub mxpv {
+        return pos_validated_list( \@_, @spec );
+    }
+}
+
+{
+    is(
+        dies {
+            mxpv( 42, [ 1, 2, 3 ], $dt );
+        },
+        undef,
+    );
+    is(
+        dies {
+            mxpv( 42, [ 1, 2, 3 ] );
+        },
+        undef,
+    );
+    ok(
+        dies {
+            mxpv( 42, [ 1, 2, 3 ], { year => 2016 } );
+        }
+    );
+}
+
+sub call_mxpv_lives {
+    mxpv( 42, [ 1, 2, 3 ], $dt );
+    mxpv( 42, [ 1, 2, 3 ] );
+}
+
+sub call_mxpv_dies {
+    eval { mxpv( 42, [ 1, 2, 3 ], { year => 2016 } ) };
+}
+
+{
+    my $tp = Type::Params::compile(
+        Int,
+        ArrayRef,
+        Optional [ InstanceOf ['DateTime'] ],
+    );
+
+    sub tp {
+        return $tp->(@_);
+    }
+}
+
+{
+    is(
+        dies {
+            tp( 42, [ 1, 2, 3 ], $dt );
+        },
+        undef,
+    );
+    is(
+        dies {
+            tp( 42, [ 1, 2, 3 ] );
+        },
+        undef,
+    );
+    ok(
+        dies {
+            tp( 42, [ 1, 2, 3 ], { year => 2016 } );
+        }
+    );
+}
+
+sub call_tp_lives {
+    tp( 42, [ 1, 2, 3 ], $dt );
+    tp( 42, [ 1, 2, 3 ] );
+}
+
+sub call_tp_dies {
+    eval { tp( 42, [ 1, 2, 3 ], { year => 2016 } ) };
+}
+
+sub pv {
+    return validate_pos(
+        @_,
+        {
+            type  => SCALAR,
+            regex => qr/^\d+$/a,
+        },
+        { type => ARRAYREF },
+        { isa  => 'DateTime', optional => 1 },
+    );
+}
+
+{
+    is(
+        dies {
+            pv( 42, [ 1, 2, 3 ], $dt );
+        },
+        undef,
+    );
+    is(
+        dies {
+            pv( 42, [ 1, 2, 3 ] );
+        },
+        undef,
+    );
+    ok(
+        dies {
+            pv( 42, [ 1, 2, 3 ], { year => 2016 } );
+        }
+    );
+}
+
+sub call_pv_lives {
+    pv( 42, [ 1, 2, 3 ], $dt );
+    pv( 42, [ 1, 2, 3 ] );
+}
+
+sub call_pv_dies {
+    eval { pv( 42, [ 1, 2, 3 ], { year => 2016 } ) };
+}
+
+done_testing();
+
+cmpthese(
+    500000, {
+        pvc_moose_lives  => \&call_pvc_moose_lives,
+        pvc_tt_lives     => \&call_pvc_tt_lives,
+        pvc_specio_lives => \&call_pvc_specio_lives,
+        mxpv_lives       => \&call_mxpv_lives,
+        tp_lives         => \&call_tp_lives,
+        pv_lives         => \&call_pv_lives,
+    }
+);
+
+print "\n" or die $!;
+
+cmpthese(
+    50000, {
+        pvc_moose_dies  => \&call_pvc_moose_dies,
+        pvc_tt_dies     => \&call_pvc_tt_dies,
+        pvc_specio_dies => \&call_pvc_specio_dies,
+        mxpv_dies       => \&call_mxpv_dies,
+        tp_dies         => \&call_tp_dies,
+        pv_dies         => \&call_pv_dies,
+    },
+);
diff --git a/git/hooks/pre-commit.sh b/git/hooks/pre-commit.sh
new file mode 100755 (executable)
index 0000000..63595ad
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+status=0
+
+PRECIOUS=$(which precious)
+if [[ -z $PRECIOUS ]]; then
+    PRECIOUS=./bin/precious
+fi
+
+"$PRECIOUS" lint -s
+if (( $? != 0 )); then
+    status+=1
+fi
+
+exit $status
diff --git a/git/setup.pl b/git/setup.pl
new file mode 100755 (executable)
index 0000000..8c99cb8
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+use Cwd qw( abs_path );
+
+symlink_hook('pre-commit');
+
+sub symlink_hook {
+    my $hook = shift;
+
+    my $dot  = ".git/hooks/$hook";
+    my $file = "git/hooks/$hook.sh";
+    my $link = "../../$file";
+
+    if ( -e $dot ) {
+        if ( -l $dot ) {
+            return if readlink $dot eq $link;
+        }
+        warn "You already have a hook at $dot!\n";
+        return;
+    }
+
+    symlink $link, $dot
+        or die "Could not link $dot => $link: $!";
+}
diff --git a/lib/Params/ValidationCompiler.pm b/lib/Params/ValidationCompiler.pm
new file mode 100644 (file)
index 0000000..a929f24
--- /dev/null
@@ -0,0 +1,338 @@
+package Params::ValidationCompiler;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.31';
+
+use Params::ValidationCompiler::Compiler;
+
+use Exporter qw( import );
+
+our @EXPORT_OK = qw( compile source_for validation_for );
+
+sub validation_for {
+    return Params::ValidationCompiler::Compiler->new(@_)->subref;
+}
+
+## no critic (TestingAndDebugging::ProhibitNoWarnings)
+no warnings 'once';
+*compile = \&validation_for;
+## use critic
+
+sub source_for {
+    return Params::ValidationCompiler::Compiler->new(@_)->source;
+}
+
+1;
+
+# ABSTRACT: Build an optimized subroutine parameter validator once, use it forever
+
+__END__
+
+=pod
+
+=encoding UTF-8
+
+=head1 NAME
+
+Params::ValidationCompiler - Build an optimized subroutine parameter validator once, use it forever
+
+=head1 VERSION
+
+version 0.31
+
+=head1 SYNOPSIS
+
+    use Types::Standard qw( Int Str );
+    use Params::ValidationCompiler qw( validation_for );
+
+    {
+        my $validator = validation_for(
+            params => {
+                foo => { type => Int },
+                bar => {
+                    type     => Str,
+                    optional => 1,
+                },
+                baz => {
+                    type    => Int,
+                    default => 42,
+                },
+            },
+        );
+
+        sub foo {
+            my %args = $validator->(@_);
+        }
+    }
+
+    {
+        my $validator = validation_for(
+            params => [
+                { type => Int },
+                {
+                    type     => Str,
+                    optional => 1,
+                },
+            ],
+        );
+
+        sub bar {
+            my ( $int, $str ) = $validator->(@_);
+        }
+    }
+
+    {
+        my $validator = validation_for(
+            params => [
+                foo => { type => Int },
+                bar => {
+                    type     => Str,
+                    optional => 1,
+                },
+            ],
+            named_to_list => 1,
+        );
+
+        sub baz {
+            my ( $foo, $bar ) = $validator->(@_);
+        }
+    }
+
+=head1 DESCRIPTION
+
+This module creates a customized, highly efficient parameter checking
+subroutine. It can handle named or positional parameters, and can return the
+parameters as key/value pairs or a list of values.
+
+In addition to type checks, it also supports parameter defaults, optional
+parameters, and extra "slurpy" parameters.
+
+=for Pod::Coverage compile
+
+=head1 PARAMETERS
+
+This module has two options exports, C<validation_for> and C<source_for>. Both
+of these subs accept the same options:
+
+=head2 params
+
+An arrayref or hashref containing a parameter specification.
+
+If you pass a hashref then the generated validator sub will expect named
+parameters. The C<params> value should be a hashref where the parameter names
+are keys and the specs are the values.
+
+If you pass an arrayref and C<named_to_list> is false, the validator will
+expect positional params. Each element of the C<params> arrayref should be a
+parameter spec.
+
+If you pass an arrayref and C<named_to_list> is true, the validator will expect
+named params, but will return a list of values. In this case the arrayref
+should contain a I<list> of key/value pairs, where parameter names are the keys
+and the specs are the values.
+
+Each spec can contain either a boolean or hashref. If the spec is a boolean,
+this indicates required (true) or optional (false).
+
+The spec hashref accepts the following keys:
+
+=over 4
+
+=item * type
+
+A type object. This can be a L<Moose> type (from L<Moose> or L<MooseX::Types>),
+a L<Type::Tiny> type, or a L<Specio> type.
+
+If the type has coercions, those will always be used.
+
+=item * default
+
+This can either be a simple (non-reference) scalar or a subroutine reference.
+The sub ref will be called without any arguments (for now).
+
+=item * optional
+
+A boolean indicating whether or not the parameter is optional. By default,
+parameters are required unless you provide a default.
+
+=back
+
+=head2 slurpy
+
+If this is a simple true value, then the generated subroutine accepts
+additional arguments not specified in C<params>. By default, extra arguments
+cause an exception.
+
+You can also pass a type constraint here, in which case all extra arguments
+must be values of the specified type.
+
+=head2 named_to_list
+
+If this is true, the generated subroutine will expect a list of key-value pairs
+or a hashref and it will return a list containing only values. The C<params>
+you pass must be a arrayref of key-value pairs. The order of these pairs
+determines the order in which values are returned.
+
+You cannot combine C<slurpy> with C<named_to_list> as there is no way to know
+how to order the extra return values.
+
+=head2 return_object
+
+If this is true, the generated subroutine will return an object instead of a
+hashref. You cannot set this option to true if you set either or C<slurpy> or
+C<named_to_list>.
+
+The object's methods correspond to the parameter names passed to the
+subroutine. While calling methods on an object is slower than accessing a
+hashref, the advantage is that if you typo a parameter name you'll get a
+helpful error.
+
+If you have L<Class::XSAccessor> installed then this will be used to create the
+class's methods, which makes it fairly fast.
+
+The returned object is in a generated class. Do not rely on this class name
+being anything in specific, and don't check this object using C<isa>, C<DOES>,
+or anything similar.
+
+When C<return_object> is true, the parameter spec hashref also accepts to the
+following additional keys:
+
+=over 4
+
+=item * getter
+
+Use this to set an explicit getter method name for the parameter. By default
+the method name will be the same as the parameter name. Note that if the
+parameter name is not a valid sub name, then you will get an error compiling
+the validation sub unless you specify a getter for the parameter.
+
+=item * predicate
+
+Use this to ask for a predicate method to be created for this parameter. The
+predicate method returns true if the parameter was passed and false if it
+wasn't. Note that this is only useful for optional parameters, but you can ask
+for a predicate for any parameter.
+
+=back
+
+=head1 EXPORTS
+
+The exported subs are:
+
+=head2 validation_for(...)
+
+This returns a subroutine that implements the specific parameter checking. This
+subroutine expects to be given the parameters to validate in C<@_>. If all the
+parameters are valid, it will return the validated parameters (with defaults as
+appropriate), either as a list of key-value pairs or as a list of just values.
+If any of the parameters are invalid it will throw an exception.
+
+For validators expected named params, the generated subroutine accepts either a
+list of key-value pairs or a single hashref. Otherwise the validator expects a
+list of values.
+
+For now, you must shift off the invocant yourself.
+
+This subroutine accepts the following additional parameters:
+
+=over 4
+
+=item * name
+
+If this is given, then the generated subroutine will be named using
+L<Sub::Util>. This is strongly recommended as it makes it possible to
+distinguish different check subroutines when profiling or in stack traces.
+
+This name will also be used in some exception messages, even if L<Sub::Util> is
+not available.
+
+Note that you must install L<Sub::Util> yourself separately, as it is not
+required by this distribution, in order to avoid requiring a compiler.
+
+=item * name_is_optional
+
+If this is true, then the name is ignored when C<Sub::Util> is not installed.
+If this is false, then passing a name when L<Sub::Util> cannot be loaded causes
+an exception.
+
+This is useful for CPAN modules where you want to set a name if you can, but
+you do not want to add a prerequisite on L<Sub::Util>.
+
+=item * debug
+
+Sets the C<EVAL_CLOSURE_PRINT_SOURCE> environment variable to true before
+calling C<Eval::Closure::eval_closure()>. This causes the source of the
+subroutine to be printed before it's C<eval>'d.
+
+=back
+
+=head2 source_for(...)
+
+This returns a two element list. The first is a string containing the source
+code for the generated sub. The second is a hashref of "environment" variables
+to be used when generating the subroutine. These are the arguments that are
+passed to L<Eval::Closure>.
+
+=head1 SUPPORT
+
+Bugs may be submitted at L<https://github.com/houseabsolute/Params-ValidationCompiler/issues>.
+
+=head1 SOURCE
+
+The source code repository for Params-ValidationCompiler can be found at L<https://github.com/houseabsolute/Params-ValidationCompiler>.
+
+=head1 DONATIONS
+
+If you'd like to thank me for the work I've done on this module, please
+consider making a "donation" to me via PayPal. I spend a lot of free time
+creating free software, and would appreciate any support you'd care to offer.
+
+Please note that B<I am not suggesting that you must do this> in order for me
+to continue working on this particular software. I will continue to do so,
+inasmuch as I have in the past, for as long as it interests me.
+
+Similarly, a donation made in this way will probably not make me work on this
+software much more, unless I get so many donations that I can consider working
+on free software full time (let's all have a chuckle at that together).
+
+To donate, log into PayPal and send money to autarch@urth.org, or use the
+button at L<https://houseabsolute.com/foss-donations/>.
+
+=head1 AUTHOR
+
+Dave Rolsky <autarch@urth.org>
+
+=head1 CONTRIBUTORS
+
+=for stopwords Gregory Oschwald Tomasz Konojacki
+
+=over 4
+
+=item *
+
+Gregory Oschwald <goschwald@maxmind.com>
+
+=item *
+
+Gregory Oschwald <oschwald@gmail.com>
+
+=item *
+
+Tomasz Konojacki <me@xenu.pl>
+
+=back
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is Copyright (c) 2016 - 2023 by Dave Rolsky.
+
+This is free software, licensed under:
+
+  The Artistic License 2.0 (GPL Compatible)
+
+The full text of the license can be found in the
+F<LICENSE> file included with this distribution.
+
+=cut
diff --git a/lib/Params/ValidationCompiler/Compiler.pm b/lib/Params/ValidationCompiler/Compiler.pm
new file mode 100644 (file)
index 0000000..a9ac181
--- /dev/null
@@ -0,0 +1,1078 @@
+package Params::ValidationCompiler::Compiler;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.31';
+
+use Carp            qw( croak );
+use Eval::Closure   qw( eval_closure );
+use List::Util 1.29 qw( pairkeys pairvalues );
+use Params::ValidationCompiler::Exceptions;
+use Scalar::Util qw( blessed looks_like_number reftype );
+use overload     ();
+use B            qw( perlstring );
+
+our @CARP_NOT = ( 'Params::ValidationCompiler', __PACKAGE__ );
+
+BEGIN {
+    ## no critic (Variables::RequireInitializationForLocalVars)
+    local $@;
+    my $has_sub_util = eval {
+        require Sub::Util;
+        Sub::Util->VERSION(1.40);
+        Sub::Util->import('set_subname');
+        1;
+    };
+
+    sub HAS_SUB_UTIL () {$has_sub_util}
+
+    unless ($has_sub_util) {
+        *set_subname = sub {
+            croak
+                'Cannot name a generated validation subroutine. Please install Sub::Util.';
+        };
+    }
+
+    my $has_cxsa = eval {
+        require Class::XSAccessor;
+        Class::XSAccessor->VERSION(1.17);
+        1;
+    };
+
+    sub HAS_CXSA {$has_cxsa}
+}
+
+my %known
+    = map { $_ => 1 }
+    qw( debug name name_is_optional named_to_list params return_object slurpy );
+
+# I'd rather use Moo here but I want to make things relatively high on the
+# CPAN river like DateTime use this distro, so reducing deps is important.
+sub new {
+    my $class = shift;
+    my %p     = @_;
+
+    unless ( exists $p{params} ) {
+        croak
+            q{You must provide a "params" parameter when creating a parameter validator};
+    }
+
+    if ( ref $p{params} eq 'HASH' ) {
+        croak q{The "params" hashref must contain at least one key-value pair}
+            unless %{ $p{params} };
+
+        croak
+            q{"named_to_list" must be used with arrayref params containing key-value pairs}
+            if $p{named_to_list};
+
+        $class->_validate_param_spec($_) for values %{ $p{params} };
+    }
+    elsif ( ref $p{params} eq 'ARRAY' ) {
+        croak q{The "params" arrayref must contain at least one element}
+            unless @{ $p{params} };
+
+        croak q{You can only use "return_object" with named params}
+            if $p{return_object};
+
+        my @specs
+            = $p{named_to_list}
+            ? pairvalues @{ $p{params} }
+            : @{ $p{params} };
+
+        $class->_validate_param_spec($_) for @specs;
+    }
+    else {
+        my $type = _describe( $p{params} );
+        croak
+            qq{The "params" parameter when creating a parameter validator must be a hashref or arrayref, you passed $type};
+    }
+
+    if ( $p{named_to_list} && $p{slurpy} ) {
+        croak q{You cannot use "named_to_list" and "slurpy" together};
+    }
+
+    if ( exists $p{name} && ( !defined $p{name} || ref $p{name} ) ) {
+        my $type = _describe( $p{name} );
+        croak
+            qq{The "name" parameter when creating a parameter validator must be a scalar, you passed $type};
+    }
+
+    if ( $p{return_object} && $p{slurpy} ) {
+        croak q{You cannot use "return_object" and "slurpy" together};
+    }
+
+    my @unknown = sort grep { !$known{$_} } keys %p;
+    if (@unknown) {
+        croak
+            "You passed unknown parameters when creating a parameter validator: [@unknown]";
+    }
+
+    my $self = bless \%p, $class;
+
+    $self->{_source} = [];
+    $self->{_env}    = {};
+
+    return $self;
+}
+
+sub _describe {
+    my $thing = shift;
+
+    if ( !defined $thing ) {
+        return 'an undef';
+    }
+    elsif ( my $class = blessed $thing ) {
+        my $article = $class =~ /^[aeiou]/i ? 'an' : 'a';
+        return "$article $class object";
+    }
+    elsif ( ref $thing ) {
+        my $ref     = lc ref $thing;
+        my $article = $ref =~ /^[aeiou]/i ? 'an' : 'a';
+        return "$article $ref" . 'ref';
+    }
+
+    return 'a scalar';
+}
+
+{
+    my %known_keys = (
+        default   => 1,
+        getter    => 1,
+        optional  => 1,
+        predicate => 1,
+        type      => 1,
+    );
+
+    sub _validate_param_spec {
+        shift;
+        my $spec = shift;
+
+        my $ref = ref $spec;
+        return unless $ref;
+
+        croak
+            "Specifications must be a scalar or hashref, but received a $ref"
+            unless $ref eq 'HASH';
+
+        my @unknown = sort grep { !$known_keys{$_} } keys %{$spec};
+        if (@unknown) {
+            croak "Specification contains unknown keys: [@unknown]";
+        }
+    }
+}
+
+sub name      { $_[0]->{name} }
+sub _has_name { exists $_[0]->{name} }
+
+sub _name_is_optional { $_[0]->{name_is_optional} }
+
+# I have no idea why critic thinks _caller isn't used.
+
+## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
+sub _caller { $_[0]->{caller} }
+## use critic
+sub _has_caller { exists $_[0]->{caller} }
+
+sub params { $_[0]->{params} }
+
+sub slurpy { $_[0]->{slurpy} }
+
+sub _source { $_[0]->{_source} }
+
+sub _env { $_[0]->{_env} }
+
+sub named_to_list { $_[0]->{named_to_list} }
+
+sub return_object { $_[0]->{return_object} }
+
+sub _inlineable_name {
+    return defined $_[0]->{name}
+        ? $_[0]->{name}
+        : 'an un-named validation subroutine';
+}
+
+sub _any_type_has_coercion {
+    my $self = shift;
+
+    return $self->{_has_coercion} if exists $self->{_has_coercion};
+
+    for my $type ( $self->_types ) {
+
+        # Specio
+        if ( $type->can('has_coercions') && $type->has_coercions ) {
+            return $self->{_has_coercion} = 1;
+        }
+
+        # Moose and Type::Tiny
+        elsif ( $type->can('has_coercion') && $type->has_coercion ) {
+            return $self->{_has_coercion} = 1;
+        }
+    }
+
+    return $self->{_has_coercion} = 0;
+}
+
+sub _types {
+    my $self = shift;
+
+    my @types;
+    if ( ref $self->params eq 'HASH' ) {
+        @types = map { $_->{type} || () }
+            grep { ref $_ } values %{ $self->params };
+    }
+    elsif ( ref $self->params eq 'ARRAY' ) {
+        if ( $self->named_to_list ) {
+            my %p = @{ $self->params };
+            @types = map { $_->{type} || () } grep { ref $_ } values %p;
+        }
+        else {
+            @types
+                = map { $_->{type} || () } grep { ref $_ } @{ $self->params };
+        }
+    }
+
+    push @types, $self->slurpy if $self->slurpy && ref $self->slurpy;
+
+    return @types;
+}
+
+sub subref {
+    my $self = shift;
+
+    $self->_compile;
+
+    local $ENV{EVAL_CLOSURE_PRINT_SOURCE} = 1 if $self->{debug};
+    my $sub = eval_closure(
+        source      => 'sub { ' . ( join "\n", @{ $self->_source } ) . ' };',
+        environment => $self->_env,
+    );
+
+    if ( $self->_has_name ) {
+        my $caller = $self->_has_caller ? $self->_caller : caller(1);
+        my $name   = join '::', $caller, $self->name;
+
+        return $sub if $self->_name_is_optional && !HAS_SUB_UTIL;
+        set_subname( $name, $sub );
+    }
+
+    return $sub;
+}
+
+sub source {
+    my $self = shift;
+
+    $self->_compile;
+    return (
+        ( join "\n", @{ $self->_source } ),
+        $self->_env,
+    );
+}
+
+sub _compile {
+    my $self = shift;
+
+    if ( ref $self->params eq 'HASH' ) {
+        $self->_compile_named_args_check;
+    }
+    elsif ( ref $self->params eq 'ARRAY' ) {
+        if ( $self->named_to_list ) {
+            $self->_compile_named_args_list_check;
+        }
+        else {
+            $self->_compile_positional_args_check;
+        }
+    }
+}
+
+sub _compile_named_args_check {
+    my $self = shift;
+
+    $self->_compile_named_args_check_body( $self->params );
+
+    if ( $self->return_object ) {
+        push @{ $self->_source }, $self->_add_return_named_args_object;
+    }
+    else {
+        push @{ $self->_source }, 'return %args;';
+    }
+
+    return;
+}
+
+{
+    my $class_id = 0;
+
+    sub _add_return_named_args_object {
+        my $self = shift;
+
+        my $params = $self->params;
+        my %getters;
+        my %predicates;
+        for my $p ( keys %{$params} ) {
+            $getters{
+                ref $params->{$p} && exists $params->{$p}{getter}
+                ? $params->{$p}{getter}
+                : $p
+            } = $p;
+            $predicates{ $params->{$p}{predicate} } = $p
+                if ref $params->{$p} && exists $params->{$p}{predicate};
+        }
+
+        my $use_cxsa = HAS_CXSA && !$ENV{TEST_NAMED_ARGS_OBJECT_WITHOUT_CXSA};
+        my $class    = sprintf(
+            '%s::OO::Args%d::%s',
+            __PACKAGE__,
+            $class_id++,
+            $use_cxsa ? 'XS' : 'PP',
+        );
+
+        if ($use_cxsa) {
+            $self->_create_cxsa_return_class(
+                $class,
+                \%getters,
+                \%predicates,
+            );
+        }
+        else {
+            $self->_create_pp_return_class( $class, \%getters, \%predicates );
+        }
+
+        return sprintf( 'bless \%%args, %s', perlstring($class) );
+    }
+}
+
+sub _create_cxsa_return_class {
+    my $self       = shift;
+    my $class      = shift;
+    my $getters    = shift;
+    my $predicates = shift;
+
+    Class::XSAccessor->import(
+        redefine          => 1,
+        class             => $class,
+        getters           => $getters,
+        exists_predicates => $predicates,
+    );
+
+    return;
+}
+
+sub _create_pp_return_class {
+    my $self       = shift;
+    my $class      = shift;
+    my $getters    = shift;
+    my $predicates = shift;
+
+    my @source = sprintf( 'package %s;', $class );
+    for my $sub ( keys %{$getters} ) {
+        push @source,
+            sprintf(
+            'sub %s { return $_[0]->{%s} }', $sub,
+            perlstring( $getters->{$sub} )
+            );
+    }
+    for my $sub ( keys %{$predicates} ) {
+        push @source,
+            sprintf(
+            'sub %s { return exists $_[0]->{%s} }', $sub,
+            perlstring( $predicates->{$sub} )
+            );
+    }
+    push @source, q{1;};
+    ## no critic (BuiltinFunctions::ProhibitStringyEval, ErrorHandling::RequireCheckingReturnValueOfEval)
+    eval join q{}, @source
+        or die $@;
+
+    return;
+}
+
+sub _compile_named_args_list_check {
+    my $self = shift;
+
+    $self->_compile_named_args_check_body( { @{ $self->params } } );
+
+    my @keys = map { perlstring($_) } pairkeys @{ $self->params };
+
+    # If we don't handle the one-key case specially we end up getting a
+    # warning like "Scalar value @args{"bar"} better written as $args{"bar"}
+    # at ..."
+    if ( @keys == 1 ) {
+        push @{ $self->_source }, "return \$args{$keys[0]};";
+    }
+    else {
+        my $keys_str = join q{, }, @keys;
+        push @{ $self->_source }, "return \@args{$keys_str};";
+    }
+
+    return;
+}
+
+sub _compile_named_args_check_body {
+    my $self   = shift;
+    my $params = shift;
+
+    push @{ $self->_source }, $self->_set_named_args_hash;
+
+    for my $name ( sort keys %{$params} ) {
+        my $spec = $params->{$name};
+        $spec = { optional => !$spec } unless ref $spec;
+
+        my $qname  = perlstring($name);
+        my $access = "\$args{$qname}";
+
+        # We check exists $spec->{optional} so as not to blow up on a
+        # restricted hash.
+        $self->_add_check_for_required_named_param( $access, $name )
+            unless ( exists $spec->{optional} && $spec->{optional} )
+            || exists $spec->{default};
+
+        $self->_add_named_default_assignment(
+            $access,
+            $name,
+            $spec->{default}
+        ) if exists $spec->{default};
+
+        # Same issue with restricted hashes here.
+        $self->_add_type_check( $access, $name, $spec )
+            if exists $spec->{type} && $spec->{type};
+    }
+
+    if ( $self->slurpy ) {
+        $self->_add_check_for_extra_hash_param_types( $self->slurpy, $params )
+            if ref $self->slurpy;
+    }
+    else {
+        $self->_add_check_for_extra_hash_params($params);
+    }
+
+    return;
+}
+
+sub _set_named_args_hash {
+    my $self = shift;
+
+    push @{ $self->_source },
+        sprintf( <<'EOF', ( $self->_inlineable_name ) x 4 );
+my %%args;
+if ( @_ %% 2 == 0 ) {
+    %%args = @_;
+}
+elsif ( @_ == 1 ) {
+    if ( ref $_[0] ) {
+        if ( Scalar::Util::blessed( $_[0] ) ) {
+            if ( overload::Overloaded( $_[0] )
+                && defined overload::Method( $_[0], '%%{}' ) ) {
+
+                %%args = %%{ $_[0] };
+            }
+            else {
+                Params::ValidationCompiler::Exception::BadArguments->throw(
+                    message =>
+                        'Expected a hash or hash reference but a single object argument was passed to %s',
+                    show_trace => 1,
+                );
+            }
+        }
+        elsif ( ref $_[0] eq 'HASH' ) {
+            %%args = %%{ $_[0] };
+        }
+        else {
+            Params::ValidationCompiler::Exception::BadArguments->throw(
+                message =>
+                    'Expected a hash or hash reference but a single '
+                    . ( ref $_[0] )
+                    . ' reference argument was passed to %s',
+                show_trace => 1,
+            );
+        }
+    }
+    else {
+        Params::ValidationCompiler::Exception::BadArguments->throw(
+            message =>
+                'Expected a hash or hash reference but a single non-reference argument was passed to %s',
+            show_trace => 1,
+        );
+    }
+}
+else {
+    Params::ValidationCompiler::Exception::BadArguments->throw(
+        message =>
+            'Expected a hash or hash reference but an odd number of arguments was passed to %s',
+        show_trace => 1,
+    );
+}
+EOF
+
+    return;
+}
+
+sub _add_check_for_required_named_param {
+    my $self   = shift;
+    my $access = shift;
+    my $name   = shift;
+
+    my $qname = perlstring($name);
+    push @{ $self->_source },
+        sprintf( <<'EOF', $access, $qname, $self->_inlineable_name, $qname );
+exists %s
+    or Params::ValidationCompiler::Exception::Named::Required->throw(
+    message    => %s . ' is a required parameter for %s',
+    parameter  => %s,
+    show_trace => 1,
+    );
+EOF
+
+    return;
+}
+
+sub _add_check_for_extra_hash_param_types {
+    my $self   = shift;
+    my $type   = shift;
+    my $params = shift;
+
+    $self->_env->{'%known'}
+        = { map { $_ => 1 } keys %{$params} };
+
+    # We need to set the name argument to something that won't conflict with
+    # names someone would actually use for a parameter.
+    my $check = join q{}, $self->_type_check(
+        '$args{$key}',
+        '__PCC extra parameters__',
+        $type,
+    );
+    push @{ $self->_source }, sprintf( <<'EOF', $check );
+for my $key ( grep { !$known{$_} } keys %%args ) {
+    %s;
+}
+EOF
+
+    return;
+}
+
+sub _add_check_for_extra_hash_params {
+    my $self   = shift;
+    my $params = shift;
+
+    $self->_env->{'%known'}
+        = { map { $_ => 1 } keys %{$params} };
+    push @{ $self->_source }, sprintf( <<'EOF', $self->_inlineable_name );
+my @extra = grep { !$known{$_} } keys %%args;
+if (@extra) {
+    my $u = join ', ', sort @extra;
+    Params::ValidationCompiler::Exception::Named::Extra->throw(
+        message    => "Found extra parameters passed to %s: [$u]",
+        parameters => \@extra,
+        show_trace => 1,
+    );
+}
+EOF
+
+    return;
+}
+
+sub _compile_positional_args_check {
+    my $self = shift;
+
+    my @specs = $self->_munge_and_check_positional_params;
+
+    my $first_optional_idx = -1;
+    for my $i ( 0 .. $#specs ) {
+        next unless $specs[$i]{optional} || exists $specs[$i]{default};
+        $first_optional_idx = $i;
+        last;
+    }
+
+    # If optional params start anywhere after the first parameter spec then we
+    # must require at least one param. If there are no optional params then
+    # they're all required.
+    $self->_add_check_for_required_positional_params(
+        $first_optional_idx == -1
+        ? ( scalar @specs )
+        : $first_optional_idx
+    ) if $first_optional_idx != 0;
+
+    $self->_add_check_for_extra_positional_params( scalar @specs )
+        unless $self->slurpy;
+
+    my $access_var = '$_';
+    my $return_var = '@_';
+    if ( $self->_any_type_has_coercion ) {
+        push @{ $self->_source }, 'my @copy = @_;';
+        $access_var = '$copy';
+        $return_var = '@copy';
+    }
+
+    for my $i ( 0 .. $#specs ) {
+        my $spec = $specs[$i];
+
+        my $name   = "Parameter $i";
+        my $access = sprintf( '%s[%i]', $access_var, $i );
+
+        $self->_add_positional_default_assignment(
+            $i,
+            $access,
+            $name,
+            $spec->{default}
+        ) if exists $spec->{default};
+
+        $self->_add_type_check( $access, $name, $spec )
+            if $spec->{type};
+    }
+
+    if ( ref $self->slurpy ) {
+        $self->_add_check_for_extra_positional_param_types(
+            scalar @specs,
+            $self->slurpy,
+            $access_var,
+        );
+    }
+
+    push @{ $self->_source }, sprintf( 'return %s;', $return_var );
+
+    return;
+}
+
+sub _munge_and_check_positional_params {
+    my $self = shift;
+
+    my @specs;
+    my $in_optional = 0;
+
+    for my $spec ( @{ $self->params } ) {
+        $spec = ref $spec ? $spec : { optional => !$spec };
+        if ( $spec->{optional} || exists $spec->{default} ) {
+            $in_optional = 1;
+        }
+        elsif ($in_optional) {
+            croak
+                'Parameter list contains an optional parameter followed by a required parameter.';
+        }
+
+        push @specs, $spec;
+    }
+
+    return @specs;
+}
+
+sub _add_check_for_required_positional_params {
+    my $self = shift;
+    my $min  = shift;
+
+    push @{ $self->_source },
+        sprintf( <<'EOF', ($min) x 2, $self->_inlineable_name, $min );
+if ( @_ < %d ) {
+    my $got = scalar @_;
+    my $got_n = @_ == 1 ? 'parameter' : 'parameters';
+    Params::ValidationCompiler::Exception::Positional::Required->throw(
+        message    => "Got $got $got_n but expected at least %d for %s",
+        minimum    => %d,
+        got        => scalar @_,
+        show_trace => 1,
+    );
+}
+EOF
+
+    return;
+}
+
+sub _add_check_for_extra_positional_param_types {
+    my $self       = shift;
+    my $max        = shift;
+    my $type       = shift;
+    my $access_var = shift;
+
+    # We need to set the name argument to something that won't conflict with
+    # names someone would actually use for a parameter.
+    my $check = join q{}, $self->_type_check(
+        sprintf( '%s[$i]', $access_var ),
+        '__PCC extra parameters__',
+        $type,
+    );
+    push @{ $self->_source }, sprintf( <<'EOF', $max, $max, $check );
+if ( @_ > %d ) {
+    for my $i ( %d .. $#_ ) {
+        %s;
+    }
+}
+EOF
+
+    return;
+}
+
+sub _add_check_for_extra_positional_params {
+    my $self = shift;
+    my $max  = shift;
+
+    push @{ $self->_source },
+        sprintf( <<'EOF', ($max) x 2, $self->_inlineable_name, $max );
+if ( @_ > %d ) {
+    my $extra = @_ - %d;
+    my $extra_n = $extra == 1 ? 'parameter' : 'parameters';
+    Params::ValidationCompiler::Exception::Positional::Extra->throw(
+        message    => "Got $extra extra $extra_n for %s",
+        maximum    => %d,
+        got        => scalar @_,
+        show_trace => 1,
+    );
+}
+EOF
+
+    return;
+}
+
+sub _add_positional_default_assignment {
+    my $self     = shift;
+    my $position = shift;
+    my $access   = shift;
+    my $name     = shift;
+    my $default  = shift;
+
+    push @{ $self->_source }, "if ( \$#_ < $position ) {";
+    $self->_add_shared_default_assignment( $access, $name, $default );
+    push @{ $self->_source }, '}';
+
+    return;
+}
+
+sub _add_named_default_assignment {
+    my $self    = shift;
+    my $access  = shift;
+    my $name    = shift;
+    my $default = shift;
+
+    my $qname = perlstring($name);
+    push @{ $self->_source }, "unless ( exists \$args{$qname} ) {";
+    $self->_add_shared_default_assignment( $access, $name, $default );
+    push @{ $self->_source }, '}';
+
+    return;
+}
+
+sub _add_shared_default_assignment {
+    my $self    = shift;
+    my $access  = shift;
+    my $name    = shift;
+    my $default = shift;
+
+    my $qname = perlstring($name);
+
+    croak 'Default must be either a plain scalar or a subroutine reference'
+        if ref $default && reftype($default) ne 'CODE';
+
+    if ( ref $default ) {
+        push @{ $self->_source }, "$access = \$defaults{$qname}->();";
+        $self->_env->{'%defaults'}{$name} = $default;
+    }
+    else {
+        if ( defined $default ) {
+            if ( looks_like_number($default) ) {
+                push @{ $self->_source }, "$access = $default;";
+            }
+            else {
+                push @{ $self->_source },
+                    "$access = " . perlstring($default) . ';';
+            }
+        }
+        else {
+            push @{ $self->_source }, "$access = undef;";
+        }
+    }
+
+    return;
+}
+
+sub _add_type_check {
+    my $self   = shift;
+    my $access = shift;
+    my $name   = shift;
+    my $spec   = shift;
+
+    my $type = $spec->{type};
+    croak "Passed a type that is not an object for $name: $type"
+        unless blessed $type;
+
+    push @{ $self->_source }, sprintf( 'if ( exists %s ) {', $access )
+        if $spec->{optional};
+
+    push @{ $self->_source },
+        $self->_type_check( $access, $name, $spec->{type} );
+
+    push @{ $self->_source }, '}'
+        if $spec->{optional};
+
+    return;
+}
+
+sub _type_check {
+    my $self   = shift;
+    my $access = shift;
+    my $name   = shift;
+    my $type   = shift;
+
+    # Specio
+    return $type->can('can_inline_coercion_and_check')
+        ? $self->_add_specio_check( $access, $name, $type )
+
+        # Type::Tiny
+        : $type->can('inline_assert')
+        ? $self->_add_type_tiny_check( $access, $name, $type )
+
+        # Moose
+        : $type->can('can_be_inlined')
+        ? $self->_add_moose_check( $access, $name, $type )
+        : croak 'Unknown type object ' . ref $type;
+}
+
+# From reading through the Type::Tiny source, I can't see any cases where a
+# Type::Tiny type or coercion needs to provide any environment variables to
+# compile with.
+sub _add_type_tiny_check {
+    my $self   = shift;
+    my $access = shift;
+    my $name   = shift;
+    my $type   = shift;
+
+    my $qname = perlstring($name);
+
+    my @source;
+    if ( $type->has_coercion ) {
+        my $coercion = $type->coercion;
+        if ( $coercion->can_be_inlined ) {
+            push @source,
+                "$access = " . $coercion->inline_coercion($access) . ';';
+        }
+        else {
+            $self->_env->{'%tt_coercions'}{$name}
+                = $coercion->compiled_coercion;
+            push @source,
+                sprintf(
+                '%s = $tt_coercions{%s}->( %s );',
+                $access, $qname, $access,
+                );
+        }
+    }
+
+    if ( $type->can_be_inlined ) {
+        push @source,
+            $type->inline_assert($access);
+    }
+    else {
+        push @source,
+            sprintf(
+            '$types{%s}->assert_valid( %s );',
+            $qname, $access,
+            );
+        $self->_env->{'%types'}{$name} = $type;
+    }
+
+    return @source;
+}
+
+sub _add_specio_check {
+    my $self   = shift;
+    my $access = shift;
+    my $name   = shift;
+    my $type   = shift;
+
+    my $qname = perlstring($name);
+
+    my @source;
+
+    if ( $type->can_inline_coercion_and_check ) {
+        if ( $type->has_coercions ) {
+            my ( $source, $env ) = $type->inline_coercion_and_check($access);
+            push @source, sprintf( '%s = %s;', $access, $source );
+            $self->_add_to_environment(
+                sprintf(
+                    'The inline_coercion_and_check for %s ',
+                    $type->_description
+                ),
+                $env,
+            );
+        }
+        else {
+            my ( $source, $env ) = $type->inline_assert($access);
+            push @source, $source . ';';
+            $self->_add_to_environment(
+                sprintf(
+                    'The inline_assert for %s ',
+                    $type->_description
+                ),
+                $env,
+            );
+        }
+    }
+    else {
+        my @coercions = $type->coercions;
+        $self->_env->{'%specio_coercions'}{$name} = \@coercions;
+        for my $i ( 0 .. $#coercions ) {
+            my $c = $coercions[$i];
+            if ( $c->can_be_inlined ) {
+                push @source,
+                    sprintf(
+                    '%s = %s if %s;',
+                    $access,
+                    $c->inline_coercion($access),
+                    $c->from->inline_check($access)
+                    );
+                $self->_add_to_environment(
+                    sprintf(
+                        'The inline_coercion for %s ',
+                        $c->_description
+                    ),
+
+                    # This should really be public in Specio
+                    $c->_inline_environment,
+                );
+            }
+            else {
+                push @source,
+                    sprintf(
+                    '%s = $specio_coercions{%s}[%s]->coerce(%s) if $specio_coercions{%s}[%s]->from->value_is_valid(%s);',
+                    $access,
+                    $qname,
+                    $i,
+                    $access,
+                    $qname,
+                    $i,
+                    $access
+                    );
+            }
+        }
+
+        push @source,
+            sprintf(
+            '$types{%s}->validate_or_die(%s);',
+            $qname, $access,
+            );
+
+        $self->_env->{'%types'}{$name} = $type;
+    }
+
+    return @source;
+}
+
+sub _add_moose_check {
+    my $self   = shift;
+    my $access = shift;
+    my $name   = shift;
+    my $type   = shift;
+
+    my $qname = perlstring($name);
+
+    my @source;
+
+    if ( $type->has_coercion ) {
+        $self->_env->{'%moose_coercions'}{$name} = $type->coercion;
+        push @source,
+            sprintf(
+            '%s = $moose_coercions{%s}->coerce( %s );',
+            $access, $qname, $access,
+            );
+    }
+
+    $self->_env->{'%types'}{$name} = $type;
+
+    my $code = <<'EOF';
+if ( !%s ) {
+    my $type  = $types{%s};
+    my $param = %s;
+    my $value = %s;
+    my $msg   = $param . q{ failed with: } . $type->get_message($value);
+    die
+        Params::ValidationCompiler::Exception::ValidationFailedForMooseTypeConstraint
+        ->new(
+        message   => $msg,
+        parameter => $param,
+        value     => $value,
+        type      => $type,
+        );
+}
+EOF
+
+    my $check
+        = $type->can_be_inlined
+        ? $type->_inline_check($access)
+        : sprintf( '$types{%s}->check( %s )', $qname, $access );
+
+    push @source, sprintf(
+        $code,
+        $check,
+        $qname,
+        $qname,
+        $access,
+    );
+
+    if ( $type->can_be_inlined ) {
+        $self->_add_to_environment(
+            sprintf( 'The %s type', $type->name ),
+            $type->inline_environment,
+        );
+    }
+
+    return @source;
+}
+
+sub _add_to_environment {
+    my $self    = shift;
+    my $what    = shift;
+    my $new_env = shift;
+
+    my $env = $self->_env;
+    for my $key ( keys %{$new_env} ) {
+        if ( exists $env->{$key} ) {
+            croak sprintf(
+                      '%s has an inline environment variable named %s'
+                    . ' that conflicts with a variable already in the environment',
+                $what, $key
+            );
+        }
+        $self->_env->{$key} = $new_env->{$key};
+    }
+}
+
+1;
+
+# ABSTRACT: Object that implements the check subroutine compilation
+
+__END__
+
+=pod
+
+=encoding UTF-8
+
+=head1 NAME
+
+Params::ValidationCompiler::Compiler - Object that implements the check subroutine compilation
+
+=head1 VERSION
+
+version 0.31
+
+=for Pod::Coverage .*
+
+=head1 SUPPORT
+
+Bugs may be submitted at L<https://github.com/houseabsolute/Params-ValidationCompiler/issues>.
+
+=head1 SOURCE
+
+The source code repository for Params-ValidationCompiler can be found at L<https://github.com/houseabsolute/Params-ValidationCompiler>.
+
+=head1 AUTHOR
+
+Dave Rolsky <autarch@urth.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is Copyright (c) 2016 - 2023 by Dave Rolsky.
+
+This is free software, licensed under:
+
+  The Artistic License 2.0 (GPL Compatible)
+
+The full text of the license can be found in the
+F<LICENSE> file included with this distribution.
+
+=cut
diff --git a/lib/Params/ValidationCompiler/Exceptions.pm b/lib/Params/ValidationCompiler/Exceptions.pm
new file mode 100644 (file)
index 0000000..99e1e56
--- /dev/null
@@ -0,0 +1,111 @@
+package Params::ValidationCompiler::Exceptions;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.31';
+
+use Exception::Class (
+    'Params::ValidationCompiler::Exception::BadArguments',
+    'Params::ValidationCompiler::Exception::Named::Extra' => {
+        fields => ['parameters'],
+    },
+    'Params::ValidationCompiler::Exception::Named::Required' => {
+        fields => ['parameter'],
+    },
+    'Params::ValidationCompiler::Exception::Positional::Extra' => {
+        fields => [ 'got', 'maximum' ],
+    },
+    'Params::ValidationCompiler::Exception::Positional::Required' => {
+        fields => [ 'got', 'minimum' ],
+    },
+    'Params::ValidationCompiler::Exception::ValidationFailedForMooseTypeConstraint'
+        => {
+        fields => [qw( parameter value type )],
+        },
+);
+
+1;
+
+# ABSTRACT: Defines exceptions thrown by Params::ValidationCompiler
+
+__END__
+
+=pod
+
+=encoding UTF-8
+
+=head1 NAME
+
+Params::ValidationCompiler::Exceptions - Defines exceptions thrown by Params::ValidationCompiler
+
+=head1 VERSION
+
+version 0.31
+
+=head1 DESCRIPTION
+
+This module defines the following exceptions:
+
+=head2 Params::ValidationCompiler::Exception::BadArguments
+
+Exception thrown when @_ does not contain a hash or hashref.
+
+=head2 Params::ValidationCompiler::Exception::Named::Extra
+
+Exception thrown when @_ contains unexpected extra named arguments.
+
+=head2 Params::ValidationCompiler::Exception::Named::Required
+
+Exception thrown when a required named parameter is not passed.
+
+=head2 Params::ValidationCompiler::Exception::Positional::Extra
+
+Exception thrown when @_ contains unexpected extra arguments.
+
+=head2 Params::ValidationCompiler::Exception::Positional::Required
+
+Exception thrown when a required positional parameter is not passed.
+
+=head2 Params::ValidationCompiler::Exception::ValidationFailedForMooseTypeConstraint
+
+Exception thrown when a Moose type constraint check fails. This class provides
+the following methods:
+
+=head3 $e->parameter
+
+This returns a string describing the parameter, something like C<The 'foo'
+parameter> or C<Parameter #1>.
+
+=head3 $e->value
+
+This is the value that failed the type constraint check.
+
+=head3 $e->type
+
+This is the type constraint object that did not accept the value.
+
+=head1 SUPPORT
+
+Bugs may be submitted at L<https://github.com/houseabsolute/Params-ValidationCompiler/issues>.
+
+=head1 SOURCE
+
+The source code repository for Params-ValidationCompiler can be found at L<https://github.com/houseabsolute/Params-ValidationCompiler>.
+
+=head1 AUTHOR
+
+Dave Rolsky <autarch@urth.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is Copyright (c) 2016 - 2023 by Dave Rolsky.
+
+This is free software, licensed under:
+
+  The Artistic License 2.0 (GPL Compatible)
+
+The full text of the license can be found in the
+F<LICENSE> file included with this distribution.
+
+=cut
diff --git a/perlcriticrc b/perlcriticrc
new file mode 100644 (file)
index 0000000..1754348
--- /dev/null
@@ -0,0 +1,70 @@
+severity = 3
+verbose = 11
+theme = (core && (pbp || bugs || maintenance || cosmetic || complexity || security || tests)) || moose
+program-extensions = pl psgi t
+
+exclude = Subroutines::ProhibitCallsToUndeclaredSubs
+
+[BuiltinFunctions::ProhibitStringySplit]
+severity = 3
+
+[CodeLayout::RequireTrailingCommas]
+severity = 3
+
+[ControlStructures::ProhibitCStyleForLoops]
+severity = 3
+
+[InputOutput::RequireCheckedSyscalls]
+functions = :builtins
+exclude_functions = sleep
+severity = 3
+
+[RegularExpressions::ProhibitComplexRegexes]
+max_characters = 200
+
+[RegularExpressions::ProhibitUnusualDelimiters]
+severity = 3
+
+[Subroutines::ProhibitUnusedPrivateSubroutines]
+private_name_regex = _(?!build)\w+
+
+[TestingAndDebugging::ProhibitNoWarnings]
+allow = redefine
+
+[ValuesAndExpressions::ProhibitEmptyQuotes]
+severity = 3
+
+[ValuesAndExpressions::ProhibitInterpolationOfLiterals]
+severity = 3
+
+[ValuesAndExpressions::RequireUpperCaseHeredocTerminator]
+severity = 3
+
+[Variables::ProhibitPackageVars]
+add_packages = Carp Test::Builder
+
+[-Subroutines::RequireFinalReturn]
+
+# This incorrectly thinks signatures are prototypes.
+[-Subroutines::ProhibitSubroutinePrototypes]
+
+[-ErrorHandling::RequireCarping]
+
+# No need for /xsm everywhere
+[-RegularExpressions::RequireDotMatchAnything]
+[-RegularExpressions::RequireExtendedFormatting]
+[-RegularExpressions::RequireLineBoundaryMatching]
+
+# http://stackoverflow.com/questions/2275317/why-does-perlcritic-dislike-using-shift-to-populate-subroutine-variables
+[-Subroutines::RequireArgUnpacking]
+
+# "use v5.14" is more readable than "use 5.014"
+[-ValuesAndExpressions::ProhibitVersionStrings]
+
+# Explicitly returning undef is a _good_ thing in many cases, since it
+# prevents very common errors when using a sub in list context to construct a
+# hash and ending up with a missing value or key.
+[-Subroutines::ProhibitExplicitReturnUndef]
+
+# Sometimes I want to write "return unless $x > 4"
+[-ControlStructures::ProhibitNegativeExpressionsInUnlessAndUntilConditions]
diff --git a/perltidyrc b/perltidyrc
new file mode 100644 (file)
index 0000000..b54e60d
--- /dev/null
@@ -0,0 +1,22 @@
+-l=78
+-i=4
+-ci=4
+-se
+-b
+-bar
+-boc
+-vt=0
+-vtc=0
+-cti=0
+-pt=1
+-bt=1
+-sbt=1
+-bbt=1
+-nolq
+-npro
+-nsfs
+--blank-lines-before-packages=0
+--opening-hash-brace-right
+--no-outdent-long-comments
+--iterations=2
+-wbb="% + - * / x != == >= <= =~ !~ < > | & >= < = **= += *= &= <<= &&= -= /= |= >>= ||= .= %= ^= x="
diff --git a/precious.toml b/precious.toml
new file mode 100644 (file)
index 0000000..a311112
--- /dev/null
@@ -0,0 +1,52 @@
+exclude = [
+    ".build/**/*",
+    "Params-ValidationCompiler-*/**/*",
+    "blib/**/*",
+    "t/00-*",
+    "t/author-*",
+    "t/release-*",
+    "t/zzz-*",
+    "xt/**/*",
+]
+
+[commands.omegasort-gitignore]
+type = "both"
+include = "**/.gitignore"
+cmd = [ "omegasort", "--sort=path" ]
+lint_flags = "--check"
+tidy_flags = "--in-place"
+ok_exit_codes = 0
+lint_failure_exit_codes = 1
+expect_stderr = true
+
+[commands.perlcritic]
+type = "lint"
+include = [ "**/*.{pl,pm,t,psgi}" ]
+cmd = [ "perlcritic", "--profile=$PRECIOUS_ROOT/perlcriticrc" ]
+ok_exit_codes = 0
+lint_failure_exit_codes = 2
+
+[commands.perltidy]
+type = "both"
+include = [ "**/*.{pl,pm,t,psgi}" ]
+cmd = [ "perltidy", "--profile=$PRECIOUS_ROOT/perltidyrc" ]
+lint_flags = [ "--assert-tidy", "--no-standard-output", "--outfile=/dev/null" ]
+tidy_flags = [ "--backup-and-modify-in-place", "--backup-file-extension=/" ]
+ok_exit_codes = 0
+lint_failure_exit_codes = 2
+expect_stderr = true
+
+[commands.podchecker]
+type = "lint"
+include = [ "**/*.{pl,pm,pod}" ]
+cmd = [ "podchecker", "--warnings", "--warnings" ]
+ok_exit_codes = [ 0, 2 ]
+lint_failure_exit_codes = 1
+expect_stderr = true
+
+[commands.podtidy]
+type = "tidy"
+include = [ "**/*.{pl,pm,pod}" ]
+cmd = [ "podtidy", "--columns", "80", "--inplace", "--nobackup" ]
+ok_exit_codes = 0
+lint_failure_exit_codes = 1
diff --git a/t/00-report-prereqs.dd b/t/00-report-prereqs.dd
new file mode 100644 (file)
index 0000000..9514e72
--- /dev/null
@@ -0,0 +1,87 @@
+do { my $x = {
+       'configure' => {
+                        'requires' => {
+                                        'ExtUtils::MakeMaker' => '0'
+                                      }
+                      },
+       'develop' => {
+                      'requires' => {
+                                      'Capture::Tiny' => '0',
+                                      'Class::XSAccessor' => '1.17',
+                                      'Const::Fast' => '0.014',
+                                      'Encode' => '0',
+                                      'File::Spec' => '0',
+                                      'FindBin' => '0',
+                                      'Hash::Merge' => '0',
+                                      'Hash::Util' => '0',
+                                      'IO::Handle' => '0',
+                                      'IPC::Open3' => '0',
+                                      'List::AllUtils' => '0',
+                                      'Moose' => '2.0000',
+                                      'Perl::Critic' => '1.138',
+                                      'Perl::Critic::Moose' => '1.05',
+                                      'Perl::Tidy' => '20210111',
+                                      'Pod::Checker' => '1.74',
+                                      'Pod::Coverage::TrustPod' => '0',
+                                      'Pod::Tidy' => '0.10',
+                                      'Pod::Wordlist' => '0',
+                                      'Set::Scalar' => '0',
+                                      'Specio' => '0.14',
+                                      'Sub::Util' => '1.40',
+                                      'Test2::Bundle::Extended' => '0',
+                                      'Test2::Plugin::NoWarnings' => '0',
+                                      'Test2::Require::Perl' => '0',
+                                      'Test::CPAN::Changes' => '0.19',
+                                      'Test::CPAN::Meta::JSON' => '0.16',
+                                      'Test::EOL' => '0',
+                                      'Test::Mojibake' => '0',
+                                      'Test::More' => '0.96',
+                                      'Test::NoTabs' => '0',
+                                      'Test::Pod' => '1.41',
+                                      'Test::Pod::Coverage' => '1.08',
+                                      'Test::Portability::Files' => '0',
+                                      'Test::Spelling' => '0.12',
+                                      'Test::Synopsis' => '0',
+                                      'Test::Version' => '2.05',
+                                      'Type::Tiny' => '0',
+                                      'Type::Utils' => '0',
+                                      'perl' => '5.006'
+                                    }
+                    },
+       'runtime' => {
+                      'recommends' => {
+                                        'Class::XSAccessor' => '1.17',
+                                        'Sub::Util' => '1.40'
+                                      },
+                      'requires' => {
+                                      'B' => '0',
+                                      'Carp' => '0',
+                                      'Eval::Closure' => '0',
+                                      'Exception::Class' => '0',
+                                      'Exporter' => '0',
+                                      'List::Util' => '1.29',
+                                      'Scalar::Util' => '0',
+                                      'overload' => '0',
+                                      'strict' => '0',
+                                      'warnings' => '0'
+                                    }
+                    },
+       'test' => {
+                   'recommends' => {
+                                     'CPAN::Meta' => '2.120900'
+                                   },
+                   'requires' => {
+                                   'ExtUtils::MakeMaker' => '0',
+                                   'File::Spec' => '0',
+                                   'Hash::Util' => '0',
+                                   'Specio' => '0.14',
+                                   'Test2::Plugin::NoWarnings' => '0',
+                                   'Test2::Require::Module' => '0',
+                                   'Test2::V0' => '0',
+                                   'Test::More' => '1.302015',
+                                   'Test::Without::Module' => '0'
+                                 }
+                 }
+     };
+  $x;
+ }
\ No newline at end of file
diff --git a/t/00-report-prereqs.t b/t/00-report-prereqs.t
new file mode 100644 (file)
index 0000000..c3a94ca
--- /dev/null
@@ -0,0 +1,193 @@
+#!perl
+
+use strict;
+use warnings;
+
+# This test was generated by Dist::Zilla::Plugin::Test::ReportPrereqs 0.028
+
+use Test::More tests => 1;
+
+use ExtUtils::MakeMaker;
+use File::Spec;
+
+# from $version::LAX
+my $lax_version_re =
+    qr/(?: undef | (?: (?:[0-9]+) (?: \. | (?:\.[0-9]+) (?:_[0-9]+)? )?
+            |
+            (?:\.[0-9]+) (?:_[0-9]+)?
+        ) | (?:
+            v (?:[0-9]+) (?: (?:\.[0-9]+)+ (?:_[0-9]+)? )?
+            |
+            (?:[0-9]+)? (?:\.[0-9]+){2,} (?:_[0-9]+)?
+        )
+    )/x;
+
+# hide optional CPAN::Meta modules from prereq scanner
+# and check if they are available
+my $cpan_meta = "CPAN::Meta";
+my $cpan_meta_pre = "CPAN::Meta::Prereqs";
+my $HAS_CPAN_META = eval "require $cpan_meta; $cpan_meta->VERSION('2.120900')" && eval "require $cpan_meta_pre"; ## no critic
+
+# Verify requirements?
+my $DO_VERIFY_PREREQS = 1;
+
+sub _max {
+    my $max = shift;
+    $max = ( $_ > $max ) ? $_ : $max for @_;
+    return $max;
+}
+
+sub _merge_prereqs {
+    my ($collector, $prereqs) = @_;
+
+    # CPAN::Meta::Prereqs object
+    if (ref $collector eq $cpan_meta_pre) {
+        return $collector->with_merged_prereqs(
+            CPAN::Meta::Prereqs->new( $prereqs )
+        );
+    }
+
+    # Raw hashrefs
+    for my $phase ( keys %$prereqs ) {
+        for my $type ( keys %{ $prereqs->{$phase} } ) {
+            for my $module ( keys %{ $prereqs->{$phase}{$type} } ) {
+                $collector->{$phase}{$type}{$module} = $prereqs->{$phase}{$type}{$module};
+            }
+        }
+    }
+
+    return $collector;
+}
+
+my @include = qw(
+
+);
+
+my @exclude = qw(
+
+);
+
+# Add static prereqs to the included modules list
+my $static_prereqs = do './t/00-report-prereqs.dd';
+
+# Merge all prereqs (either with ::Prereqs or a hashref)
+my $full_prereqs = _merge_prereqs(
+    ( $HAS_CPAN_META ? $cpan_meta_pre->new : {} ),
+    $static_prereqs
+);
+
+# Add dynamic prereqs to the included modules list (if we can)
+my ($source) = grep { -f } 'MYMETA.json', 'MYMETA.yml';
+my $cpan_meta_error;
+if ( $source && $HAS_CPAN_META
+    && (my $meta = eval { CPAN::Meta->load_file($source) } )
+) {
+    $full_prereqs = _merge_prereqs($full_prereqs, $meta->prereqs);
+}
+else {
+    $cpan_meta_error = $@;    # capture error from CPAN::Meta->load_file($source)
+    $source = 'static metadata';
+}
+
+my @full_reports;
+my @dep_errors;
+my $req_hash = $HAS_CPAN_META ? $full_prereqs->as_string_hash : $full_prereqs;
+
+# Add static includes into a fake section
+for my $mod (@include) {
+    $req_hash->{other}{modules}{$mod} = 0;
+}
+
+for my $phase ( qw(configure build test runtime develop other) ) {
+    next unless $req_hash->{$phase};
+    next if ($phase eq 'develop' and not $ENV{AUTHOR_TESTING});
+
+    for my $type ( qw(requires recommends suggests conflicts modules) ) {
+        next unless $req_hash->{$phase}{$type};
+
+        my $title = ucfirst($phase).' '.ucfirst($type);
+        my @reports = [qw/Module Want Have/];
+
+        for my $mod ( sort keys %{ $req_hash->{$phase}{$type} } ) {
+            next if $mod eq 'perl';
+            next if grep { $_ eq $mod } @exclude;
+
+            my $file = $mod;
+            $file =~ s{::}{/}g;
+            $file .= ".pm";
+            my ($prefix) = grep { -e File::Spec->catfile($_, $file) } @INC;
+
+            my $want = $req_hash->{$phase}{$type}{$mod};
+            $want = "undef" unless defined $want;
+            $want = "any" if !$want && $want == 0;
+
+            my $req_string = $want eq 'any' ? 'any version required' : "version '$want' required";
+
+            if ($prefix) {
+                my $have = MM->parse_version( File::Spec->catfile($prefix, $file) );
+                $have = "undef" unless defined $have;
+                push @reports, [$mod, $want, $have];
+
+                if ( $DO_VERIFY_PREREQS && $HAS_CPAN_META && $type eq 'requires' ) {
+                    if ( $have !~ /\A$lax_version_re\z/ ) {
+                        push @dep_errors, "$mod version '$have' cannot be parsed ($req_string)";
+                    }
+                    elsif ( ! $full_prereqs->requirements_for( $phase, $type )->accepts_module( $mod => $have ) ) {
+                        push @dep_errors, "$mod version '$have' is not in required range '$want'";
+                    }
+                }
+            }
+            else {
+                push @reports, [$mod, $want, "missing"];
+
+                if ( $DO_VERIFY_PREREQS && $type eq 'requires' ) {
+                    push @dep_errors, "$mod is not installed ($req_string)";
+                }
+            }
+        }
+
+        if ( @reports ) {
+            push @full_reports, "=== $title ===\n\n";
+
+            my $ml = _max( map { length $_->[0] } @reports );
+            my $wl = _max( map { length $_->[1] } @reports );
+            my $hl = _max( map { length $_->[2] } @reports );
+
+            if ($type eq 'modules') {
+                splice @reports, 1, 0, ["-" x $ml, "", "-" x $hl];
+                push @full_reports, map { sprintf("    %*s %*s\n", -$ml, $_->[0], $hl, $_->[2]) } @reports;
+            }
+            else {
+                splice @reports, 1, 0, ["-" x $ml, "-" x $wl, "-" x $hl];
+                push @full_reports, map { sprintf("    %*s %*s %*s\n", -$ml, $_->[0], $wl, $_->[1], $hl, $_->[2]) } @reports;
+            }
+
+            push @full_reports, "\n";
+        }
+    }
+}
+
+if ( @full_reports ) {
+    diag "\nVersions for all modules listed in $source (including optional ones):\n\n", @full_reports;
+}
+
+if ( $cpan_meta_error || @dep_errors ) {
+    diag "\n*** WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING ***\n";
+}
+
+if ( $cpan_meta_error ) {
+    my ($orig_source) = grep { -f } 'MYMETA.json', 'MYMETA.yml';
+    diag "\nCPAN::Meta->load_file('$orig_source') failed with: $cpan_meta_error\n";
+}
+
+if ( @dep_errors ) {
+    diag join("\n",
+        "\nThe following REQUIRED prerequisites were not satisfied:\n",
+        @dep_errors,
+        "\n"
+    );
+}
+
+pass('Reported prereqs');
+
+# vim: ts=4 sts=4 sw=4 et:
diff --git a/t/default.t b/t/default.t
new file mode 100644 (file)
index 0000000..ae6327d
--- /dev/null
@@ -0,0 +1,44 @@
+use strict;
+use warnings;
+
+use Test2::V0;
+use Test2::Plugin::NoWarnings;
+
+use Params::ValidationCompiler qw( validation_for );
+
+{
+    my $sub = validation_for(
+        params => {
+            foo => { default => 42 },
+            bar => { default => undef },
+            baz => { default => 'string' },
+            buz => {
+                default => sub { [] }
+            },
+        },
+    );
+
+    is(
+        { $sub->() },
+        {
+            foo => 42,
+            bar => undef,
+            baz => 'string',
+            buz => [],
+        },
+        'all defaults are used when no values are passed'
+    );
+
+    is(
+        { $sub->( foo => 99 ) },
+        {
+            foo => 99,
+            bar => undef,
+            baz => 'string',
+            buz => [],
+        },
+        'defaults are not used when when a value is passed'
+    );
+}
+
+done_testing();
diff --git a/t/exceptions.t b/t/exceptions.t
new file mode 100644 (file)
index 0000000..806cffd
--- /dev/null
@@ -0,0 +1,25 @@
+use strict;
+use warnings;
+
+use Test2::V0;
+use Test2::Plugin::NoWarnings;
+use Test2::Require::Module 'Specio::Library::Builtins';
+
+use Params::ValidationCompiler qw( validation_for );
+use Specio::Library::Builtins;
+
+{
+    my $sub = validation_for(
+        params           => [ { type => t('Str') } ],
+        name             => 'test validator',
+        name_is_optional => 1,
+    );
+
+    like(
+        dies { $sub->( 'foo', 'bar' ) },
+        qr{Got 1 extra parameter for test validator.+called at .*t[\\/]exceptions\.t line \d+}s,
+        'exception includes stack trace',
+    );
+}
+
+done_testing();
diff --git a/t/moose.t b/t/moose.t
new file mode 100644 (file)
index 0000000..fa96deb
--- /dev/null
+++ b/t/moose.t
@@ -0,0 +1,147 @@
+## no critic (Moose::RequireCleanNamespace)
+use strict;
+use warnings;
+
+use Test2::V0;
+use Test2::Plugin::NoWarnings;
+use Test2::Require::Module 'Moose::Util::TypeConstraints' => '2.0000';
+
+use Params::ValidationCompiler qw( validation_for );
+use Moose::Util::TypeConstraints;
+
+my $moose_int = find_type_constraint('Int');
+subtest(
+    'type can be inlined',
+    sub {
+        _test_int_type($moose_int);
+    }
+);
+
+my $myint = subtype 'MyInt' => as 'Num' => where {/\A-?[0-9]+\z/};
+subtest(
+    'type cannot be inlined',
+    sub {
+        _test_int_type($myint);
+    }
+);
+
+subtest(
+    'type can be inlined but coercion cannot',
+    sub {
+        my $type = subtype 'ArrayRefInt', as 'ArrayRef[Int]';
+        coerce $type => from 'Int' => via { [$_] };
+
+        _test_int_to_arrayref_coercion($type);
+    }
+);
+
+subtest(
+    'neither type not coercion can be inlined',
+    sub {
+        my $type = subtype as 'ArrayRef[MyInt]';
+        coerce $type => from 'Int' => via { [$_] };
+
+        _test_int_to_arrayref_coercion($type);
+    }
+);
+
+# This tests that a type which provides an inline_environment actually has
+# that env available.
+subtest(
+    'enum type',
+    sub {
+        my $sub = validation_for(
+            params => {
+                foo => { type => enum [qw( red green blue )] },
+            },
+        );
+        is(
+            { $sub->( foo => 'red' ) },
+            { foo => 'red' },
+            'enum type is validated properly'
+        );
+    }
+);
+
+# This tests that a type which provides an inline_environment actually has
+# that env available.
+subtest(
+    'empty enum subtype',
+    sub {
+        my $enum    = enum [qw( red green blue )];
+        my $subtype = subtype( as $enum );
+        my $sub     = validation_for(
+            params => {
+                foo => { type => $subtype },
+            },
+        );
+        is(
+            { $sub->( foo => 'red' ) },
+            { foo => 'red' },
+            'enum type is validated properly'
+        );
+    }
+);
+
+done_testing();
+
+sub _test_int_type {
+    my $type = shift;
+
+    my $sub = validation_for(
+        params => {
+            foo => { type => $type },
+        },
+    );
+
+    is(
+        dies { $sub->( foo => 42 ) },
+        undef,
+        'lives when foo is an integer'
+    );
+
+    my $name = $type->name;
+    like(
+        dies { $sub->( foo => [] ) },
+        qr/\Qfoo failed with: Validation failed for '$name' with value \E(?:ARRAY|\[ +\])/,
+        'dies when foo is an arrayref'
+    );
+}
+
+sub _test_int_to_arrayref_coercion {
+    my $type = shift;
+
+    my $sub = validation_for(
+        params => {
+            foo => { type => $type },
+        },
+    );
+
+    is(
+        dies { $sub->( foo => 42 ) },
+        undef,
+        'lives when foo is an integer'
+    );
+
+    is(
+        dies { $sub->( foo => [ 42, 1 ] ) },
+        undef,
+        'lives when foo is an arrayref of integers'
+    );
+
+    my $name = $type->name;
+    like(
+        dies { $sub->( foo => {} ) },
+        qr/\QValidation failed for '$name' with value \E(?:HASH|\{ +\})/,
+        'dies when foo is a hashref'
+    );
+
+    my $pos = validation_for(
+        params => [ { type => $type } ],
+    );
+    is(
+        dies { $pos->(42) },
+        undef,
+        'lives when coercing a moose type with positional parameters'
+    );
+}
diff --git a/t/name-fails.t b/t/name-fails.t
new file mode 100644 (file)
index 0000000..46c5d53
--- /dev/null
@@ -0,0 +1,41 @@
+# HARNESS-NO-PRELOAD
+use strict;
+use warnings;
+
+use Test2::V0;
+use Test2::Plugin::NoWarnings;
+use Test::Without::Module qw( Sub::Util );
+
+use Params::ValidationCompiler qw( validation_for );
+
+{
+    my $e = dies {
+        validation_for(
+            name   => 'Check for X',
+            params => { foo => 1 },
+        );
+    };
+
+    like(
+        $e,
+        qr/\QCannot name a generated validation subroutine. Please install Sub::Util./,
+        'passing name when Sub::Util is not installed fails',
+    );
+}
+
+{
+
+    is(
+        dies {
+            validation_for(
+                name             => 'Check for X',
+                name_is_optional => 1,
+                params           => { foo => 1 },
+            );
+        },
+        undef,
+        'passing name and name_is_optional when Sub::Util is not installed lives'
+    );
+}
+
+done_testing();
diff --git a/t/name.t b/t/name.t
new file mode 100644 (file)
index 0000000..f7611b4
--- /dev/null
+++ b/t/name.t
@@ -0,0 +1,24 @@
+use strict;
+use warnings;
+
+use Test2::V0;
+use Test2::Plugin::NoWarnings;
+use Test2::Require::Module 'Sub::Util';
+
+use Params::ValidationCompiler qw( validation_for );
+
+{
+    my $sub = validation_for(
+        name   => 'Check for X',
+        params => { foo => 1 },
+    );
+
+    my $e = dies { $sub->() };
+    like(
+        $e->trace->as_string,
+        qr/main::Check for X/,
+        'got expected sub name in stack trace',
+    );
+}
+
+done_testing();
diff --git a/t/named/args-check.t b/t/named/args-check.t
new file mode 100644 (file)
index 0000000..6559ba4
--- /dev/null
@@ -0,0 +1,64 @@
+use strict;
+use warnings;
+
+use Test2::V0;
+use Test2::Plugin::NoWarnings;
+
+use Params::ValidationCompiler qw( validation_for );
+
+{
+    my $sub = validation_for(
+        params => {
+            foo => 1,
+        },
+    );
+
+    like(
+        dies { $sub->(42) },
+        qr/\QExpected a hash or hash reference but a single non-reference argument was passed/,
+        'dies when given a single non-ref argument'
+    );
+
+    like(
+        dies { $sub->( [] ) },
+        qr/\QExpected a hash or hash reference but a single ARRAY reference argument was passed/,
+        'dies when given a single arrayref argument'
+    );
+
+    like(
+        dies { $sub->( foo => 42, 'bar' ) },
+        qr/\QExpected a hash or hash reference but an odd number of arguments was passed/,
+        'dies when given three arguments'
+    );
+
+    is(
+        dies { $sub->( foo => 42 ) },
+        undef,
+        'lives when given two arguments'
+    );
+
+    is(
+        dies { $sub->( { foo => 42 } ) },
+        undef,
+        'lives when given a single hashref argument'
+    );
+
+    like(
+        dies { $sub->( bless { foo => 42 }, 'anything' ) },
+        qr/Expected a hash or hash reference but a single object argument was passed/,
+        'dies when passed a blessed object',
+    );
+
+    {
+        package OverloadsHash;
+        use overload '%{}' => sub { return { foo => 42 } };
+    }
+
+    is(
+        dies { $sub->( bless [], 'OverloadsHash' ) },
+        undef,
+        'lives when given a single object that overloads hash dereferencing'
+    );
+}
+
+done_testing();
diff --git a/t/named/const-hash.t b/t/named/const-hash.t
new file mode 100644 (file)
index 0000000..c583d13
--- /dev/null
@@ -0,0 +1,96 @@
+use strict;
+use warnings;
+
+use Test2::V0;
+use Test2::Plugin::NoWarnings;
+use Test2::Require::Module 'Const::Fast';
+
+use Const::Fast;
+use Params::ValidationCompiler qw( validation_for );
+use Specio::Library::Builtins;
+
+skip_all(
+    q{Const::Fast doesn't work on dev/blead perl. Also the tests fail on Perl 5.18 but not others for no reason I can understand}
+);
+
+{
+    # Trying to use const my %spec gives a "Can't store CODE items at
+    # /home/autarch/.perlbrew/libs/perl-5.24.0@dev/lib/perl5/Const/Fast.pm
+    # line 29." Presumably this is because the Specio type ultimately contains
+    # a code ref deep inside. Is Const::Fast iterating through the object and
+    # trying to make it readonly as well?!
+    todo(
+        'cannot pass a hash with a CODE ref nested in it',
+        sub {
+            is(
+                dies {
+                    const my %spec => (
+                        foo => 1,
+                        bar => {
+                            type     => t('Int'),
+                            optional => 1,
+                        },
+                    );
+                },
+                undef,
+            );
+        }
+    );
+
+    # But somehow this works.
+    const my $spec => {
+        foo => 1,
+        bar => {
+            type     => t('Int'),
+            optional => 1,
+        },
+    };
+
+    my $sub = validation_for( params => $spec );
+
+    const my %params1 => ( foo => 42 );
+    is(
+        dies { $sub->(%params1) },
+        undef,
+        'lives when given foo param but no bar'
+    );
+
+    const my %params2 => ( foo => 42, bar => 42 );
+    is(
+        dies { $sub->(%params2) },
+        undef,
+        'lives when given foo and bar params'
+    );
+}
+
+{
+    const my $spec => {
+        foo => 1,
+        bar => {
+            optional => 1,
+        },
+    };
+
+    is(
+        dies { validation_for( params => $spec ) },
+        undef,
+        'can pass constant hashref as spec to validation_for'
+    );
+}
+
+{
+    const my %spec => (
+        foo => {},
+        bar => {
+            optional => 1,
+        },
+    );
+
+    is(
+        dies { validation_for( params => \%spec ) },
+        undef,
+        'can pass constant hash as spec to validation_for'
+    );
+}
+
+done_testing();
diff --git a/t/named/locked-hash.t b/t/named/locked-hash.t
new file mode 100644 (file)
index 0000000..cf828ab
--- /dev/null
@@ -0,0 +1,75 @@
+use strict;
+use warnings;
+
+use Test2::V0;
+use Test2::Plugin::NoWarnings;
+use Test2::Require::Module 'Hash::Util';
+
+use Hash::Util                 qw( lock_hash );
+use Params::ValidationCompiler qw( validation_for );
+use Specio::Library::Builtins;
+
+{
+    my %spec = (
+        foo => 1,
+        bar => {
+            type     => t('Int'),
+            optional => 1,
+        },
+    );
+    lock_hash(%spec);
+
+    my $sub = validation_for( params => \%spec );
+
+    my %params1 = ( foo => 42 );
+    lock_hash(%params1);
+
+    is(
+        dies { $sub->(%params1) },
+        undef,
+        'lives when given foo param but no bar'
+    );
+
+    my %params2 = ( foo => 42, bar => 42 );
+    lock_hash(%params2);
+
+    is(
+        dies { $sub->(%params2) },
+        undef,
+        'lives when given foo and bar params'
+    );
+}
+
+{
+    my %spec = (
+        foo => 1,
+        bar => {
+            optional => 1,
+        },
+    );
+    lock_hash(%spec);
+
+    is(
+        dies { validation_for( params => \%spec ) },
+        undef,
+        'can pass locked hashref as spec to validation_for'
+    );
+}
+
+{
+    my %spec = (
+        foo => {},
+        bar => {
+            optional => 1,
+        },
+    );
+    lock_hash(%spec);
+
+    is(
+        dies { validation_for( params => \%spec ) },
+        undef,
+        'can pass locked hash as spec to validation_for'
+    );
+}
+
+done_testing();
diff --git a/t/named/required.t b/t/named/required.t
new file mode 100644 (file)
index 0000000..704024b
--- /dev/null
@@ -0,0 +1,40 @@
+use strict;
+use warnings;
+
+use Test2::V0;
+use Test2::Plugin::NoWarnings;
+
+use Params::ValidationCompiler qw( validation_for );
+use Specio::Library::Builtins;
+
+{
+    my $sub = validation_for(
+        params => {
+            foo => 1,
+            bar => {
+                type     => t('Int'),
+                optional => 1,
+            },
+        },
+    );
+
+    is(
+        dies { $sub->( foo => 42 ) },
+        undef,
+        'lives when given foo param but no bar'
+    );
+
+    is(
+        dies { $sub->( foo => 42, bar => 42 ) },
+        undef,
+        'lives when given foo and bar params'
+    );
+
+    like(
+        dies { $sub->( bar => 42 ) },
+        qr/foo is a required parameter/,
+        'dies when not given foo param'
+    );
+}
+
+done_testing();
diff --git a/t/named/return-object.t b/t/named/return-object.t
new file mode 100644 (file)
index 0000000..c1f1197
--- /dev/null
@@ -0,0 +1,96 @@
+use strict;
+use warnings;
+
+use Test2::V0;
+use Test2::Plugin::NoWarnings;
+
+use Params::ValidationCompiler qw( validation_for );
+use Scalar::Util               qw( blessed );
+use Specio::Library::Builtins;
+
+for my $want_cxsa ( 0, 1 ) {
+    next if $want_cxsa && !Params::ValidationCompiler::Compiler->HAS_CXSA;
+    subtest(
+        ( $want_cxsa ? 'with' : 'without' ) . ' Class::XSAccessor',
+        sub { test_return_object($want_cxsa) },
+    );
+}
+
+sub test_return_object {
+    my $want_cxsa = shift;
+    local $ENV{TEST_NAMED_ARGS_OBJECT_WITHOUT_CXSA} = !$want_cxsa;
+
+    {
+        my $sub = validation_for(
+            params => {
+                foo => 1,
+                bar => {
+                    type     => t('Int'),
+                    optional => 1,
+                },
+            },
+            return_object => 1,
+        );
+
+        my $ret = $sub->( foo => 42 );
+        ok(
+            blessed $ret,
+            'returned value is a blessed object'
+        );
+
+        if ($want_cxsa) {
+            like(
+                blessed $ret,
+                qr/XS/,
+                'returned object class uses Class::XSAccessor'
+            );
+        }
+        else {
+            like(
+                blessed $ret,
+                qr/PP/,
+                'returned object class uses pure Perl'
+            );
+        }
+
+        is(
+            $ret->foo,
+            42,
+            'returned object contains foo param'
+        );
+
+        is(
+            $ret->bar,
+            undef,
+            'returned object has undef for bar param'
+        );
+    }
+
+    {
+        my $sub = validation_for(
+            params => {
+                foo => { getter => 'get_foo' },
+                bar => {
+                    type      => t('Int'),
+                    optional  => 1,
+                    predicate => 'has_bar',
+                },
+            },
+            return_object => 1,
+        );
+
+        my $ret = $sub->( foo => 42 );
+        is(
+            $ret->get_foo,
+            42,
+            'getter name is used instead of param name'
+        );
+
+        ok(
+            !$ret->has_bar,
+            'predicate is created when requested'
+        );
+    }
+}
+
+done_testing();
diff --git a/t/named/slurpy.t b/t/named/slurpy.t
new file mode 100644 (file)
index 0000000..cf101c4
--- /dev/null
@@ -0,0 +1,105 @@
+use strict;
+use warnings;
+
+use Test2::V0;
+use Test2::Plugin::NoWarnings;
+
+use Params::ValidationCompiler qw( validation_for );
+use Specio::Library::Builtins;
+
+{
+    my $sub = validation_for(
+        params => {
+            foo => 1,
+            bar => { optional => 1 },
+        },
+    );
+
+    like(
+        dies { $sub->( foo => 42, extra => [] ) },
+        qr/Found extra parameters passed to an un-named validation subroutine: \[extra\]/,
+        'dies when given one extra parameter'
+    );
+
+    like(
+        dies { $sub->( foo => 42, extra => [], more => 0 ) },
+        qr/Found extra parameters passed to an un-named validation subroutine: \[extra, more\]/,
+        'dies when given two extra parameters'
+    );
+}
+
+{
+    my $sub = validation_for(
+        params => {
+            foo => 1,
+        },
+        slurpy => 1,
+    );
+
+    like(
+        dies { $sub->() },
+        qr/foo is a required parameter/,
+        'foo is still required when slurpy is true'
+    );
+
+    is(
+        {
+            $sub->(
+                foo => 42,
+                bar => 'whatever',
+            )
+        },
+        {
+            foo => 42,
+            bar => 'whatever',
+        },
+        'extra parameters are returned',
+    );
+}
+
+{
+    my $sub = validation_for(
+        params => {
+            foo => 1,
+        },
+        slurpy => t('Int'),
+    );
+
+    like(
+        dies { $sub->() },
+        qr/foo is a required parameter/,
+        'foo is still required when slurpy is a type constraint'
+    );
+
+    is(
+        {
+            $sub->(
+                foo => 42,
+                bar => 43,
+            )
+        },
+        {
+            foo => 42,
+            bar => 43,
+        },
+        'extra parameters are returned when they pass the type constraint',
+    );
+
+    like(
+        dies {
+            $sub->( foo => 42, bar => 'string' );
+        },
+        qr/Validation failed for type named Int.+with value "string"/,
+        'extra parameters are type checked with one extra',
+    );
+
+    like(
+        dies {
+            $sub->( foo => 42, baz => 1, bar => 'string' );
+        },
+        qr/Validation failed for type named Int.+with value "string"/,
+        'all extra parameters are type checked with multiple extras',
+    );
+}
+
+done_testing();
diff --git a/t/pairs-to-value-list.t b/t/pairs-to-value-list.t
new file mode 100644 (file)
index 0000000..aede09a
--- /dev/null
@@ -0,0 +1,39 @@
+use strict;
+use warnings;
+
+use Test2::V0;
+use Test2::Plugin::NoWarnings;
+
+use Params::ValidationCompiler qw( validation_for );
+
+{
+    my $sub = validation_for(
+        params => [
+            bar => 0,
+            foo => 1,
+        ],
+        named_to_list => 1,
+    );
+
+    is(
+        [ $sub->( foo => 'test' ) ], [ undef, 'test' ],
+        'passing required param returns optional values as undef'
+    );
+
+    is(
+        [ $sub->( foo => 'test', bar => 'b' ) ], [ 'b', 'test' ],
+        'optional params are returned as expected'
+    );
+}
+
+{
+    # We have to handle a single named argument specially to avoid warnings.
+    validation_for(
+        params => [
+            bar => 0,
+        ],
+        named_to_list => 1,
+    );
+}
+
+done_testing();
diff --git a/t/positional/default.t b/t/positional/default.t
new file mode 100644 (file)
index 0000000..4cd3ffb
--- /dev/null
@@ -0,0 +1,106 @@
+use strict;
+use warnings;
+
+use Test2::V0;
+use Test2::Plugin::NoWarnings;
+
+use Params::ValidationCompiler qw( validation_for );
+use Specio::Declare;
+use Specio::Library::Builtins;
+
+{
+    my $validator = validation_for(
+        params => [
+            { type => t('Int') },
+            {
+                default => 10,
+            },
+        ],
+    );
+
+    is(
+        [ $validator->(3) ],
+        [ 3, 10 ],
+        'able to set defaults on positional validator'
+    );
+
+    is(
+        [ $validator->( 3, undef ) ],
+        [ 3, undef ],
+        'default is only set when element does not exist'
+    );
+}
+
+{
+    my $validator = validation_for(
+        params => [
+            1,
+            { default => 0 },
+        ],
+    );
+
+    is(
+        [ $validator->(0) ],
+        [ 0, 0 ],
+        'positional params with default are optional'
+    );
+}
+
+{
+    my $x         = 1;
+    my $validator = validation_for(
+        params => [
+            1,
+            {
+                default => sub { $x++ }
+            },
+        ],
+    );
+
+    is(
+        [ $validator->(0) ],
+        [ 0, 1 ],
+        'default sub for positional params is called'
+    );
+    is(
+        [ $validator->(0) ],
+        [ 0, 2 ],
+        'default sub for positional params is called again'
+    );
+}
+
+{
+    declare(
+        'UCStr',
+        parent => t('Str'),
+        where  => sub { $_[0] =~ /^[A-Z]+$/ },
+    );
+    coerce(
+        t('UCStr'),
+        from  => t('Str'),
+        using => sub { uc $_[0] },
+    );
+
+    my $validator = validation_for(
+        params => [
+            1,
+            {
+                type    => t('UCStr'),
+                default => sub {'ABC'}
+            },
+        ],
+    );
+
+    is(
+        [ $validator->( 0, 'xyz' ) ],
+        [ 0, 'XYZ' ],
+        'coercion for positional param is called'
+    );
+    is(
+        [ $validator->(0) ],
+        [ 0, 'ABC' ],
+        'default for position param with coercion is called'
+    );
+}
+
+done_testing();
diff --git a/t/positional/required.t b/t/positional/required.t
new file mode 100644 (file)
index 0000000..8168da2
--- /dev/null
@@ -0,0 +1,68 @@
+use strict;
+use warnings;
+
+use Test2::V0;
+use Test2::Plugin::NoWarnings;
+
+use Params::ValidationCompiler qw( validation_for );
+use Specio::Library::Builtins;
+
+{
+    my $sub = validation_for(
+        params => [
+            1,
+            {
+                type     => t('Int'),
+                optional => 1,
+            },
+        ],
+    );
+
+    is(
+        dies { $sub->(42) },
+        undef,
+        'lives when given 1st param but no 2nd'
+    );
+
+    is(
+        dies { $sub->( 42, 42 ) },
+        undef,
+        'lives when given 1st and 2nd params'
+    );
+
+    like(
+        dies { $sub->() },
+        qr/Got 0 parameters but expected at least 1/,
+        'dies when not given any params'
+    );
+}
+
+{
+    like(
+        dies {
+            validation_for(
+                params => [
+                    { optional => 1 },
+                    { type     => t('Int') },
+                ],
+            );
+        },
+        qr/\QParameter list contains an optional parameter followed by a required parameter/,
+        'cannot have positional parameters where an optional param comes before a required one'
+    );
+
+    like(
+        dies {
+            validation_for(
+                params => [
+                    { default => 42 },
+                    { type    => t('Int') },
+                ],
+            );
+        },
+        qr/\QParameter list contains an optional parameter followed by a required parameter/,
+        'cannot have positional parameters where a param with a default comes before a required one'
+    );
+}
+
+done_testing();
diff --git a/t/positional/slurpy.t b/t/positional/slurpy.t
new file mode 100644 (file)
index 0000000..564b5b0
--- /dev/null
@@ -0,0 +1,79 @@
+use strict;
+use warnings;
+
+use Test2::V0;
+use Test2::Plugin::NoWarnings;
+
+use Params::ValidationCompiler qw( validation_for );
+use Specio::Library::Builtins;
+
+{
+    my $sub = validation_for(
+        params => [
+            1,
+            { optional => 1 },
+        ],
+    );
+
+    like(
+        dies { $sub->( 42, 43, 44 ) },
+        qr/Got 1 extra parameter/,
+        'dies when given one extra parameter'
+    );
+
+    like(
+        dies { $sub->( 42, 43, 44, 'whuh' ) },
+        qr/Got 2 extra parameters/,
+        'dies when given two extra parameters'
+    );
+}
+
+{
+    my $sub = validation_for(
+        params => [
+            1,
+        ],
+        slurpy => 1,
+    );
+
+    like(
+        dies { $sub->() },
+        qr/Got 0 parameters but expected at least 1/,
+        'foo is still required when slurpy is true'
+    );
+
+    is(
+        [ $sub->( 42, 'whatever' ) ],
+        [ 42, 'whatever' ],
+        'extra parameters are returned',
+    );
+}
+
+{
+    my $sub = validation_for(
+        params => [
+            1,
+        ],
+        slurpy => t('Int'),
+    );
+
+    like(
+        dies { $sub->() },
+        qr/Got 0 parameters but expected at least 1/,
+        'foo is still required when slurpy is a type constraint'
+    );
+
+    is(
+        [ $sub->( 42, 43 ) ],
+        [ 42, 43 ],
+        'one extra parameter is returned when they pass the type constraint',
+    );
+
+    is(
+        [ $sub->( 42, 43, 44 ) ],
+        [ 42, 43, 44 ],
+        'two extra parameters are returned when they pass the type constraint',
+    );
+}
+
+done_testing();
diff --git a/t/self-check.t b/t/self-check.t
new file mode 100644 (file)
index 0000000..a64826e
--- /dev/null
@@ -0,0 +1,142 @@
+## no critic (Moose::RequireCleanNamespace)
+use strict;
+use warnings;
+
+use Test2::V0;
+use Test2::Plugin::NoWarnings;
+
+use Params::ValidationCompiler qw( validation_for );
+use Specio::Library::Builtins;
+
+my $location_re = qr{.+at .*t[\\/]self-check\.t line \d+};
+
+like(
+    dies { validation_for() },
+    qr/\QYou must provide a "params" parameter when creating a parameter validator\E$location_re/,
+    'got expected error message when validation_for is called without parameters'
+);
+
+like(
+    dies { validation_for( params => 42 ) },
+    qr/\QThe "params" parameter when creating a parameter validator must be a hashref or arrayref, you passed a scalar\E$location_re/,
+    'got expected error message when validation_for is called with params as a scalar'
+);
+
+like(
+    dies { validation_for( params => undef ) },
+    qr/\QThe "params" parameter when creating a parameter validator must be a hashref or arrayref, you passed an undef\E$location_re/,
+    'got expected error message when validation_for is called params as an undef'
+);
+
+like(
+    dies { validation_for( params => \42 ) },
+    qr/\QThe "params" parameter when creating a parameter validator must be a hashref or arrayref, you passed a scalarref\E$location_re/,
+    'got expected error message when validation_for is called params as a scalarref'
+);
+
+like(
+    dies { validation_for( params => bless {}, 'Foo' ) },
+    qr/\QThe "params" parameter when creating a parameter validator must be a hashref or arrayref, you passed a Foo object\E$location_re/,
+    'got expected error message when validation_for is called params as an object'
+);
+
+like(
+    dies { validation_for( params => { a => {} }, foo => 1, bar => 2 ) },
+    qr/\QYou passed unknown parameters when creating a parameter validator: [bar foo]\E$location_re/,
+    'got expected error message when validation_for is called with extra unknown parameters'
+);
+
+like(
+    dies { validation_for( params => { a => {} }, name => undef, ) },
+    qr/\QThe "name" parameter when creating a parameter validator must be a scalar, you passed an undef\E$location_re/,
+    'got expected error message when validation_for is called with name as an undef'
+);
+
+like(
+    dies { validation_for( params => { a => {} }, name => [], ) },
+    qr/\QThe "name" parameter when creating a parameter validator must be a scalar, you passed an arrayref\E$location_re/,
+    'got expected error message when validation_for is called with name as an arrayref'
+);
+
+like(
+    dies { validation_for( params => { a => {} }, name => bless {}, 'Foo' ) },
+    qr/\QThe "name" parameter when creating a parameter validator must be a scalar, you passed a Foo object\E$location_re/,
+    'got expected error message when validation_for is called with name as an object'
+);
+
+like(
+    dies {
+        validation_for(
+            params        => [ a => 1 ],
+            named_to_list => 1,
+            slurpy        => 1,
+        );
+    },
+    qr/\QYou cannot use "named_to_list" and "slurpy" together\E$location_re/,
+    'got expected error message when validation_for is called with named_to_list and slurpy'
+);
+
+like(
+    dies {
+        validation_for(
+            params        => [ a => { isa => 1, typo => 2 } ],
+            named_to_list => 1,
+        );
+    },
+    qr/\QSpecification contains unknown keys: [isa typo]\E$location_re/,
+    'got expected error message when validation_for is called with named_to_list and an invalid spec keys'
+);
+
+like(
+    dies {
+        validation_for(
+            params => [ { isa => 1, } ],
+        );
+    },
+    qr/\QSpecification contains unknown keys: [isa]\E$location_re/,
+    'got expected error message when validation_for is called with an arrayref params and an invalid spec keys'
+);
+
+like(
+    dies {
+        validation_for(
+            params => { a => { isa => 1, typo => 2 } },
+        );
+    },
+    qr/\QSpecification contains unknown keys: [isa typo]\E$location_re/,
+    'got expected error message when validation_for is called with an hashref params and an invalid spec keys'
+);
+like(
+    dies {
+        validation_for(
+            params => { foo => t('Int') },
+        );
+    },
+    qr/\QSpecifications must be a scalar or hashref, but received a Specio::Constraint::Simple/,
+    'got expected error message when validation_for is called with a spec that is a type instead of a hashref'
+);
+
+like(
+    dies {
+        validation_for(
+            params        => [ { type => t('Str') } ],
+            return_object => 1,
+        );
+    },
+    qr/\QYou can only use "return_object" with named params\E$location_re/,
+    'got expected error message when validation_for is called with arrayref params and return_object is true'
+);
+
+like(
+    dies {
+        validation_for(
+            params        => { foo => { type => t('Str') } },
+            return_object => 1,
+            slurpy        => 1,
+        );
+    },
+    qr/\QYou cannot use "return_object" and "slurpy" together\E$location_re/,
+    'got expected error message when validation_for is called with return_object and slurpy both set'
+);
+
+done_testing();
diff --git a/t/source_for.t b/t/source_for.t
new file mode 100644 (file)
index 0000000..db0b624
--- /dev/null
@@ -0,0 +1,18 @@
+use strict;
+use warnings;
+
+use Test2::V0;
+use Test2::Plugin::NoWarnings;
+
+use Params::ValidationCompiler qw( source_for );
+
+my ( $source, $env ) = source_for( params => { foo => 1 } );
+like(
+    $source,
+    qr/exists \$args/,
+    'source_for returns expected source'
+);
+
+is( $env, { '%known' => { foo => 1 } }, 'got expected environment' );
+
+done_testing();
diff --git a/t/specio.t b/t/specio.t
new file mode 100644 (file)
index 0000000..354d1c3
--- /dev/null
@@ -0,0 +1,149 @@
+use strict;
+use warnings;
+
+use Test2::V0;
+use Test2::Plugin::NoWarnings;
+
+use Params::ValidationCompiler qw( validation_for );
+use Specio::Declare;
+use Specio::Library::Builtins;
+
+subtest(
+    'type can be inlined',
+    sub {
+        _test_int_type( t('Int') );
+    }
+);
+
+declare(
+    'MyInt',
+    where => sub { $_[0] =~ /\A-?[0-9]+\z/ },
+);
+
+declare(
+    'MyInt2',
+    where => sub { $_[0] =~ /\A-?[0-9]+\z/ },
+);
+
+declare(
+    'ArrayRefOfInt',
+    parent => t( 'ArrayRef', of => t('Int') ),
+);
+
+declare(
+    'ArrayRefOfInt2',
+    parent => t( 'ArrayRef', of => t('Int') ),
+);
+
+subtest(
+    'type cannot be inlined',
+    sub {
+        _test_int_type( t('MyInt') );
+    }
+);
+
+subtest(
+    'type and coercion can be inlined',
+    sub {
+        coerce(
+            t('ArrayRefOfInt'),
+            from   => t('Int'),
+            inline => sub { return "[ $_[1] ]" },
+        );
+
+        _test_arrayref_to_int_coercion( t('ArrayRefOfInt') );
+    }
+);
+
+subtest(
+    'type can be inlined but coercion cannot',
+    sub {
+        coerce(
+            t('ArrayRefOfInt2'),
+            from  => t('Int'),
+            using => sub { return [ $_[0] ] },
+        );
+
+        _test_arrayref_to_int_coercion( t('ArrayRefOfInt2') );
+    }
+);
+
+subtest(
+    'type cannot be inlined but coercion can',
+    sub {
+        coerce(
+            t('MyInt'),
+            from   => t('ArrayRef'),
+            inline => sub { return "scalar \@{ $_[1] }" },
+        );
+
+        _test_arrayref_to_int_coercion( t('MyInt') );
+    }
+);
+
+subtest(
+    'neither type not coercion can be inlined',
+    sub {
+        coerce(
+            t('MyInt2'),
+            from  => t('ArrayRef'),
+            using => sub { return scalar @{ $_[0] } },
+        );
+
+        _test_arrayref_to_int_coercion( t('MyInt2') );
+    }
+);
+
+done_testing();
+
+sub _test_int_type {
+    my $type = shift;
+
+    my $sub = validation_for(
+        params => {
+            foo => { type => $type },
+        },
+    );
+
+    is(
+        dies { $sub->( foo => 42 ) },
+        undef,
+        'lives when foo is an integer'
+    );
+
+    my $name = $type->name;
+    like(
+        dies { $sub->( foo => [] ) },
+        qr/Validation failed for type named $name declared in .+ with value \Q[  ]/,
+        'dies when foo is an arrayref'
+    );
+}
+
+sub _test_arrayref_to_int_coercion {
+    my $type = shift;
+
+    my $sub = validation_for(
+        params => {
+            foo => { type => $type },
+        },
+    );
+
+    is(
+        dies { $sub->( foo => 42 ) },
+        undef,
+        'lives when foo is an integer'
+    );
+
+    is(
+        dies { $sub->( foo => [ 42, 1 ] ) },
+        undef,
+        'lives when foo is an arrayref of integers'
+    );
+
+    my $name = $type->name;
+    like(
+        dies { $sub->( foo => {} ) },
+        qr/Validation failed for type named $name declared in .+ with value \Q{  }/,
+        'dies when foo is a hashref'
+    );
+}
diff --git a/t/type-tiny.t b/t/type-tiny.t
new file mode 100644 (file)
index 0000000..ae64ece
--- /dev/null
@@ -0,0 +1,128 @@
+use strict;
+use warnings;
+
+use Test2::V0;
+use Test2::Plugin::NoWarnings;
+use Test2::Require::Module 'Type::Tiny' => '0.024';
+
+use Params::ValidationCompiler qw( validation_for );
+use Types::Standard            qw( ArrayRef Int );
+
+subtest(
+    'type can be inlined',
+    sub {
+        _test_int_type(Int);
+    }
+);
+
+my $myint = Type::Tiny->new(
+    name       => 'MyInt',
+    constraint => sub {/\A-?[0-9]+\z/},
+);
+
+subtest(
+    'type cannot be inlined',
+    sub {
+        _test_int_type($myint);
+    }
+);
+
+subtest(
+    'type and coercion can be inlined',
+    sub {
+        my $type = ( ArrayRef [Int] )->plus_coercions(
+            Int, '[$_]',
+        );
+
+        _test_int_to_arrayref_coercion($type);
+    }
+);
+
+subtest(
+    'type can be inlined but coercion cannot',
+    sub {
+        my $type = ( ArrayRef [Int] )->plus_coercions(
+            Int, sub { [$_] },
+        );
+
+        _test_int_to_arrayref_coercion($type);
+    }
+);
+
+# XXX - if the type cannot be inlined then the coercion reports itself as
+# uninlinable as well, but that could change in the future.
+subtest(
+    'type cannot be inlined but coercion can',
+    sub {
+        my $type = ( ArrayRef [$myint] )->plus_coercions(
+            $myint, '[$_]',
+        );
+
+        _test_int_to_arrayref_coercion($type);
+    }
+);
+
+subtest(
+    'neither type not coercion can be inlined',
+    sub {
+        my $type = ( ArrayRef [$myint] )->plus_coercions(
+            $myint, sub { [$_] },
+        );
+
+        _test_int_to_arrayref_coercion($type);
+    }
+);
+
+done_testing();
+
+sub _test_int_type {
+    my $type = shift;
+
+    my $sub = validation_for(
+        params => {
+            foo => { type => $type },
+        },
+    );
+
+    is(
+        dies { $sub->( foo => 42 ) },
+        undef,
+        'lives when foo is an integer'
+    );
+
+    my $name = $type->display_name;
+    like(
+        dies { $sub->( foo => [] ) },
+        qr/\QReference [] did not pass type constraint "$name"/,
+        'dies when foo is an arrayref'
+    );
+}
+
+sub _test_int_to_arrayref_coercion {
+    my $type = shift;
+
+    my $sub = validation_for(
+        params => {
+            foo => { type => $type },
+        },
+    );
+
+    is(
+        dies { $sub->( foo => 42 ) },
+        undef,
+        'lives when foo is an integer'
+    );
+
+    is(
+        dies { $sub->( foo => [ 42, 1 ] ) },
+        undef,
+        'lives when foo is an arrayref of integers'
+    );
+
+    my $name = $type->display_name;
+    like(
+        dies { $sub->( foo => {} ) },
+        qr/\QReference {} did not pass type constraint "$name"/,
+        'dies when foo is a hashref'
+    );
+}
diff --git a/test-matrix.als b/test-matrix.als
new file mode 100644 (file)
index 0000000..c392974
--- /dev/null
@@ -0,0 +1,35 @@
+one sig Validator {
+       style: Style,
+       specs: set Spec,
+       slurpy: Slurpy,
+}
+
+abstract sig Slurpy { }
+
+enum Style { named, positional, named_to_list }
+
+sig Spec {
+       is_required: Bool,
+       type: Type,
+       default: Default,
+}
+
+enum Bool { false, true }
+
+enum Default { absent, simple, coderef }
+
+sig Type extends Slurpy {
+       system: TypeSystem,
+       inlinable: Inlinable,
+     coercions: set Coercion,
+}
+
+enum TypeSystem { moose, specio, type_tiny}
+
+enum Inlinable { cannot, yes, with_env }
+
+sig Coercion {
+       inlinable: Inlinable,
+}
+
+run {} for 5
diff --git a/xt/author/00-compile.t b/xt/author/00-compile.t
new file mode 100644 (file)
index 0000000..a8a39b5
--- /dev/null
@@ -0,0 +1,62 @@
+use 5.006;
+use strict;
+use warnings;
+
+# this test was generated with Dist::Zilla::Plugin::Test::Compile 2.058
+
+use Test::More;
+
+plan tests => 4;
+
+my @module_files = (
+    'Params/ValidationCompiler.pm',
+    'Params/ValidationCompiler/Compiler.pm',
+    'Params/ValidationCompiler/Exceptions.pm'
+);
+
+
+
+# no fake home requested
+
+my @switches = (
+    -d 'blib' ? '-Mblib' : '-Ilib',
+);
+
+use File::Spec;
+use IPC::Open3;
+use IO::Handle;
+
+open my $stdin, '<', File::Spec->devnull or die "can't open devnull: $!";
+
+my @warnings;
+for my $lib (@module_files)
+{
+    # see L<perlfaq8/How can I capture STDERR from an external command?>
+    my $stderr = IO::Handle->new;
+
+    diag('Running: ', join(', ', map { my $str = $_; $str =~ s/'/\\'/g; q{'} . $str . q{'} }
+            $^X, @switches, '-e', "require q[$lib]"))
+        if $ENV{PERL_COMPILE_TEST_DEBUG};
+
+    my $pid = open3($stdin, '>&STDERR', $stderr, $^X, @switches, '-e', "require q[$lib]");
+    binmode $stderr, ':crlf' if $^O eq 'MSWin32';
+    my @_warnings = <$stderr>;
+    waitpid($pid, 0);
+    is($?, 0, "$lib loaded ok");
+
+    shift @_warnings if @_warnings and $_warnings[0] =~ /^Using .*\bblib/
+        and not eval { +require blib; blib->VERSION('1.01') };
+
+    if (@_warnings)
+    {
+        warn @_warnings;
+        push @warnings, @_warnings;
+    }
+}
+
+
+
+is(scalar(@warnings), 0, 'no warnings found')
+    or diag 'got warnings: ', ( Test::More->can('explain') ? Test::More::explain(\@warnings) : join("\n", '', @warnings) );
+
+
diff --git a/xt/author/eol.t b/xt/author/eol.t
new file mode 100644 (file)
index 0000000..35fd696
--- /dev/null
@@ -0,0 +1,37 @@
+use strict;
+use warnings;
+
+# this test was generated with Dist::Zilla::Plugin::Test::EOL 0.19
+
+use Test::More 0.88;
+use Test::EOL;
+
+my @files = (
+    'lib/Params/ValidationCompiler.pm',
+    'lib/Params/ValidationCompiler/Compiler.pm',
+    'lib/Params/ValidationCompiler/Exceptions.pm',
+    't/00-report-prereqs.dd',
+    't/00-report-prereqs.t',
+    't/default.t',
+    't/exceptions.t',
+    't/moose.t',
+    't/name-fails.t',
+    't/name.t',
+    't/named/args-check.t',
+    't/named/const-hash.t',
+    't/named/locked-hash.t',
+    't/named/required.t',
+    't/named/return-object.t',
+    't/named/slurpy.t',
+    't/pairs-to-value-list.t',
+    't/positional/default.t',
+    't/positional/required.t',
+    't/positional/slurpy.t',
+    't/self-check.t',
+    't/source_for.t',
+    't/specio.t',
+    't/type-tiny.t'
+);
+
+eol_unix_ok($_, { trailing_whitespace => 1 }) foreach @files;
+done_testing;
diff --git a/xt/author/matrix.t b/xt/author/matrix.t
new file mode 100644 (file)
index 0000000..2e46857
--- /dev/null
@@ -0,0 +1,781 @@
+## no critic (Moose::RequireCleanNamespace)
+use strict;
+use warnings;
+
+use Test2::Bundle::Extended;
+use Test2::Plugin::NoWarnings;
+use Test2::Require::Perl '5.012';
+
+use Hash::Merge qw( merge );
+use List::AllUtils qw( first_index reduce );
+use Params::ValidationCompiler qw( validation_for );
+use Set::Scalar;
+
+# It'd be nice to add a third parameter and test with three but that ends up
+# running a ridiculous number of tests.
+my %foo = _one_param_permutations('foo');
+my %bar = _one_param_permutations('bar');
+
+my %slurpy = _slurpy_param_permutations('slurpy');
+
+OUTER:
+for my $foo_desc ( sort keys %foo ) {
+    subtest 'one parameter' => sub {
+        my $p = $foo{$foo_desc};
+
+        subtest $foo_desc => sub {
+            subtest 'named params' => sub {
+                _named_params_tests($p);
+            };
+
+            subtest 'positional params' => sub {
+                _positional_params_tests($p);
+            };
+
+            subtest 'named_to_list params' => sub {
+                _named_to_list_params_tests($p);
+            };
+
+            for my $slurpy_desc ( sort keys %slurpy ) {
+                subtest 'slurpy named params' => sub {
+                    _named_slurpy_tests( $p, $slurpy{$slurpy_desc} );
+                };
+                subtest 'slurpy positional params' => sub {
+                    _positional_slurpy_tests( $p, $slurpy{$slurpy_desc} );
+                };
+            }
+        };
+    } or last OUTER;
+
+    for my $bar_desc ( sort keys %bar ) {
+        subtest 'two parameters' => sub {
+            my @p = ( $foo{$foo_desc}, $bar{$bar_desc} );
+
+            subtest "$foo_desc + $bar_desc" => sub {
+                subtest 'named params' => sub {
+                    _named_params_tests(@p);
+                };
+
+                subtest 'positional params' => sub {
+                    _positional_params_tests(@p);
+                };
+
+                subtest 'named_to_list params' => sub {
+                    _named_to_list_params_tests(@p);
+                };
+            };
+        } or last OUTER;
+    }
+}
+
+done_testing();
+
+sub _named_params_tests {
+    my @p = @_;
+
+    my $named_v;
+    is(
+        dies {
+            $named_v = validation_for(
+                params => { map { $_->{key} => $_->{spec} } @p } );
+        },
+        undef,
+        'no exception compiling named params validator'
+    ) or return;
+
+    _run_param_tests(
+        \@p,
+        'named',
+        sub {
+            my $input  = shift;
+            my $output = shift;
+
+            is(
+                { $named_v->( %{$input} ) },
+                $output,
+                'input as k/v pairs'
+            );
+
+            is(
+                { $named_v->($input) },
+                $output,
+                'input as hashref'
+            );
+        },
+    );
+}
+
+sub _positional_params_tests {
+    my @p = @_;
+
+    # If this permutation has an optional param before a required param then
+    # we cannot run these tests, as this is not allowed.
+    my $first_o
+        = first_index { $_->{spec}{optional} || exists $_->{spec}{default} }
+    @p;
+    my $first_r = first_index {
+        !( $_->{spec}{optional} || exists $_->{spec}{default} )
+    }
+    @p;
+
+    if ( $first_o >= 0 && $first_o < $first_r ) {
+    SKIP: {
+            skip(
+                'test would end up with optional params before required',
+                1
+            );
+        }
+        return;
+    }
+
+    my $pos_v;
+    is(
+        dies {
+            $pos_v = validation_for( params => [ map { $_->{spec} } @p ] )
+        },
+        undef,
+        'no exception compiling positional params validator'
+    ) or return;
+
+    _run_param_tests(
+        \@p,
+        'pos',
+        sub {
+            my $input  = shift;
+            my $output = shift;
+
+            is(
+                [ $pos_v->( @{$input} ) ],
+                $output,
+                'input as list'
+            );
+        },
+    );
+}
+
+sub _named_to_list_params_tests {
+    my @p = @_;
+
+    my @sorted_p = sort { $a->{key} cmp $b->{key} } @p;
+
+    my $ntl_v;
+    is(
+        dies {
+            $ntl_v = validation_for(
+                params => [ map { $_->{key} => $_->{spec} } @sorted_p ],
+                named_to_list => 1,
+            );
+        },
+        undef,
+        'no exception compiling positional params validator with named_to_list'
+    ) or return;
+
+    _run_param_tests(
+        \@p,
+        'named',
+        sub {
+            my $input  = shift;
+            my $output = shift;
+
+            is(
+                [ $ntl_v->($input) ],
+                [ map { $output->{$_} } map { $_->{key} } @sorted_p ],
+            );
+        },
+    );
+}
+
+sub _named_slurpy_tests {
+    my $p      = shift;
+    my $slurpy = shift;
+
+    my $slurpy_v;
+    is(
+        dies {
+            $slurpy_v = validation_for(
+                params => { $p->{key} => $p->{spec} },
+                slurpy => ( $slurpy->{spec}{type} || 1 ),
+            );
+        },
+        undef,
+        'no exception compiling named params + slurpy validator'
+    ) or return;
+
+    _run_param_tests(
+        [ $p, $slurpy ],
+        'named',
+        sub {
+            my $input  = shift;
+            my $output = shift;
+
+            is(
+                { $slurpy_v->( %{$input} ) },
+                $output,
+                'input as k/v pairs'
+            );
+        },
+    );
+}
+
+sub _positional_slurpy_tests {
+    my $p      = shift;
+    my $slurpy = shift;
+
+    my $slurpy_v;
+    is(
+        dies {
+            $slurpy_v = validation_for(
+                params => [ $p->{spec} ],
+                slurpy => ( $slurpy->{spec}{type} || 1 ),
+            );
+        },
+        undef,
+        'no exception compiling positional params + slurpy validator'
+    ) or return;
+
+    _run_param_tests(
+        [ $p, $slurpy ],
+        'pos',
+        sub {
+            my $input  = shift;
+            my $output = shift;
+
+            is(
+                [ $slurpy_v->( @{$input} ) ],
+                $output,
+                'input as list'
+            );
+        },
+    );
+}
+
+sub _run_param_tests {
+    my $p         = shift;
+    my $type      = shift;
+    my $test_code = shift;
+
+    my @sets
+        = map { Set::Scalar->new( keys %{ $_->{tests} } ) } @{$p};
+
+    my $iter = Set::Scalar->cartesian_product_iterator(@sets);
+    while ( my @test_keys = $iter->() ) {
+        my $subtest = join q{ + }, @test_keys;
+
+        # If we're testing positional params with more than 1 parameter, and
+        # any parameter but the last has an empty input list, then we cannot
+        # run that particular test set. We'd end up with an array built from
+        # [], [42], [{ foo => 1 }] as the list of inputs, which gets turned
+        # into a 2 element array when 3 need to be passed in.
+        if ( $type eq 'pos' && @{$p} > 1 ) {
+            for my $i ( 0 .. $#test_keys - 1 ) {
+                if ( @{ $p->[$i]->{tests}{ $test_keys[$i] }{$type}{input} }
+                    == 0 ) {
+                    subtest $subtest => sub {
+                    SKIP: {
+                            skip
+                                'Cannot run a test set where any non-last parameter has an empty input list',
+                                1;
+                        }
+                    };
+                    return;
+                }
+            }
+        }
+
+        my $in_out = reduce { merge( $a, $b ) }
+        map { $p->[$_]->{tests}{ $test_keys[$_] }{$type} } 0 .. $#test_keys;
+
+        subtest $subtest => sub {
+            $test_code->( $in_out->{input}, $in_out->{output} );
+        };
+    }
+}
+
+sub _one_param_permutations {
+    my $key = shift;
+
+    my %types = (
+        'no type' => undef,
+        _one_type_permutations(),
+    );
+
+    my %optional = (
+        required => 0,
+        optional => 1,
+    );
+
+    my %default = (
+        none            => undef,
+        'simple scalar' => 42,
+        'subroutine'    => sub {42},
+    );
+
+    my %perms;
+
+    for my $t ( sort keys %types ) {
+        for my $o ( sort keys %optional ) {
+            for my $d ( sort keys %default ) {
+                my $desc = "type = $t";
+
+                my %spec;
+                my %tests = (
+                    (
+                        $t eq 'no type'
+                        ? 'any value is accepted'
+                        : 'value passes type check'
+                    ) => {
+                        named => {
+                            input  => { $key => 700 },
+                            output => { $key => 700 },
+                        },
+                        pos => {
+                            input  => [700],
+                            output => [700],
+                        },
+                    },
+                );
+
+                if ( $t =~ /coercion/ ) {
+                    $tests{'value is coerced'} = {
+                        named => {
+                            input  => { $key => [ 1 .. 4 ] },
+                            output => { $key => 4 },
+                        },
+                        pos => {
+                            input => [ [ 1 .. 4 ] ],
+                            output => [4],
+                        },
+                    };
+                }
+
+                if ( $optional{$o} ) {
+                    $spec{optional} = 1;
+                    $desc .= "; $o";
+
+                    $tests{'no value given for optional param'} = {
+                        named => {
+                            input  => {},
+                            output => {},
+                        },
+                        pos => {
+                            input  => [],
+                            output => [],
+                        }
+                    };
+                }
+                else {
+                    $spec{default} = $default{$d} if $default{$d};
+
+                    if ( $d eq 'none' ) {
+                        $desc .= "; $o; default = $d";
+                    }
+                    else {
+                        $tests{'no value given for param with default'} = {
+                            named => {
+                                input  => {},
+                                output => { $key => 42 },
+                            },
+                            pos => {
+                                input  => [],
+                                output => [42],
+                            }
+                        };
+
+                        $desc .= "; default = $d";
+                    }
+                }
+
+                $spec{type} = $types{$t} if $types{$t};
+
+                $perms{$desc} = {
+                    key   => $key,
+                    spec  => \%spec,
+                    tests => \%tests,
+                };
+            }
+        }
+    }
+
+    return %perms;
+}
+
+sub _slurpy_param_permutations {
+    my $key = shift;
+
+    my %types = (
+        'no type' => undef,
+        _one_type_permutations(),
+    );
+
+    my %perms;
+
+    for my $t ( sort keys %types ) {
+        my $desc = "type = $t";
+
+        my %spec;
+        my %tests = (
+            (
+                $t eq 'no type'
+                ? 'any value is accepted'
+                : 'value passes type check'
+            ) => {
+                named => {
+                    input  => { $key => 700 },
+                    output => { $key => 700 },
+                },
+                pos => {
+                    input  => [700],
+                    output => [700],
+                },
+            },
+        );
+
+        if ( $t =~ /coercion/ ) {
+            $tests{'value is coerced'} = {
+                named => {
+                    input  => { $key => [ 1 .. 4 ] },
+                    output => { $key => 4 },
+                },
+                pos => {
+                    input => [ [ 1 .. 4 ] ],
+                    output => [4],
+                },
+            };
+        }
+
+        $spec{type} = $types{$t} if $types{$t};
+
+        $spec{$desc} = {
+            key   => $key,
+            spec  => \%spec,
+            tests => \%tests,
+        };
+    }
+
+    return %perms;
+}
+
+sub _one_type_permutations {
+    my %subs = (
+        'inlinable type'                     => 'inl_type',
+        'inlinable type, inlinable coercion' => 'inl_type_with_inl_coercion',
+        'inlinable type, non-inlinable coercion' =>
+            'inl_type_with_no_inl_coercion',
+        'non-inlinable type' => 'no_inl_type',
+        'non-inlinable type, inlinable coercion' =>
+            'no_inl_type_with_inl_coercion',
+        'non-inlinable type, non-inlinable coercion' =>
+            'no_inl_type_with_no_inl_coercion',
+        'inlinable type with closed-over variables' => 'closure_inl_env_type',
+    );
+
+    my %perms;
+
+    for my $flavor (qw( Moose TT Specio )) {
+        my $pkg = '_Types::' . $flavor;
+
+        for my $k ( sort keys %subs ) {
+            my $s = $subs{$k};
+            next unless $pkg->can($s);
+
+            $perms{"$flavor - $k"} = $pkg->$s();
+        }
+    }
+
+    return %perms;
+}
+
+## no critic (Modules::ProhibitMultiplePackages)
+
+{
+    package _Types::Moose;
+
+    use Moose::Util::TypeConstraints;
+
+    ## no critic (Subroutines::ProtectPrivateSubs)
+
+    sub inl_type {
+        my $type = subtype(
+            as 'Int',
+            where { $_ > 0 },
+            inline_as {
+                $_[0]->parent->_inline_check( $_[1] ) . " && $_[1] > 0"
+            },
+        );
+
+        return $type;
+    }
+
+    sub inl_type_with_no_inl_coercion {
+        my $type = subtype(
+            as 'Int',
+            where { $_ > 0 },
+            inline_as {
+                $_[0]->parent->_inline_check( $_[1] ) . " && $_[1] > 0"
+            },
+        );
+
+        coerce(
+            $type,
+            from 'ArrayRef',
+            via { scalar @{$_} },
+        );
+
+        return $type;
+    }
+
+    sub no_inl_type {
+        my $type = subtype(
+            as 'Int',
+            where { $_ > 0 },
+        );
+
+        return $type;
+    }
+
+    sub no_inl_type_with_no_inl_coercion {
+        my $type = subtype(
+            as 'Int',
+            where { $_ > 0 },
+        );
+
+        coerce(
+            $type,
+            from 'ArrayRef',
+            via { scalar @{$_} },
+        );
+
+        return $type;
+    }
+
+    sub closure_inl_env_type {
+        return enum( [ 42, 43, 44, 700 ] );
+    }
+}
+
+{
+    package _Types::TT;
+
+    use Types::Standard qw( Int ArrayRef );
+    use Type::Utils -all;
+
+    sub inl_type {
+        my $type = subtype(
+            as Int,
+            where { $_ > 0 },
+            inline_as {
+                $_[0]->parent->inline_check( $_[1] ) . " && $_[1] > 0"
+            },
+        );
+
+        return $type;
+    }
+
+    sub inl_type_with_inl_coercion {
+        my $type = subtype(
+            as Int,
+            where { $_ > 0 },
+            inline_as {
+                $_[0]->parent->inline_check( $_[1] ) . " && $_[1] > 0"
+            },
+        );
+
+        return $type->plus_coercions( ArrayRef, 'scalar @{ $_ }' );
+    }
+
+    sub inl_type_with_no_inl_coercion {
+        my $type = subtype(
+            as Int,
+            where { $_ > 0 },
+            inline_as {
+                $_[0]->parent->inline_check( $_[1] ) . " && $_[1] > 0"
+            },
+        );
+
+        return $type->plus_coercions( ArrayRef, sub { scalar @{$_} } );
+    }
+
+    sub no_inl_type {
+        my $type = subtype(
+            as Int,
+            where { $_ > 0 }
+        );
+
+        return $type;
+    }
+
+    sub no_inl_type_with_inl_coercion {
+        my $type = subtype(
+            as Int,
+            where { $_ > 0 }
+        );
+
+        return $type->plus_coercions( ArrayRef, 'scalar @{ $_ }' );
+    }
+
+    sub no_inl_type_with_no_inl_coercion {
+        my $type = subtype(
+            as Int,
+            where { $_ > 0 }
+        );
+
+        return $type->plus_coercions( ArrayRef, sub { scalar @{$_} } );
+    }
+}
+
+{
+    package _Types::Specio;
+
+    use Specio::Declare;
+    use Specio::Library::Builtins;
+
+    sub inl_type {
+        my $type = anon(
+            parent => t('Int'),
+            inline => sub {
+                $_[0]->parent->inline_check( $_[1] ) . " && $_[1] > 0";
+            },
+        );
+
+        return $type;
+    }
+
+    sub inl_type_with_inl_coercion {
+        my $type = anon(
+            parent => t('Int'),
+            inline => sub {
+                $_[0]->parent->inline_check( $_[1] ) . " && $_[1] > 0";
+            },
+        );
+
+        coerce(
+            $type,
+            from   => t('ArrayRef'),
+            inline => sub {"scalar \@{ $_[1] }"},
+        );
+
+        return $type;
+    }
+
+    sub inl_type_with_no_inl_coercion {
+        my $type = anon(
+            parent => t('Int'),
+            inline => sub {
+                $_[0]->parent->inline_check( $_[1] ) . " && $_[1] > 0";
+            },
+        );
+
+        coerce(
+            $type,
+            from  => t('ArrayRef'),
+            using => sub { scalar @{ $_[0] } },
+        );
+
+        return $type;
+    }
+
+    sub no_inl_type {
+        my $type = anon(
+            parent => t('Int'),
+            inline => sub {
+                $_[0]->parent->inline_check( $_[1] ) . " && $_[1] > 0";
+            },
+        );
+
+        return $type;
+    }
+
+    sub no_inl_type_with_inl_coercion {
+        my $type = anon(
+            parent => t('Int'),
+            inline => sub {
+                $_[0]->parent->inline_check( $_[1] ) . " && $_[1] > 0";
+            },
+        );
+
+        coerce(
+            $type,
+            from   => t('ArrayRef'),
+            inline => sub {"scalar \@{ $_[1] }"},
+        );
+
+        return $type;
+    }
+
+    sub no_inl_type_with_no_inl_coercion {
+        my $type = anon(
+            parent => t('Int'),
+            inline => sub {
+                $_[0]->parent->inline_check( $_[1] ) . " && $_[1] > 0";
+            },
+        );
+
+        coerce(
+            $type,
+            from  => t('ArrayRef'),
+            using => sub { scalar @{ $_[0] } },
+        );
+
+        return $type;
+    }
+
+    sub closure_inl_env_type {
+        return enum( values => [ 42, 43, 44, 700 ] );
+    }
+}
+
+__END__
+
+## Parameter specs
+
+### Type
+
+* No type, just required
+* Has a type -> [Types](#types)
+
+### Required or not
+
+* Required
+* Optional
+
+### Defaults
+
+* No default
+* Has a non-sub default
+* Has a subroutine default
+
+## Slurpy
+
+* Any type
+* A specific type -> [Types](#types)
+
+## Parameter passing style
+
+* Named
+  * As k/v pairs
+  * As hashref
+* Named to list
+* Positional
+
+## Types
+
+### Type System
+
+* Moose
+* Specio
+* Type::Tiny
+
+### Inlining
+
+* Type can be inlined
+  * Type inlining requires "env" vars
+  * Coercion inlining requires "env" vars
+* Type cannot be inlined
+
+### Coercions
+
+* No coercion
+* With coercion(s)
+  * that can all be inlined
+  * none of which can be inlined
+  * some of which can be inlined
diff --git a/xt/author/mojibake.t b/xt/author/mojibake.t
new file mode 100644 (file)
index 0000000..5ef161e
--- /dev/null
@@ -0,0 +1,9 @@
+#!perl
+
+use strict;
+use warnings qw(all);
+
+use Test::More;
+use Test::Mojibake;
+
+all_files_encoding_ok();
diff --git a/xt/author/no-tabs.t b/xt/author/no-tabs.t
new file mode 100644 (file)
index 0000000..35e4bf3
--- /dev/null
@@ -0,0 +1,37 @@
+use strict;
+use warnings;
+
+# this test was generated with Dist::Zilla::Plugin::Test::NoTabs 0.15
+
+use Test::More 0.88;
+use Test::NoTabs;
+
+my @files = (
+    'lib/Params/ValidationCompiler.pm',
+    'lib/Params/ValidationCompiler/Compiler.pm',
+    'lib/Params/ValidationCompiler/Exceptions.pm',
+    't/00-report-prereqs.dd',
+    't/00-report-prereqs.t',
+    't/default.t',
+    't/exceptions.t',
+    't/moose.t',
+    't/name-fails.t',
+    't/name.t',
+    't/named/args-check.t',
+    't/named/const-hash.t',
+    't/named/locked-hash.t',
+    't/named/required.t',
+    't/named/return-object.t',
+    't/named/slurpy.t',
+    't/pairs-to-value-list.t',
+    't/positional/default.t',
+    't/positional/required.t',
+    't/positional/slurpy.t',
+    't/self-check.t',
+    't/source_for.t',
+    't/specio.t',
+    't/type-tiny.t'
+);
+
+notabs_ok($_) foreach @files;
+done_testing;
diff --git a/xt/author/pod-coverage.t b/xt/author/pod-coverage.t
new file mode 100644 (file)
index 0000000..8878c2d
--- /dev/null
@@ -0,0 +1,44 @@
+#!perl
+# This file was automatically generated by Dist::Zilla::Plugin::Test::Pod::Coverage::Configurable 0.07.
+
+use Test::Pod::Coverage 1.08;
+use Test::More 0.88;
+
+BEGIN {
+    if ( $] <= 5.008008 ) {
+        plan skip_all => 'These tests require Pod::Coverage::TrustPod, which only works with Perl 5.8.9+';
+    }
+}
+use Pod::Coverage::TrustPod;
+
+my %skip = map { $_ => 1 } qw(  );
+
+my @modules;
+for my $module ( all_modules() ) {
+    next if $skip{$module};
+
+    push @modules, $module;
+}
+
+plan skip_all => 'All the modules we found were excluded from POD coverage test.'
+    unless @modules;
+
+plan tests => scalar @modules;
+
+my %trustme = ();
+
+my @also_private;
+
+for my $module ( sort @modules ) {
+    pod_coverage_ok(
+        $module,
+        {
+            coverage_class => 'Pod::Coverage::TrustPod',
+            also_private   => \@also_private,
+            trustme        => $trustme{$module} || [],
+        },
+        "pod coverage for $module"
+    );
+}
+
+done_testing();
diff --git a/xt/author/pod-spell.t b/xt/author/pod-spell.t
new file mode 100644 (file)
index 0000000..6d92566
--- /dev/null
@@ -0,0 +1,38 @@
+use strict;
+use warnings;
+use Test::More;
+
+# generated by Dist::Zilla::Plugin::Test::PodSpelling 2.007005
+use Test::Spelling 0.12;
+use Pod::Wordlist;
+
+
+add_stopwords(<DATA>);
+all_pod_files_spelling_ok( qw( bin lib ) );
+__DATA__
+Compiler
+DROLSKY
+DROLSKY's
+Dave
+Exceptions
+Gregory
+Konojacki
+Oschwald
+Params
+PayPal
+Rolsky
+Rolsky's
+Tomasz
+ValidationCompiler
+autarch
+drolsky
+getter
+goschwald
+lib
+me
+oschwald
+params
+slurpy
+uncompromised
+validator
+validators
diff --git a/xt/author/pod-syntax.t b/xt/author/pod-syntax.t
new file mode 100644 (file)
index 0000000..e563e5d
--- /dev/null
@@ -0,0 +1,7 @@
+#!perl
+# This file was automatically generated by Dist::Zilla::Plugin::PodSyntaxTests.
+use strict; use warnings;
+use Test::More;
+use Test::Pod 1.41;
+
+all_pod_files_ok();
diff --git a/xt/author/portability.t b/xt/author/portability.t
new file mode 100644 (file)
index 0000000..6d1d92d
--- /dev/null
@@ -0,0 +1,8 @@
+use strict;
+use warnings;
+
+use Test::More;
+
+use Test::Portability::Files;
+
+run_tests();
diff --git a/xt/author/precious.t b/xt/author/precious.t
new file mode 100644 (file)
index 0000000..ee161de
--- /dev/null
@@ -0,0 +1,24 @@
+use strict;
+use warnings;
+
+use Test::More;
+
+use Capture::Tiny qw( capture );
+use Encode qw( decode );
+use FindBin qw( $Bin );
+
+binmode $_, ':encoding(utf-8)'
+    for map { Test::More->builder->$_ }
+    qw( output failure_output todo_output );
+
+chdir "$Bin/../.."
+    or die "Cannot chdir to $Bin/../..: $!";
+
+my ( $out, $err ) = capture { system(qw( precious lint -a )) };
+$_ = decode( 'UTF-8', $_ ) for grep {defined} $out, $err;
+
+is( $? >> 8, 0, 'precious lint -a exited with 0' )
+    or diag($out);
+is( $err, q{}, 'no output to stderr' );
+
+done_testing();
diff --git a/xt/author/synopsis.t b/xt/author/synopsis.t
new file mode 100644 (file)
index 0000000..3e03427
--- /dev/null
@@ -0,0 +1,5 @@
+#!perl
+
+use Test::Synopsis;
+
+all_synopsis_ok();
diff --git a/xt/author/test-version.t b/xt/author/test-version.t
new file mode 100644 (file)
index 0000000..b47210e
--- /dev/null
@@ -0,0 +1,23 @@
+use strict;
+use warnings;
+use Test::More;
+
+# generated by Dist::Zilla::Plugin::Test::Version 1.09
+use Test::Version;
+
+my @imports = qw( version_all_ok );
+
+my $params = {
+    is_strict      => 1,
+    has_version    => 1,
+    multiple       => 0,
+
+};
+
+push @imports, $params
+    if version->parse( $Test::Version::VERSION ) >= version->parse('1.002');
+
+Test::Version->import(@imports);
+
+version_all_ok;
+done_testing;
diff --git a/xt/release/cpan-changes.t b/xt/release/cpan-changes.t
new file mode 100644 (file)
index 0000000..286005a
--- /dev/null
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+
+# this test was generated with Dist::Zilla::Plugin::Test::CPAN::Changes 0.012
+
+use Test::More 0.96 tests => 1;
+use Test::CPAN::Changes;
+subtest 'changes_ok' => sub {
+    changes_file_ok('Changes');
+};
diff --git a/xt/release/meta-json.t b/xt/release/meta-json.t
new file mode 100644 (file)
index 0000000..5ddad73
--- /dev/null
@@ -0,0 +1,4 @@
+#!perl
+
+use Test::CPAN::Meta::JSON;
+meta_json_ok();