From: TizenOpenSource Date: Wed, 14 Feb 2024 04:51:18 +0000 (+0900) Subject: Imported Upstream version 0.31 X-Git-Tag: upstream/0.31^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=b3de05bd43e12876b12cbe06060548a6d5137924;p=platform%2Fupstream%2Fperl-Params-ValidationCompiler.git Imported Upstream version 0.31 --- diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..ac28652 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -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 index 0000000..dfa1f44 --- /dev/null +++ b/CONTRIBUTING.md @@ -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 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 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 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 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 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 " + ], + "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 ", + "Gregory Oschwald ", + "Tomasz Konojacki " + ], + "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 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 ' +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 ' + - 'Gregory Oschwald ' + - 'Tomasz Konojacki ' +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 index 0000000..ad2d3de --- /dev/null +++ b/Makefile.PL @@ -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 ", + "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 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 + +# CONTRIBUTORS + +- Gregory Oschwald +- Gregory Oschwald +- Tomasz Konojacki + +# 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 index 0000000..4c64519 --- /dev/null +++ b/azure-pipelines.yml @@ -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 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 index 0000000..0f3e7b6 --- /dev/null +++ b/dev-bin/install-xt-tools.sh @@ -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 index 0000000..71a9441 --- /dev/null +++ b/dist.ini @@ -0,0 +1,39 @@ +name = Params-ValidationCompiler +author = Dave Rolsky +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 index 0000000..233e994 --- /dev/null +++ b/eg/bench-named.pl @@ -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 index 0000000..45dd5d2 --- /dev/null +++ b/eg/bench-pos.pl @@ -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 index 0000000..63595ad --- /dev/null +++ b/git/hooks/pre-commit.sh @@ -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 index 0000000..8c99cb8 --- /dev/null +++ b/git/setup.pl @@ -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 index 0000000..a929f24 --- /dev/null +++ b/lib/Params/ValidationCompiler.pm @@ -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 and C. 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 value should be a hashref where the parameter names +are keys and the specs are the values. + +If you pass an arrayref and C is false, the validator will +expect positional params. Each element of the C arrayref should be a +parameter spec. + +If you pass an arrayref and C is true, the validator will expect +named params, but will return a list of values. In this case the arrayref +should contain a I 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 type (from L or L), +a L type, or a L 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. 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 +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 with C 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 or +C. + +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 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, C, +or anything similar. + +When C 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. 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 is +not available. + +Note that you must install L 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 is not installed. +If this is false, then passing a name when L 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. + +=item * debug + +Sets the C environment variable to true before +calling C. This causes the source of the +subroutine to be printed before it's C'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. + +=head1 SUPPORT + +Bugs may be submitted at L. + +=head1 SOURCE + +The source code repository for Params-ValidationCompiler can be found at L. + +=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 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. + +=head1 AUTHOR + +Dave Rolsky + +=head1 CONTRIBUTORS + +=for stopwords Gregory Oschwald Tomasz Konojacki + +=over 4 + +=item * + +Gregory Oschwald + +=item * + +Gregory Oschwald + +=item * + +Tomasz Konojacki + +=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 file included with this distribution. + +=cut diff --git a/lib/Params/ValidationCompiler/Compiler.pm b/lib/Params/ValidationCompiler/Compiler.pm new file mode 100644 index 0000000..a9ac181 --- /dev/null +++ b/lib/Params/ValidationCompiler/Compiler.pm @@ -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. + +=head1 SOURCE + +The source code repository for Params-ValidationCompiler can be found at L. + +=head1 AUTHOR + +Dave Rolsky + +=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 file included with this distribution. + +=cut diff --git a/lib/Params/ValidationCompiler/Exceptions.pm b/lib/Params/ValidationCompiler/Exceptions.pm new file mode 100644 index 0000000..99e1e56 --- /dev/null +++ b/lib/Params/ValidationCompiler/Exceptions.pm @@ -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 or C. + +=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. + +=head1 SOURCE + +The source code repository for Params-ValidationCompiler can be found at L. + +=head1 AUTHOR + +Dave Rolsky + +=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 file included with this distribution. + +=cut diff --git a/perlcriticrc b/perlcriticrc new file mode 100644 index 0000000..1754348 --- /dev/null +++ b/perlcriticrc @@ -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 index 0000000..b54e60d --- /dev/null +++ b/perltidyrc @@ -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 index 0000000..a311112 --- /dev/null +++ b/precious.toml @@ -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 index 0000000..9514e72 --- /dev/null +++ b/t/00-report-prereqs.dd @@ -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 index 0000000..c3a94ca --- /dev/null +++ b/t/00-report-prereqs.t @@ -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 index 0000000..ae6327d --- /dev/null +++ b/t/default.t @@ -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 index 0000000..806cffd --- /dev/null +++ b/t/exceptions.t @@ -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 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 index 0000000..46c5d53 --- /dev/null +++ b/t/name-fails.t @@ -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 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 index 0000000..6559ba4 --- /dev/null +++ b/t/named/args-check.t @@ -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 index 0000000..c583d13 --- /dev/null +++ b/t/named/const-hash.t @@ -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 index 0000000..cf828ab --- /dev/null +++ b/t/named/locked-hash.t @@ -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 index 0000000..704024b --- /dev/null +++ b/t/named/required.t @@ -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 index 0000000..c1f1197 --- /dev/null +++ b/t/named/return-object.t @@ -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 index 0000000..cf101c4 --- /dev/null +++ b/t/named/slurpy.t @@ -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 index 0000000..aede09a --- /dev/null +++ b/t/pairs-to-value-list.t @@ -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 index 0000000..4cd3ffb --- /dev/null +++ b/t/positional/default.t @@ -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 index 0000000..8168da2 --- /dev/null +++ b/t/positional/required.t @@ -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 index 0000000..564b5b0 --- /dev/null +++ b/t/positional/slurpy.t @@ -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 index 0000000..a64826e --- /dev/null +++ b/t/self-check.t @@ -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 index 0000000..db0b624 --- /dev/null +++ b/t/source_for.t @@ -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 index 0000000..354d1c3 --- /dev/null +++ b/t/specio.t @@ -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 index 0000000..ae64ece --- /dev/null +++ b/t/type-tiny.t @@ -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 index 0000000..c392974 --- /dev/null +++ b/test-matrix.als @@ -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 index 0000000..a8a39b5 --- /dev/null +++ b/xt/author/00-compile.t @@ -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 + 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 index 0000000..35fd696 --- /dev/null +++ b/xt/author/eol.t @@ -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 index 0000000..2e46857 --- /dev/null +++ b/xt/author/matrix.t @@ -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 index 0000000..5ef161e --- /dev/null +++ b/xt/author/mojibake.t @@ -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 index 0000000..35e4bf3 --- /dev/null +++ b/xt/author/no-tabs.t @@ -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 index 0000000..8878c2d --- /dev/null +++ b/xt/author/pod-coverage.t @@ -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 index 0000000..6d92566 --- /dev/null +++ b/xt/author/pod-spell.t @@ -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(); +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 index 0000000..e563e5d --- /dev/null +++ b/xt/author/pod-syntax.t @@ -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 index 0000000..6d1d92d --- /dev/null +++ b/xt/author/portability.t @@ -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 index 0000000..ee161de --- /dev/null +++ b/xt/author/precious.t @@ -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 index 0000000..3e03427 --- /dev/null +++ b/xt/author/synopsis.t @@ -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 index 0000000..b47210e --- /dev/null +++ b/xt/author/test-version.t @@ -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 index 0000000..286005a --- /dev/null +++ b/xt/release/cpan-changes.t @@ -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 index 0000000..5ddad73 --- /dev/null +++ b/xt/release/meta-json.t @@ -0,0 +1,4 @@ +#!perl + +use Test::CPAN::Meta::JSON; +meta_json_ok();