diff options
-rw-r--r-- | Changes | 125 | ||||
-rw-r--r-- | LICENSE | 379 | ||||
-rw-r--r-- | MANIFEST | 27 | ||||
-rw-r--r-- | META.json | 78 | ||||
-rw-r--r-- | META.yml | 44 | ||||
-rw-r--r-- | Makefile.PL | 60 | ||||
-rw-r--r-- | README | 103 | ||||
-rw-r--r-- | cpanfile | 9 | ||||
-rw-r--r-- | dist.ini | 4 | ||||
-rw-r--r-- | lib/Module/CPANfile.pm | 323 | ||||
-rw-r--r-- | lib/Module/CPANfile/Environment.pm | 173 | ||||
-rw-r--r-- | lib/Module/CPANfile/Prereq.pm | 21 | ||||
-rw-r--r-- | lib/Module/CPANfile/Prereqs.pm | 117 | ||||
-rw-r--r-- | lib/Module/CPANfile/Requirement.pm | 25 | ||||
-rw-r--r-- | lib/cpanfile-faq.pod | 128 | ||||
-rw-r--r-- | lib/cpanfile.pod | 128 | ||||
-rwxr-xr-x | script/cpanfile-dump | 131 | ||||
-rwxr-xr-x | script/mymeta-cpanfile | 115 | ||||
-rw-r--r-- | t/Utils.pm | 39 | ||||
-rw-r--r-- | t/feature.t | 87 | ||||
-rw-r--r-- | t/from_prereqs.t | 36 | ||||
-rw-r--r-- | t/merge.t | 65 | ||||
-rw-r--r-- | t/mirror.t | 38 | ||||
-rw-r--r-- | t/parse.t | 74 | ||||
-rw-r--r-- | t/release-pod-syntax.t | 14 | ||||
-rw-r--r-- | t/requirement.t | 81 |
26 files changed, 2424 insertions, 0 deletions
@@ -0,0 +1,125 @@ +Revision history for Module::Install::CPANfile + +1.1000 2014-08-29 13:48:13 JST + - Make it non-development release + +1.0905 2013-12-09 16:26:08 PST + - Support mirror syntax (masaki) #22 + +1.0904 2013-09-21 07:56:31 JST + - Removed git/ref accessor from Requirement + - Added $requirement->has_options + +1.0903 2013-09-20 15:36:30 JST + - Added $cpanfile->merged_requirements to get the CPAN::Meta::Requirements for all prereqs + +1.0902 2013-09-20 13:45:15 JST + - s/rev/ref/ for git options + +1.0901 2013-09-19 18:59:55 JST + - Remove unused code + +1.0900 2013-09-19 18:45:59 JST + - Experimental support for git URL specification + - Complete rewrite of internal to preserve original prereq statement as much as possible + +1.0002 2013-09-06 12:26:11 PDT + - Add cpanfile-dump utility (xdg) + +1.0001 2013-08-05 14:24:07 PDT + - Updated documentation about syntax + +1.0000 2013-08-04 12:28:32 PDT + - Make the cpanfile spec 1.0 + - Updated documentation + +0.9036 2013-07-24 13:11:42 PDT + - Repackage after git rebase + +0.9035 2013-07-24 13:10:29 PDT + - Fix a bug where warnings will be raised if the content of cpanfile has sprintf-style + %X content somewhere (reported by frew) + +0.9034 2013-06-05 12:09:11 JST + - split .pm files, mainly for PAUSE + +0.9033 2013-06-05 11:38:56 JST + - merge with git master + +0.9032 2013-06-05 11:35:58 JST + - repackage for PAUSE + +0.9031 2013-04-17 08:39:17 JST + - Revert EXPERIMENTAL git URL options since it causes failures when stringified + inside CPAN::Meta #13 + +0.9030 2013-04-14 17:05:46 JST + - Fixed a bug where prereqs_with with an empty feature list causes an exception + +0.9029 2013-04-14 13:05:30 JST + - Support EXPERIMENTAL git URL parsing (ikasam_a) + - Allow feature DSL to omit description + - Add better error messages for feature syntax errors + +0.9028 2013-04-14 01:27:29 JST + - Add new 'feature' DSL and features support for CPAN::Meta::Feature + +0.9027 2013-03-31 12:45:10 PDT + - Improved error message shows filename and line number + +0.9026 2013-03-28 12:07:27 PDT + - Updated FAQ to mention tools that support cpanfile + +0.9025 2013-03-24 23:27:57 PDT + - rebuild package with new Milla to support script + +0.9024 2013-03-23 21:29:41 PDT + - Make it non-trial + +0.9023 2013-03-23 21:26:07 PDT + - mymeta-cpanfile: Added options to filter specific phases and types + +0.9022 2013-03-23 20:34:30 America/Los_Angeles + - Convert to Milla + - Made CPAN::Meta and ::Prereqs a hard requirement than runtime optional + +0.9021 Sat Mar 23 09:11:46 PDT 2013 + - added mymeta-cpanfile utility script for migration from Makefile.PL/Build.PL + +0.9020 Sat Mar 23 02:48:50 PDT 2013 + - Implemeneted from_prereqs, to_string and save utility methods, + to allow creating a new cpanfile out of (MY)META files + +0.9010 Fri Feb 15 13:24:40 PST 2013 + - Fix the package name in the example + +0.9009 Thu Feb 7 09:24:11 PST 2013 + - Fixed CPAN::Meta dependency in the test (Chris Weyl) + +0.9008 Wed Jan 30 21:53:32 PST 2013 + - Added merge_meta method to be used in Module::Install::CPANfile + +0.9007 Sat Apr 14 14:55:49 CST 2012 + - Fixed documentation + +0.9006 Fri Apr 13 07:30:33 JST 2012 + - Alias prereqs method to prereq + +0.9005 Thu Apr 12 20:41:27 JST 2012 + - Include all files into Module/CPANfile.pm + +0.9004 Thu Apr 12 18:31:59 JST 2012 + - Renamed the distribution + +0.9003 Sat Apr 7 01:26:04 PDT 2012 + - Don't use $! in error checks because we don't open file there. + This also caused issues in 5.8 where $! isn't often cleared + +0.9002 Sat Apr 7 01:14:00 PDT 2012 + - Fixed the test again to not fail on 5.8 + +0.9001 Tue Apr 3 04:30:31 CEST 2012 + - Added t/samples in the directory so the test won't fail + +0.9000 Sun Apr 1 15:10:36 CEST 2012 + - Initial version @@ -0,0 +1,379 @@ +This software is copyright (c) 2014 by Tatsuhiko Miyagawa. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +Terms of the Perl programming language system itself + +a) the GNU General Public License as published by the Free + Software Foundation; either version 1, or (at your option) any + later version, or +b) the "Artistic License" + +--- The GNU General Public License, Version 1, February 1989 --- + +This software is Copyright (c) 2014 by Tatsuhiko Miyagawa. + +This is free software, licensed under: + + The GNU General Public License, Version 1, February 1989 + + GNU GENERAL PUBLIC LICENSE + Version 1, February 1989 + + Copyright (C) 1989 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The license agreements of most software companies try to keep users +at the mercy of those companies. By contrast, our General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. The +General Public License applies to the Free Software Foundation's +software and to any other program whose authors commit to using it. +You can use it for your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Specifically, the General Public License is designed to make +sure that you have the freedom to give away or sell copies of free +software, that you receive source code or can get it if you want it, +that you can change the software or use pieces of it in new free +programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of a such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must tell them their rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any program or other work which +contains a notice placed by the copyright holder saying it may be +distributed under the terms of this General Public License. The +"Program", below, refers to any such program or work, and a "work based +on the Program" means either the Program or any work containing the +Program or a portion of it, either verbatim or with modifications. Each +licensee is addressed as "you". + + 1. You may copy and distribute verbatim copies of the Program's source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this +General Public License and to the absence of any warranty; and give any +other recipients of the Program a copy of this General Public License +along with the Program. You may charge a fee for the physical act of +transferring a copy. + + 2. You may modify your copy or copies of the Program or any portion of +it, and copy and distribute such modifications under the terms of Paragraph +1 above, provided that you also do the following: + + a) cause the modified files to carry prominent notices stating that + you changed the files and the date of any change; and + + b) cause the whole of any work that you distribute or publish, that + in whole or in part contains the Program or any part thereof, either + with or without modifications, to be licensed at no charge to all + third parties under the terms of this General Public License (except + that you may choose to grant warranty protection to some or all + third parties, at your option). + + c) If the modified program normally reads commands interactively when + run, you must cause it, when started running for such interactive use + in the simplest and most usual way, to print or display an + announcement including an appropriate copyright notice and a notice + that there is no warranty (or else, saying that you provide a + warranty) and that users may redistribute the program under these + conditions, and telling the user how to view a copy of this General + Public License. + + d) You may charge a fee for the physical act of transferring a + copy, and you may at your option offer warranty protection in + exchange for a fee. + +Mere aggregation of another independent work with the Program (or its +derivative) on a volume of a storage or distribution medium does not bring +the other work under the scope of these terms. + + 3. You may copy and distribute the Program (or a portion or derivative of +it, under Paragraph 2) in object code or executable form under the terms of +Paragraphs 1 and 2 above provided that you also do one of the following: + + a) accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of + Paragraphs 1 and 2 above; or, + + b) accompany it with a written offer, valid for at least three + years, to give any third party free (except for a nominal charge + for the cost of distribution) a complete machine-readable copy of the + corresponding source code, to be distributed under the terms of + Paragraphs 1 and 2 above; or, + + c) accompany it with the information you received as to where the + corresponding source code may be obtained. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form alone.) + +Source code for a work means the preferred form of the work for making +modifications to it. For an executable file, complete source code means +all the source code for all modules it contains; but, as a special +exception, it need not include source code for modules which are standard +libraries that accompany the operating system on which the executable +file runs, or for standard header files or definitions files that +accompany that operating system. + + 4. You may not copy, modify, sublicense, distribute or transfer the +Program except as expressly provided under this General Public License. +Any attempt otherwise to copy, modify, sublicense, distribute or transfer +the Program is void, and will automatically terminate your rights to use +the Program under this License. However, parties who have received +copies, or rights to use copies, from you under this General Public +License will not have their licenses terminated so long as such parties +remain in full compliance. + + 5. By copying, distributing or modifying the Program (or any work based +on the Program) you indicate your acceptance of this license to do so, +and all its terms and conditions. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the original +licensor to copy, distribute or modify the Program subject to these +terms and conditions. You may not impose any further restrictions on the +recipients' exercise of the rights granted herein. + + 7. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of the license which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +the license, you may choose any version ever published by the Free Software +Foundation. + + 8. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to humanity, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + + To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) 19yy <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 1, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19xx name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the +appropriate parts of the General Public License. Of course, the +commands you use may be called something other than `show w' and `show +c'; they could even be mouse-clicks or menu items--whatever suits your +program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + program `Gnomovision' (a program to direct compilers to make passes + at assemblers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +That's all there is to it! + + +--- The Artistic License 1.0 --- + +This software is Copyright (c) 2014 by Tatsuhiko Miyagawa. + +This is free software, licensed under: + + The Artistic License 1.0 + +The Artistic License + +Preamble + +The intent of this document is to state the conditions under which a Package +may be copied, such that the Copyright Holder maintains some semblance of +artistic control over the development of the package, while giving the users of +the package the right to use and distribute the Package in a more-or-less +customary fashion, plus the right to make reasonable modifications. + +Definitions: + + - "Package" refers to the collection of files distributed by the Copyright + Holder, and derivatives of that collection of files created through + textual modification. + - "Standard Version" refers to such a Package if it has not been modified, + or has been modified in accordance with the wishes of the Copyright + Holder. + - "Copyright Holder" is whoever is named in the copyright or copyrights for + the package. + - "You" is you, if you're thinking about copying or distributing this Package. + - "Reasonable copying fee" is whatever you can justify on the basis of media + cost, duplication charges, time of people involved, and so on. (You will + not be required to justify it to the Copyright Holder, but only to the + computing community at large as a market that must bear the fee.) + - "Freely Available" means that no fee is charged for the item itself, though + there may be fees involved in handling the item. It also means that + recipients of the item may redistribute it under the same conditions they + received it. + +1. You may make and give away verbatim copies of the source form of the +Standard Version of this Package without restriction, provided that you +duplicate all of the original copyright notices and associated disclaimers. + +2. You may apply bug fixes, portability fixes and other modifications derived +from the Public Domain or from the Copyright Holder. A Package modified in such +a way shall still be considered the Standard Version. + +3. You may otherwise modify your copy of this Package in any way, provided that +you insert a prominent notice in each changed file stating how and when you +changed that file, and provided that you do at least ONE of the following: + + a) place your modifications in the Public Domain or otherwise make them + Freely Available, such as by posting said modifications to Usenet or an + equivalent medium, or placing the modifications on a major archive site + such as ftp.uu.net, or by allowing the Copyright Holder to include your + modifications in the Standard Version of the Package. + + b) use the modified Package only within your corporation or organization. + + c) rename any non-standard executables so the names do not conflict with + standard executables, which must also be provided, and provide a separate + manual page for each non-standard executable that clearly documents how it + differs from the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +4. You may distribute the programs of this Package in object code or executable +form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and library files, + together with instructions (in the manual page or equivalent) on where to + get the Standard Version. + + b) accompany the distribution with the machine-readable source of the Package + with your modifications. + + c) accompany any non-standard executables with their corresponding Standard + Version executables, giving the non-standard executables non-standard + names, and clearly documenting the differences in manual pages (or + equivalent), together with instructions on where to get the Standard + Version. + + d) make other distribution arrangements with the Copyright Holder. + +5. You may charge a reasonable copying fee for any distribution of this +Package. You may charge any fee you choose for support of this Package. You +may not charge a fee for this Package itself. However, you may distribute this +Package in aggregate with other (possibly commercial) programs as part of a +larger (possibly commercial) software distribution provided that you do not +advertise this Package as a product of your own. + +6. The scripts and library files supplied as input to or produced as output +from the programs of this Package do not automatically fall under the copyright +of this Package, but belong to whomever generated them, and may be sold +commercially, and may be aggregated with this Package. + +7. C or perl subroutines supplied by you and linked into this Package shall not +be considered part of this Package. + +8. The name of the Copyright Holder may not be used to endorse or promote +products derived from this software without specific prior written permission. + +9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +The End + diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..5e99412 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,27 @@ +# This file was automatically generated by Dist::Zilla::Plugin::Manifest v5.020. +Changes +LICENSE +MANIFEST +META.json +META.yml +Makefile.PL +README +cpanfile +dist.ini +lib/Module/CPANfile.pm +lib/Module/CPANfile/Environment.pm +lib/Module/CPANfile/Prereq.pm +lib/Module/CPANfile/Prereqs.pm +lib/Module/CPANfile/Requirement.pm +lib/cpanfile-faq.pod +lib/cpanfile.pod +script/cpanfile-dump +script/mymeta-cpanfile +t/Utils.pm +t/feature.t +t/from_prereqs.t +t/merge.t +t/mirror.t +t/parse.t +t/release-pod-syntax.t +t/requirement.t diff --git a/META.json b/META.json new file mode 100644 index 0000000..3a9248c --- /dev/null +++ b/META.json @@ -0,0 +1,78 @@ +{ + "abstract" : "Parse cpanfile", + "author" : [ + "Tatsuhiko Miyagawa" + ], + "dynamic_config" : 0, + "generated_by" : "Dist::Milla version v1.0.5, Dist::Zilla version 5.020, CPAN::Meta::Converter version 2.142060", + "license" : [ + "perl_5" + ], + "meta-spec" : { + "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", + "version" : "2" + }, + "name" : "Module-CPANfile", + "no_index" : { + "directory" : [ + "t", + "xt", + "inc", + "share", + "eg", + "examples" + ] + }, + "prereqs" : { + "configure" : { + "requires" : { + "ExtUtils::MakeMaker" : "0" + } + }, + "develop" : { + "requires" : { + "Test::Pod" : "1.41" + } + }, + "runtime" : { + "recommends" : { + "Pod::Usage" : "0" + }, + "requires" : { + "CPAN::Meta" : "2.12091", + "CPAN::Meta::Prereqs" : "2.12091", + "parent" : "0" + } + }, + "test" : { + "requires" : { + "Test::More" : "0.88" + } + } + }, + "release_status" : "stable", + "resources" : { + "bugtracker" : { + "web" : "https://github.com/miyagawa/cpanfile/issues" + }, + "homepage" : "https://github.com/miyagawa/cpanfile", + "repository" : { + "type" : "git", + "url" : "https://github.com/miyagawa/cpanfile.git", + "web" : "https://github.com/miyagawa/cpanfile" + } + }, + "version" : "1.1000", + "x_contributors" : [ + "Atsushi Kato <ktat@cpan.org>", + "David Golden <dagolden@cpan.org>", + "David Steinbrunner <dsteinbrunner@pobox.com>", + "Gregory Oschwald <oschwald@gmail.com>", + "Kenichi Ishigaki <ishigaki@cpan.org>", + "Masahiro Honma <hiratara@cpan.org>", + "Michiel Beijen <michiel.beijen@gmail.com>", + "grtodd <gtodd@iciti.ca>", + "ikasam_a <masaki.nakagawa@gmail.com>" + ] +} + diff --git a/META.yml b/META.yml new file mode 100644 index 0000000..6d66dc8 --- /dev/null +++ b/META.yml @@ -0,0 +1,44 @@ +--- +abstract: 'Parse cpanfile' +author: + - 'Tatsuhiko Miyagawa' +build_requires: + Test::More: '0.88' +configure_requires: + ExtUtils::MakeMaker: '0' +dynamic_config: 0 +generated_by: 'Dist::Milla version v1.0.5, Dist::Zilla version 5.020, CPAN::Meta::Converter version 2.142060' +license: perl +meta-spec: + url: http://module-build.sourceforge.net/META-spec-v1.4.html + version: '1.4' +name: Module-CPANfile +no_index: + directory: + - t + - xt + - inc + - share + - eg + - examples +recommends: + Pod::Usage: '0' +requires: + CPAN::Meta: '2.12091' + CPAN::Meta::Prereqs: '2.12091' + parent: '0' +resources: + bugtracker: https://github.com/miyagawa/cpanfile/issues + homepage: https://github.com/miyagawa/cpanfile + repository: https://github.com/miyagawa/cpanfile.git +version: '1.1000' +x_contributors: + - 'Atsushi Kato <ktat@cpan.org>' + - 'David Golden <dagolden@cpan.org>' + - 'David Steinbrunner <dsteinbrunner@pobox.com>' + - 'Gregory Oschwald <oschwald@gmail.com>' + - 'Kenichi Ishigaki <ishigaki@cpan.org>' + - 'Masahiro Honma <hiratara@cpan.org>' + - 'Michiel Beijen <michiel.beijen@gmail.com>' + - 'grtodd <gtodd@iciti.ca>' + - 'ikasam_a <masaki.nakagawa@gmail.com>' diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..6707fad --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,60 @@ + +# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v5.020. +use strict; +use warnings; + + + +use ExtUtils::MakeMaker ; + + + +my %WriteMakefileArgs = ( + "ABSTRACT" => "Parse cpanfile", + "AUTHOR" => "Tatsuhiko Miyagawa", + "CONFIGURE_REQUIRES" => { + "ExtUtils::MakeMaker" => 0 + }, + "DISTNAME" => "Module-CPANfile", + "EXE_FILES" => [ + "script/cpanfile-dump", + "script/mymeta-cpanfile" + ], + "LICENSE" => "perl", + "NAME" => "Module::CPANfile", + "PREREQ_PM" => { + "CPAN::Meta" => "2.12091", + "CPAN::Meta::Prereqs" => "2.12091", + "parent" => 0 + }, + "TEST_REQUIRES" => { + "Test::More" => "0.88" + }, + "VERSION" => "1.1000", + "test" => { + "TESTS" => "t/*.t" + } +); + + +my %FallbackPrereqs = ( + "CPAN::Meta" => "2.12091", + "CPAN::Meta::Prereqs" => "2.12091", + "Test::More" => "0.88", + "parent" => 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); + + + @@ -0,0 +1,103 @@ +NAME + Module::CPANfile - Parse cpanfile + +SYNOPSIS + use Module::CPANfile; + + my $file = Module::CPANfile->load("cpanfile"); + my $prereqs = $file->prereqs; # CPAN::Meta::Prereqs object + + my @features = $file->features; # CPAN::Meta::Feature objects + my $merged_prereqs = $file->prereqs_with(@identifiers); # CPAN::Meta::Prereqs + + $file->merge_meta('MYMETA.json'); + +DESCRIPTION + Module::CPANfile is a tool to handle cpanfile format to load application + specific dependencies, not just for CPAN distributions. + +METHODS + load + $file = Module::CPANfile->load; + $file = Module::CPANfile->load('cpanfile'); + + Load and parse a cpanfile. By default it tries to load "cpanfile" in + the current directory, unless you pass the path to its argument. + + from_prereqs + $file = Module::CPANfile->from_prereqs({ + runtime => { requires => { DBI => '1.000' } }, + }); + + Creates a new Module::CPANfile object from prereqs hash you can get + via CPAN::Meta's "prereqs", or CPAN::Meta::Prereqs' + "as_string_hash". + + # read MYMETA, then feed the prereqs to create Module::CPANfile + my $meta = CPAN::Meta->load_file('MYMETA.json'); + my $file = Module::CPANfile->from_prereqs($meta->prereqs); + + # load cpanfile, then recreate it with round-trip + my $file = Module::CPANfile->load('cpanfile'); + $file = Module::CPANfile->from_prereqs($file->prereq_specs); + # or $file->prereqs->as_string_hash + + prereqs + Returns CPAN::Meta::Prereqs object out of the parsed cpanfile. + + prereq_specs + Returns a hash reference that should be passed to + "CPAN::Meta::Prereqs->new". + + features + Returns a list of features available in the cpanfile as + CPAN::Meta::Feature. + + prereqs_with(@identifiers), effective_prereqs(\@identifiers) + Returns CPAN::Meta::Prereqs object, with merged prereqs for features + identified with the @identifiers. + + to_string($include_empty) + $file->to_string; + $file->to_string(1); + + Returns a canonical string (code) representation for cpanfile. + Useful if you want to convert CPAN::Meta::Prereqs to a new cpanfile. + + # read MYMETA's prereqs and print cpanfile representation of it + my $meta = CPAN::Meta->load_file('MYMETA.json'); + my $file = Module::CPANfile->from_prereqs($meta->prereqs); + print $file->to_string; + + By default, it omits the phase where there're no modules registered. + If you pass the argument of a true value, it will print them as + well. + + save + $file->save('cpanfile'); + + Saves the currently loaded prereqs as a new "cpanfile" by calling + "to_string". Beware this method will overwrite the existing cpanfile + without any warning or backup. Taking a backup or giving warnings to + users is a caller's responsibility. + + # Read MYMETA.json and creates a new cpanfile + my $meta = CPAN::Meta->load_file('MYMETA.json'); + my $file = Module::CPANfile->from_prereqs($meta->prereqs); + $file->save('cpanfile'); + + merge_meta + $file->merge_meta('META.yml'); + $file->merge_meta('MYMETA.json', '2.0'); + + Merge the effective prereqs with Meta specification loaded from the + given META file, using CPAN::Meta. You can specify the META spec + version in the second argument, which defaults to 1.4 in case the + given file is YAML, and 2 if it is JSON. + +AUTHOR + Tatsuhiko Miyagawa + +SEE ALSO + cpanfile, CPAN::Meta, CPAN::Meta::Spec + diff --git a/cpanfile b/cpanfile new file mode 100644 index 0000000..84cba43 --- /dev/null +++ b/cpanfile @@ -0,0 +1,9 @@ +requires 'CPAN::Meta', 2.12091; +requires 'CPAN::Meta::Prereqs', 2.12091; +requires 'parent'; + +recommends 'Pod::Usage'; + +on test => sub { + requires 'Test::More', 0.88; +}; diff --git a/dist.ini b/dist.ini new file mode 100644 index 0000000..22b6051 --- /dev/null +++ b/dist.ini @@ -0,0 +1,4 @@ +name = Module-CPANfile + +[@Milla] +installer = MakeMaker diff --git a/lib/Module/CPANfile.pm b/lib/Module/CPANfile.pm new file mode 100644 index 0000000..dc11a17 --- /dev/null +++ b/lib/Module/CPANfile.pm @@ -0,0 +1,323 @@ +package Module::CPANfile; +use strict; +use warnings; +use Cwd; +use Carp (); +use Module::CPANfile::Environment; +use Module::CPANfile::Requirement; + +our $VERSION = '1.1000'; + +sub new { + my($class, $file) = @_; + bless {}, $class; +} + +sub load { + my($proto, $file) = @_; + + my $self = ref $proto ? $proto : $proto->new; + $self->parse($file || Cwd::abs_path('cpanfile')); + $self; +} + +sub save { + my($self, $path) = @_; + + open my $out, ">", $path or die "$path: $!"; + print {$out} $self->to_string; +} + +sub parse { + my($self, $file) = @_; + + my $code = do { + open my $fh, "<", $file or die "$file: $!"; + join '', <$fh>; + }; + + my $env = Module::CPANfile::Environment->new($file); + $env->parse($code) or die $@; + + $self->{_mirrors} = $env->mirrors; + $self->{_prereqs} = $env->prereqs; +} + +sub from_prereqs { + my($proto, $prereqs) = @_; + + my $self = $proto->new; + $self->{_prereqs} = Module::CPANfile::Prereqs->from_cpan_meta($prereqs); + + $self; +} + +sub mirrors { + my $self = shift; + $self->{_mirrors} || []; +} + +sub features { + my $self = shift; + map $self->feature($_), $self->{_prereqs}->identifiers; +} + +sub feature { + my($self, $identifier) = @_; + $self->{_prereqs}->feature($identifier); +} + +sub prereq { shift->prereqs } + +sub prereqs { + my $self = shift; + $self->{_prereqs}->as_cpan_meta; +} + +sub merged_requirements { + my $self = shift; + $self->{_prereqs}->merged_requirements; +} + +sub effective_prereqs { + my($self, $features) = @_; + $self->prereqs_with(@{$features || []}); +} + +sub prereqs_with { + my($self, @feature_identifiers) = @_; + + my $prereqs = $self->prereqs; + my @others = map { $self->feature($_)->prereqs } @feature_identifiers; + + $prereqs->with_merged_prereqs(\@others); +} + +sub prereq_specs { + my $self = shift; + $self->prereqs->as_string_hash; +} + +sub prereq_for_module { + my($self, $module) = @_; + $self->{_prereqs}->find($module); +} + +sub options_for_module { + my($self, $module) = @_; + my $prereq = $self->prereq_for_module($module) or return; + $prereq->requirement->options; +} + +sub merge_meta { + my($self, $file, $version) = @_; + + require CPAN::Meta; + + $version ||= $file =~ /\.yml$/ ? '1.4' : '2'; + + my $prereq = $self->prereqs; + + my $meta = CPAN::Meta->load_file($file); + my $prereqs_hash = $prereq->with_merged_prereqs($meta->effective_prereqs)->as_string_hash; + my $struct = { %{$meta->as_struct}, prereqs => $prereqs_hash }; + + CPAN::Meta->new($struct)->save($file, { version => $version }); +} + +sub _dump { + my $str = shift; + require Data::Dumper; + chomp(my $value = Data::Dumper->new([$str])->Terse(1)->Dump); + $value; +} + +sub to_string { + my($self, $include_empty) = @_; + + my $mirrors = $self->mirrors; + my $prereqs = $self->prereq_specs; + + my $code = ''; + $code .= $self->_dump_mirrors($mirrors); + $code .= $self->_dump_prereqs($prereqs, $include_empty); + + for my $feature ($self->features) { + $code .= sprintf "feature %s, %s => sub {\n", _dump($feature->{identifier}), _dump($feature->{description}); + $code .= $self->_dump_prereqs($feature->{spec}, $include_empty, 4); + $code .= "}\n\n"; + } + + $code =~ s/\n+$/\n/s; + $code; +} + +sub _dump_mirrors { + my($self, $mirrors) = @_; + + my $code = ""; + + for my $url (@$mirrors) { + $code .= "mirror '$url';\n"; + } + + $code =~ s/\n+$/\n/s; + $code; +} + +sub _dump_prereqs { + my($self, $prereqs, $include_empty, $base_indent) = @_; + + my $code = ''; + for my $phase (qw(runtime configure build test develop)) { + my $indent = $phase eq 'runtime' ? '' : ' '; + $indent = (' ' x ($base_indent || 0)) . $indent; + + my($phase_code, $requirements); + $phase_code .= "on $phase => sub {\n" unless $phase eq 'runtime'; + + for my $type (qw(requires recommends suggests conflicts)) { + for my $mod (sort keys %{$prereqs->{$phase}{$type}}) { + my $ver = $prereqs->{$phase}{$type}{$mod}; + $phase_code .= $ver eq '0' + ? "${indent}$type '$mod';\n" + : "${indent}$type '$mod', '$ver';\n"; + $requirements++; + } + } + + $phase_code .= "\n" unless $requirements; + $phase_code .= "};\n" unless $phase eq 'runtime'; + + $code .= $phase_code . "\n" if $requirements or $include_empty; + } + + $code =~ s/\n+$/\n/s; + $code; +} + +1; + +__END__ + +=head1 NAME + +Module::CPANfile - Parse cpanfile + +=head1 SYNOPSIS + + use Module::CPANfile; + + my $file = Module::CPANfile->load("cpanfile"); + my $prereqs = $file->prereqs; # CPAN::Meta::Prereqs object + + my @features = $file->features; # CPAN::Meta::Feature objects + my $merged_prereqs = $file->prereqs_with(@identifiers); # CPAN::Meta::Prereqs + + $file->merge_meta('MYMETA.json'); + +=head1 DESCRIPTION + +Module::CPANfile is a tool to handle L<cpanfile> format to load application +specific dependencies, not just for CPAN distributions. + +=head1 METHODS + +=over 4 + +=item load + + $file = Module::CPANfile->load; + $file = Module::CPANfile->load('cpanfile'); + +Load and parse a cpanfile. By default it tries to load C<cpanfile> in +the current directory, unless you pass the path to its argument. + +=item from_prereqs + + $file = Module::CPANfile->from_prereqs({ + runtime => { requires => { DBI => '1.000' } }, + }); + +Creates a new Module::CPANfile object from prereqs hash you can get +via L<CPAN::Meta>'s C<prereqs>, or L<CPAN::Meta::Prereqs>' +C<as_string_hash>. + + # read MYMETA, then feed the prereqs to create Module::CPANfile + my $meta = CPAN::Meta->load_file('MYMETA.json'); + my $file = Module::CPANfile->from_prereqs($meta->prereqs); + + # load cpanfile, then recreate it with round-trip + my $file = Module::CPANfile->load('cpanfile'); + $file = Module::CPANfile->from_prereqs($file->prereq_specs); + # or $file->prereqs->as_string_hash + +=item prereqs + +Returns L<CPAN::Meta::Prereqs> object out of the parsed cpanfile. + +=item prereq_specs + +Returns a hash reference that should be passed to C<< CPAN::Meta::Prereqs->new >>. + +=item features + +Returns a list of features available in the cpanfile as L<CPAN::Meta::Feature>. + +=item prereqs_with(@identifiers), effective_prereqs(\@identifiers) + +Returns L<CPAN::Meta::Prereqs> object, with merged prereqs for +features identified with the C<@identifiers>. + +=item to_string($include_empty) + + $file->to_string; + $file->to_string(1); + +Returns a canonical string (code) representation for cpanfile. Useful +if you want to convert L<CPAN::Meta::Prereqs> to a new cpanfile. + + # read MYMETA's prereqs and print cpanfile representation of it + my $meta = CPAN::Meta->load_file('MYMETA.json'); + my $file = Module::CPANfile->from_prereqs($meta->prereqs); + print $file->to_string; + +By default, it omits the phase where there're no modules +registered. If you pass the argument of a true value, it will print +them as well. + +=item save + + $file->save('cpanfile'); + +Saves the currently loaded prereqs as a new C<cpanfile> by calling +C<to_string>. Beware B<this method will overwrite the existing +cpanfile without any warning or backup>. Taking a backup or giving +warnings to users is a caller's responsibility. + + # Read MYMETA.json and creates a new cpanfile + my $meta = CPAN::Meta->load_file('MYMETA.json'); + my $file = Module::CPANfile->from_prereqs($meta->prereqs); + $file->save('cpanfile'); + +=item merge_meta + + $file->merge_meta('META.yml'); + $file->merge_meta('MYMETA.json', '2.0'); + +Merge the effective prereqs with Meta specification loaded from the +given META file, using CPAN::Meta. You can specify the META spec +version in the second argument, which defaults to 1.4 in case the +given file is YAML, and 2 if it is JSON. + +=back + +=head1 AUTHOR + +Tatsuhiko Miyagawa + +=head1 SEE ALSO + +L<cpanfile>, L<CPAN::Meta>, L<CPAN::Meta::Spec> + +=cut diff --git a/lib/Module/CPANfile/Environment.pm b/lib/Module/CPANfile/Environment.pm new file mode 100644 index 0000000..e1c0ea1 --- /dev/null +++ b/lib/Module/CPANfile/Environment.pm @@ -0,0 +1,173 @@ +package Module::CPANfile::Environment; +use strict; +use warnings; +use Module::CPANfile::Prereqs; +use Carp (); + +my @bindings = qw( + on requires recommends suggests conflicts + feature + osname + mirror + configure_requires build_requires test_requires author_requires +); + +my $file_id = 1; + +sub new { + my($class, $file) = @_; + bless { + file => $file, + phase => 'runtime', # default phase + feature => undef, + features => {}, + prereqs => Module::CPANfile::Prereqs->new, + mirrors => [], + }, $class; +} + +sub bind { + my $self = shift; + my $pkg = caller; + + for my $binding (@bindings) { + no strict 'refs'; + *{"$pkg\::$binding"} = sub { $self->$binding(@_) }; + } +} + +sub parse { + my($self, $code) = @_; + + my $err; + { + local $@; + $file_id++; + $self->_evaluate(<<EVAL); +package Module::CPANfile::Sandbox$file_id; +no warnings; +BEGIN { \$_environment->bind } + +# line 1 "$self->{file}" +$code; +EVAL + $err = $@; + } + + if ($err) { die "Parsing $self->{file} failed: $err" }; + + return 1; +} + +sub _evaluate { + my $_environment = $_[0]; + eval $_[1]; +} + +sub prereqs { $_[0]->{prereqs} } + +sub mirrors { $_[0]->{mirrors} } + +# DSL goes from here + +sub on { + my($self, $phase, $code) = @_; + local $self->{phase} = $phase; + $code->(); +} + +sub feature { + my($self, $identifier, $description, $code) = @_; + + # shortcut: feature identifier => sub { ... } + if (@_ == 3 && ref($description) eq 'CODE') { + $code = $description; + $description = $identifier; + } + + unless (ref $description eq '' && ref $code eq 'CODE') { + Carp::croak("Usage: feature 'identifier', 'Description' => sub { ... }"); + } + + local $self->{feature} = $identifier; + $self->prereqs->add_feature($identifier, $description); + + $code->(); +} + +sub osname { die "TODO" } + +sub mirror { + my($self, $url) = @_; + push @{$self->{mirrors}}, $url; +} + +sub requirement_for { + my($self, $module, @args) = @_; + + my $requirement = 0; + $requirement = shift @args if @args % 2; + + return Module::CPANfile::Requirement->new( + name => $module, + version => $requirement, + @args, + ); +} + +sub requires { + my $self = shift; + $self->add_prereq(requires => @_); +} + +sub recommends { + my $self = shift; + $self->add_prereq(recommends => @_); +} + +sub suggests { + my $self = shift; + $self->add_prereq(suggests => @_); +} + +sub conflicts { + my $self = shift; + $self->add_prereq(conflicts => @_); +} + +sub add_prereq { + my($self, $type, $module, @args) = @_; + + $self->prereqs->add_prereq( + feature => $self->{feature}, + phase => $self->{phase}, + type => $type, + module => $module, + requirement => $self->requirement_for($module, @args), + ); +} + +# Module::Install compatible shortcuts + +sub configure_requires { + my($self, @args) = @_; + $self->on(configure => sub { $self->requires(@args) }); +} + +sub build_requires { + my($self, @args) = @_; + $self->on(build => sub { $self->requires(@args) }); +} + +sub test_requires { + my($self, @args) = @_; + $self->on(test => sub { $self->requires(@args) }); +} + +sub author_requires { + my($self, @args) = @_; + $self->on(develop => sub { $self->requires(@args) }); +} + +1; + diff --git a/lib/Module/CPANfile/Prereq.pm b/lib/Module/CPANfile/Prereq.pm new file mode 100644 index 0000000..cf675f1 --- /dev/null +++ b/lib/Module/CPANfile/Prereq.pm @@ -0,0 +1,21 @@ +package Module::CPANfile::Prereq; +use strict; + +sub new { + my($class, %options) = @_; + bless \%options, $class; +} + +sub feature { $_[0]->{feature} } +sub phase { $_[0]->{phase} } +sub type { $_[0]->{type} } +sub module { $_[0]->{module} } +sub requirement { $_[0]->{requirement} } + +sub match_feature { + my($self, $identifier) = @_; + no warnings 'uninitialized'; + $self->feature eq $identifier; +} + +1; diff --git a/lib/Module/CPANfile/Prereqs.pm b/lib/Module/CPANfile/Prereqs.pm new file mode 100644 index 0000000..c3126ea --- /dev/null +++ b/lib/Module/CPANfile/Prereqs.pm @@ -0,0 +1,117 @@ +package Module::CPANfile::Prereqs; +use strict; +use Carp (); +use CPAN::Meta::Feature; +use Module::CPANfile::Prereq; + +sub from_cpan_meta { + my($class, $prereqs) = @_; + + my $self = $class->new; + + for my $phase (keys %$prereqs) { + for my $type (keys %{ $prereqs->{$phase} }) { + while (my($module, $requirement) = each %{ $prereqs->{$phase}{$type} }) { + $self->add_prereq( + phase => $phase, + type => $type, + module => $module, + requirement => Module::CPANfile::Requirement->new(name => $module, version => $requirement), + ); + } + } + } + + $self; +} + +sub new { + my $class = shift; + bless { + prereqs => [], + features => {}, + }, $class; +} + +sub add_feature { + my($self, $identifier, $description) = @_; + $self->{features}{$identifier} = { description => $description }; +} + +sub add_prereq { + my($self, %args) = @_; + $self->add( Module::CPANfile::Prereq->new(%args) ); +} + +sub add { + my($self, $prereq) = @_; + push @{$self->{prereqs}}, $prereq; +} + +sub as_cpan_meta { + my $self = shift; + $self->{cpanmeta} ||= $self->build_cpan_meta; +} + +sub build_cpan_meta { + my($self, $identifier) = @_; + + my $prereq_spec = {}; + $self->prereq_each($identifier, sub { + my $prereq = shift; + $prereq_spec->{$prereq->phase}{$prereq->type}{$prereq->module} = $prereq->requirement->version; + }); + + CPAN::Meta::Prereqs->new($prereq_spec); +} + +sub prereq_each { + my($self, $identifier, $code) = @_; + + for my $prereq (@{$self->{prereqs}}) { + next unless $prereq->match_feature($identifier); + $code->($prereq); + } +} + +sub merged_requirements { + my $self = shift; + + my $reqs = CPAN::Meta::Requirements->new; + for my $prereq (@{$self->{prereqs}}) { + $reqs->add_string_requirement($prereq->module, $prereq->requirement->version); + } + + $reqs; +} + +sub find { + my($self, $module) = @_; + + for my $prereq (@{$self->{prereqs}}) { + return $prereq if $prereq->module eq $module; + } + + return; +} + +sub identifiers { + my $self = shift; + keys %{$self->{features}}; +} + +sub feature { + my($self, $identifier) = @_; + + my $data = $self->{features}{$identifier} + or Carp::croak("Unknown feature '$identifier'"); + + my $prereqs = $self->build_cpan_meta($identifier); + + CPAN::Meta::Feature->new($identifier, { + description => $data->{description}, + prereqs => $prereqs->as_string_hash, + }); +} + +1; diff --git a/lib/Module/CPANfile/Requirement.pm b/lib/Module/CPANfile/Requirement.pm new file mode 100644 index 0000000..01c6358 --- /dev/null +++ b/lib/Module/CPANfile/Requirement.pm @@ -0,0 +1,25 @@ +package Module::CPANfile::Requirement; +use strict; + +sub new { + my ($class, %args) = @_; + + $args{version} ||= 0; + + bless +{ + name => delete $args{name}, + version => delete $args{version}, + options => \%args, + }, $class; +} + +sub name { $_[0]->{name} } +sub version { $_[0]->{version} } + +sub options { $_[0]->{options} } + +sub has_options { + keys %{$_[0]->{options}} > 0; +} + +1; diff --git a/lib/cpanfile-faq.pod b/lib/cpanfile-faq.pod new file mode 100644 index 0000000..56548d8 --- /dev/null +++ b/lib/cpanfile-faq.pod @@ -0,0 +1,128 @@ +=head1 NAME + +cpanfile-faq - cpanfile FAQ + +=head1 QUESTIONS + +=head2 Does cpanfile replace Makefile.PL/Build.PL or META.yml/json? + +No, it doesn't. C<cpanfile> is a simpler way to declare CPAN +dependencies, mainly for I<your application> rather than CPAN +distributions. + +However, while CPAN distributions do not need to B<switch> to +C<cpanfile>, you can certainly I<manage> the dependencies in +C<cpanfile>, then export them into C<META.json> files when shipping to +CPAN, using tools such as L<Dist::Milla> or L<Module::Install::CPANfile> + +=head2 Why do we need yet another format? + +Here are some of the reasons that motivates the new L<cpanfile> +format. + +=over 4 + +=item Not everything is a CPAN distribution + +First of all, it is annoying to write (a dummy) C<Makefile.PL> when +what you develop is not a CPAN distribution, just so that installation +like C<cpanm --installdeps .> would work. + +It gets more painful when you develop a web application that you want +to deploy on a different environment using version control system +(such as PaaS/cloud infrastructure), because it requires you to often +commit the META file or C<inc/> directory (or even worse, both) to a +repository. + +Many web application frameworks generate a boiler-plate C<Makefile.PL> +for dependency declaration and to let you install dependencies with +C<< cpanm --installdeps . >>, but that doesn't always mean they are +meant to be installed. Things can be often much simpler if you run the +application from the checkout directory. + +With L<cpanfile>, dependencies can be installed either globally or +locally using supported tools such as L<cpanm> or L<Carton>. Because +C<cpanfile> lists all the dependencies of your entire application and +will be updated over time, it makes perfect sense to commit the file +to a version control system, and push the file for a deployment. + +=item Familiar DSL syntax + +This is a new file type, but the format and syntax isn't entirely +new. The metadata it can declare is exactly a subset of "Prereqs" in +L<CPAN Meta Spec|CPAN::Meta::Spec>. + +The syntax borrows a lot from L<Module::Install>. Module::Install is a +great way to easily declare module metadata such as name, author and +dependencies. L<cpanfile> format is simply to extract the dependencies +into a separate file, which means most of the developers are familiar +with the syntax. + +=item Complete CPAN Meta Spec v2 support + +C<cpanfile> basically allows you to declare L<CPAN::Meta::Spec> +prerequisite specification using an easy Perl DSL syntax. This makes +it easy to declare per-phase dependencies and newer version 2 features +such as conflicts and version ranges. + +=back + +=head2 How can I start using C<cpanfile>? + +First of all, most distributions on CPAN are not required to update to +this format. + +If your application currently uses C<Makefile.PL> etc. for dependency +declaration because of the current toolchain implementation (e.g. C<< +cpanm --installdeps . >>), you can upgrade to C<cpanfile> while +keeping the build file based installation working for the backward +compatibility. + +If you are an author of CPAN module and want to manage CPAN module +prerequisites using C<cpanfile> you can use one of the following +tools: + +=over 4 + +=item Dist::Milla + +L<Dist::Milla> is a profile for L<Dist::Zilla> that has a C<cpanfile> +support to declare dependencies for your module. + +=item Dist::Zilla + +L<Dist::Zilla::Plugin::Prereqs::FromCPANfile> provides a way to merge +dependencies declared in C<cpanfile> into META files as well as build +files. You can combine them using other prerequisite scanners like +C<AutoPrereqs>. + +=item Minilla + +L<Minilla> is a yet another authoring tool that supports C<cpanfile> +as a way to describe dependencies for your CPAN module. + +=item Module::Install + +L<Module::Install::CPANfile> provides a C<cpanfile> DSL that reads +C<cpanfile> to merge prerequisites when dumping C<MYMETA> files upon +installation. + +=item Module::Build + +L<Module::Build::Pluggable::CPANfile> merges C<cpanfile> dependencies +from C<Build.PL> when dumping out MYMETA information. + +However you're recommended to switch to an authoring system that emits +C<Build.PL> with parsed CPANfile information, like L<Dist::Zilla> +mentioned above. + +=item ExtUtils::MakeMaker + +L<ExtUtils::MakeMaker::CPANfile> merges C<cpanfile> prerequisites +when dumping C<MYMETA> files upon installation. + +However you're recommended to switch to an authoring system that emits +C<Makefile.PL> with parsed CPANfile information, like L<Dist::Zilla> +mentioned above. + +=back diff --git a/lib/cpanfile.pod b/lib/cpanfile.pod new file mode 100644 index 0000000..a554dd6 --- /dev/null +++ b/lib/cpanfile.pod @@ -0,0 +1,128 @@ +=head1 NAME + +cpanfile - A format for describing CPAN dependencies for Perl applications + +=head1 SYNOPSIS + + requires 'Plack', '1.0'; # 1.0 or newer + requires 'JSON', '>= 2.00, < 2.80'; + + recommends 'JSON::XS', '2.0'; + conflicts 'JSON', '< 1.0'; + + on 'test' => sub { + requires 'Test::More', '>= 0.96, < 2.0'; + recommends 'Test::TCP', '1.12'; + }; + + on 'develop' => sub { + recommends 'Devel::NYTProf'; + }; + + feature 'sqlite', 'SQLite support' => sub { + recommends 'DBD::SQLite'; + }; + +=head1 VERSION + +This document describes cpanfile format version 1.0. + +=head1 DESCRIPTION + +C<cpanfile> describes CPAN dependencies required to execute associated +Perl code. + +=head1 SYNTAX + +=over 4 + +=item requires, recommends, suggests, conflicts + + requires $module, $version_requirement; + +Describes the requirement for a module. See L<CPAN::Meta::Spec> for +the meanings of each requirement type. + +When version requirement is omitted, it is assumed that C<0> is +passed, meaning any version of the module would satisfy the +requirement. + +Version requirement can either be a version number or a string that +satisfies L<CPAN::Meta::Spec/Version Ranges>, such as C<< >= 1.0, != +1.1 >>. + +Note that, per L<CPAN::Meta::Spec>, when a plain version number is +given, it means the version I<or newer> is required. If you want a +specific version for a module, use the specific range syntax, i.e. +C< == 2.1 >. + +=item on + + on $phase => sub { ... }; + +Describe requirements for a specific phase. Available phases are +C<configure>, C<build>, C<test>, C<runtime> and C<develop>. + +=item feature + + feature $identifier, $description => sub { ... }; + +Group requirements with features. Description can be omitted, when it +is assumed to be the same as identifier. See +L<CPAN::Meta::Spec/optional_features> for more details. + +=item configure_requires, build_requires, test_requires, author_requires + + configure_requires $module, $version; + # on 'configure' => sub { requires $module, $version } + + build_requires $module, $version; + # on 'build' => sub { requires $module, $version }; + + test_requires $module, $version; + # on 'test' => sub { requires $module, $version }; + + author_requires $module, $version; + # on 'develop' => sub { requires $module, $version }; + +Shortcut for C<requires> in specific phase. This is mainly provided +for compatibilities with L<Module::Install> DSL. + +=back + +=head1 USAGE + +C<cpanfile> is a format to describe dependencies. How to use this file +is dependent on the tools reading/writing it. + +Usually, you're expected to place the C<cpanfile> in the root of the +directory containing the associated code. + +Tools supporting C<cpanfile> format (e.g. L<cpanm> and L<carton>) will +automatically detect the file and install dependencies for the code to +run. + +There are also tools to support converting cpanfile to CPAN toolchain +compatible formats, such as L<Module::CPANfile>, +L<Dist::Zilla::Plugin::Prereqs::FromCPANfile>, +L<Module::Install::CPANfile>, so that C<cpanfile> can be used to +describe dependencies for a CPAN distribution as well. + +=head1 AUTHOR + +Tatsuhiko Miyagawa + +=head1 ACKNOWLEDGEMENTS + +The format (DSL syntax) is inspired by L<Module::Install> and +L<Module::Build::Functions>. + +C<cpanfile> specification (this document) is based on Ruby's +L<Gemfile|http://bundler.io/v1.3/man/gemfile.5.html> specification. + +=head1 SEE ALSO + +L<CPAN::Meta::Spec> L<Module::Install> L<Carton> + +=cut + diff --git a/script/cpanfile-dump b/script/cpanfile-dump new file mode 100755 index 0000000..04e1736 --- /dev/null +++ b/script/cpanfile-dump @@ -0,0 +1,131 @@ +#!perl +use strict; +use warnings; +use CPAN::Meta::Requirements; +use Module::CPANfile; +use Getopt::Long qw(:config posix_default no_ignore_case gnu_compat); + +my @phases = qw(configure build test develop runtime); +my @types = qw(requires recommends suggests conflicts); + +my %o = map { $_ => 1 } qw/configure build test runtime requires recommends/; + +GetOptions( + "h|help", \$o{help}, + "with-feature=s@", \$o{with}, + "without-feature=s@", \$o{without}, + "with-all-features", \$o{with_all}, + map { ("$_!", \$o{$_}) } (@phases, @types), +); + +if ($o{conflicts}) { + delete $o{$_} for qw/requires recommends suggests/; +} + +if ($o{help}) { + if (eval { require Pod::Usage; 1 }) { + Pod::Usage::pod2usage(1); + } else { + die "Usage: cpanfile-dump\n\nSee perldoc cpanfile-dump for more details.\n"; + } +} + +my $file = Module::CPANfile->load("cpanfile"); + +my %excludes = map { $_ => 1 } @{$o{without}}; +my @features = grep { !$excludes{$_} } $o{with_all} + ? ( map { $_->identifier } $file->features ) + : @{$o{with}}; + +my $prereqs = $file->prereqs_with( @features ); # CPAN::Meta::Prereqs object + +my $merged = CPAN::Meta::Requirements->new; + +for my $phase ( @phases ) { + next unless $o{$phase}; + for my $type ( @types ) { + next unless $o{$type}; + $merged->add_requirements( $prereqs->requirements_for( $phase, $type ) ); + } +} + +print "$_\n" for sort $merged->required_modules; + + +__END__ + +=head1 NAME + +cpanfile-dump - Dump prerequisites from a cpanfile + +=head1 SYNOPSIS + + # Install typical required and recommended modules + cpan `cpanfile-dump` + + # Skip configures phase + cpan `cpanfile-dump --no-configure` + + # Also include develop phase and suggests type + cpan `cpanfile-dump --develop --suggests` + + # Include a feature + cpan `cpanfile-dump --with-feature=sqlite` + +=head1 DESCRIPTION + +This script reads prereqs from a F<cpanfile> and dumps a raw list of +them to standard output. This is useful for piping these as input to +another program that doesn't support reading cpanfile directly, +i.e. C<cpan> or C<cpanp>. + +By default, it prints configure, build, test and runtime requirements and +recommendations. Command line options can be used to modify the default +choices. + +This script is distributed with L<Module::CPANfile> since version 1.0002. + +=head1 OPTIONS + +=over 4 + +=item --configure, --build, --test, --runtime, --develop + +Specify the phase to include/exclude. Defaults to include all but +C<--develop> but you can exclude some phases by specifying the options with +C<--no-> prefix, like C<--no-configure>. + +=item --requires, --recommends, --suggests, --conflicts + +Specify the type to include/exclude. Defaults to include only C<--requires> and +C<--recommends> but you can exclude some types by specifying the options with +C<--no-> prefix, like C<--no-recommends>. + +Specifying C<--conflicts> will turn off all other types (even if specified +on the command line). + +=item --with-feature, --with-all-features, --without-feature + + cpanfile-dump --with-feature=sqlite + cpanfile-dump --with-all-features --without-feature=yaml + +Specify features to include in the dump. C<--with-feature> and C<--without-feature> +may be used more than once. + +=back + +=head1 NOTES + +Because C<cpanm> supports reading cpanfile directly, instead of piping the output of this +program, you're recommended to use C<cpanm --installdeps .> to install modules from cpanfile. + +=head1 AUTHOR + +David Golden + +=head1 SEE ALSO + +L<Module::CPANfile> L<cpanfile> L<App::mymeta_requires> + +=cut + diff --git a/script/mymeta-cpanfile b/script/mymeta-cpanfile new file mode 100755 index 0000000..66c4916 --- /dev/null +++ b/script/mymeta-cpanfile @@ -0,0 +1,115 @@ +#!perl +use strict; +use warnings; +use CPAN::Meta; +use Module::CPANfile; +use Getopt::Long qw(:config posix_default no_ignore_case gnu_compat); + +my @phases = qw(configure build test develop runtime); +my @types = qw(requires recommends suggests conflicts); + +my %o = map { $_ => 1 } @phases, @types; # default all + +GetOptions( + "include-empty!", \$o{include_empty}, + "h|help", \$o{help}, + map { ("$_!", \$o{$_}) } (@phases, @types), +); + +if ($o{help}) { + if (eval { require Pod::Usage; 1 }) { + Pod::Usage::pod2usage(1); + } else { + die "Usage: mymeta-cpanfile\n\nSee perldoc mymeta-cpanfile for more details.\n"; + } +} + +sub get_mymeta { + for my $file (qw( MYMETA.json MYMETA.yml META.json META.yml )) { + next unless -r $file; + my $meta = eval { CPAN::Meta->load_file($file) }; + return $meta if $meta; + } +} + +my $meta = get_mymeta or die "Could not locate any META files\n"; + +my $prereqs = $meta->prereqs; +my $filtered = {}; + +while (my($phase, $types) = each %$prereqs) { + next unless $o{$phase}; + while (my($type, $reqs) = each %$types) { + next unless $o{$type}; + $filtered->{$phase}{$type} = $reqs; + } +} + +my $cpanfile = Module::CPANfile->from_prereqs($filtered); +print $cpanfile->to_string($o{include_empty}); + +__END__ + +=head1 NAME + +mymeta-cpanfile - Dump cpanfile out of (MY)META files + +=head1 SYNOPSIS + + perl Makefile.PL # or Build.PL + mymeta-cpanfile + + # Skip configures phase and suggests type + mymeta-cpanfile --no-configure --no-suggests + + # Include empty blcok for phases without prereqs + mymeta-cpanfile --include-empty + +=head1 DESCRIPTION + +This script reads prereqs metadata from MYMETA files in the current +directory and prints C<cpanfile> that represents the prereqs. Useful +when you want to migrate to using L<cpanfile> from existing +C<Makefile.PL> or C<Build.PL> with dependency specification. + +This script is distributed with L<Module::CPANfile> since version 0.9021. + +=head1 OPTIONS + +=over 4 + +=item --configure, --build, --test, --runtime, --develop + +Specify the phase to include/exclude. Defaults to include all phases, +but you can exclude some phases by specifying the options with +C<--no-> prefix, like C<--no-configure>. + +=item --requires, --recommends, --suggests, --conflicts + +Specify the type to include/exclude. Defaults to include all types, +but you can exclude some types by specifying the options with C<--no-> +prefix, like C<--no-conflicts>. + +=item --include-empty + +By default, phases without any prereqs are not dumped, By giving this +option, cpanfile will have an empty block such as: + + on test => sub { + + }; + +Defaults to false. + +=back + +=head1 AUTHOR + +Tatsuhiko Miyagawa + +=head1 SEE ALSO + +L<Module::CPANfile> L<cpanfile> L<App::mymeta_requires> + +=cut + diff --git a/t/Utils.pm b/t/Utils.pm new file mode 100644 index 0000000..f59983c --- /dev/null +++ b/t/Utils.pm @@ -0,0 +1,39 @@ +package t::Utils; +use base qw(Exporter); + +our @EXPORT = qw(write_cpanfile write_files); + +sub write_cpanfile { + write_files('cpanfile' => $_[0]); +} + +sub write_files { + my %files = @_; + + my $dir = "t/sample-" . rand(100000); + mkdir $dir; + chdir $dir; + + for my $file (keys %files) { + open my $fh, ">", $file or die $!; + print $fh $files{$file}; + } + + return Remover->new($dir, [ keys %files ]); +} + +package + Remover; +sub new { + bless { dir => $_[1], files => $_[2] }, $_[0]; +} + +sub DESTROY { + my $self = shift; + for my $file (@{$self->{files}}) { + unlink $file; + } + chdir "../.."; + rmdir $self->{dir}; +} + diff --git a/t/feature.t b/t/feature.t new file mode 100644 index 0000000..2600b77 --- /dev/null +++ b/t/feature.t @@ -0,0 +1,87 @@ +use strict; +use Module::CPANfile; +use Test::More; +use t::Utils; + +{ + my $r = write_cpanfile(<<FILE); +on test => sub { + requires 'Test::More', '0.90'; +}; + +feature 'sqlite' => sub { + on runtime => sub { requires 'DBD::SQLite' }, +}; +FILE + my $cpanfile = Module::CPANfile->load; + my @features = $cpanfile->features; + is $features[0]->identifier, 'sqlite'; + is $features[0]->description, 'sqlite'; +} + +{ + my $r = write_cpanfile(<<FILE); +on test => sub { + requires 'Test::More', '0.90'; +}; + +feature 'sqlite', 'SQLite support' => sub { + on runtime => sub { requires 'DBD::SQLite' }, +}; +FILE + my $cpanfile = Module::CPANfile->load; + + my @features = $cpanfile->features; + is @features, 1; + ok $features[0]->isa('CPAN::Meta::Feature'); + is $features[0]->identifier, 'sqlite'; + is $features[0]->description, 'SQLite support'; + ok $features[0]->prereqs; + + is_deeply $features[0]->prereqs->as_string_hash, { runtime => { requires => { 'DBD::SQLite' => '0' } } }; + + { + my $prereqs = $cpanfile->prereqs; + is_deeply $prereqs->as_string_hash, { + test => { requires => { 'Test::More' => '0.90' } }, + }; + } + + { + my $prereqs = $cpanfile->effective_prereqs; + is_deeply $prereqs->as_string_hash, { + test => { requires => { 'Test::More' => '0.90' } }, + }; + } + + { + my $prereqs = $cpanfile->prereqs_with('sqlite'); + is_deeply $prereqs->as_string_hash, { + test => { requires => { 'Test::More' => '0.90' } }, + runtime => { requires => { 'DBD::SQLite' => '0' } }, + }; + } + + { + my $prereqs = $cpanfile->effective_prereqs(['sqlite']); + is_deeply $prereqs->as_string_hash, { + test => { requires => { 'Test::More' => '0.90' } }, + runtime => { requires => { 'DBD::SQLite' => '0' } }, + }; + } + + { + eval { my $prereqs = $cpanfile->prereqs_with('foobar') }; + like $@, qr/Unknown feature 'foobar'/; + } + + { + # no features, it's ok + eval { my $prereqs = $cpanfile->prereqs_with() }; + ok !$@, $@; + } + + like $cpanfile->to_string, qr/feature/; +} + +done_testing; diff --git a/t/from_prereqs.t b/t/from_prereqs.t new file mode 100644 index 0000000..eaaf984 --- /dev/null +++ b/t/from_prereqs.t @@ -0,0 +1,36 @@ +use strict; +use Test::More; + +use Module::CPANfile; +use t::Utils; + +{ + my $r = write_cpanfile(<<FILE); +requires 'perl', '5.008001'; +requires 'DBI'; +requires 'Plack', '1.0001'; +test_requires 'Test::More', '0.90, != 0.91'; +FILE + + my $prereqs = Module::CPANfile->load->prereqs; + my $file = Module::CPANfile->from_prereqs($prereqs->as_string_hash); + + is_deeply $file->prereq_specs, $prereqs->as_string_hash; + + is $file->to_string, <<FILE; +requires 'DBI'; +requires 'Plack', '1.0001'; +requires 'perl', '5.008001'; + +on test => sub { + requires 'Test::More', '>= 0.90, != 0.91'; +}; +FILE + + $file->save('cpanfile'); + + my $content = do { local $/; open my $in, 'cpanfile'; <$in> }; + is $content, $file->to_string; +} + +done_testing; diff --git a/t/merge.t b/t/merge.t new file mode 100644 index 0000000..f082cbd --- /dev/null +++ b/t/merge.t @@ -0,0 +1,65 @@ +use strict; +use Module::CPANfile; +use Test::More; +use t::Utils; + +{ + my $r = write_files(cpanfile => <<CPANFILE, 'META.json' => <<META); +requires 'Plack', '0.9970'; + +on 'test' => sub { + requires 'Test::More', '0.90'; +}; + +on 'develop' => sub { + requires 'Catalyst::Runtime', '> 5.8000, < 5.9'; +}; +CPANFILE +{ + "abstract" : "A format for describing CPAN dependencies of Perl applications", + "author" : [ + "Tatsuhiko Miyagawa" + ], + "dynamic_config" : 0, + "generated_by" : "ExtUtils::MakeMaker version 6.64, CPAN::Meta::Converter version 2.120921", + "meta-spec" : { + "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", + "version" : "2" + }, + "name" : "Module-CPANfile", + "prereqs" : { + "build" : { + "requires" : { + "ExtUtils::MakeMaker" : "0" + } + }, + "configure" : { + "requires" : { + "ExtUtils::MakeMaker" : "6.31" + } + }, + "runtime" : { + "requires" : { + "perl" : "5.008001", + "Plack" : "0.9000" + } + } + }, + "version" : "0.9007" +} +META + + my $file = Module::CPANfile->load; + $file->merge_meta('META.json'); + + my $meta = CPAN::Meta->load_file('META.json'); + is_deeply $meta->prereqs, { + build => { requires => { 'ExtUtils::MakeMaker' => 0 } }, + configure => { requires => { 'ExtUtils::MakeMaker' => '6.31' } }, + runtime => { requires => { 'perl' => '5.008001', 'Plack' => '0.9970' } }, + develop => { requires => { 'Catalyst::Runtime' => '> 5.8000, < 5.9' } }, + test => { requires => { 'Test::More' => '0.90' } }, + }; +} + +done_testing; diff --git a/t/mirror.t b/t/mirror.t new file mode 100644 index 0000000..0aa3a54 --- /dev/null +++ b/t/mirror.t @@ -0,0 +1,38 @@ +use strict; +use Module::CPANfile; +use Test::More; +use t::Utils; + +{ + my $r = write_cpanfile(<<FILE); +mirror 'http://www.cpan.org'; +mirror 'http://backpan.cpan.org'; + +requires 'DBI'; +requires 'Plack', '0.9970'; + +on 'test' => sub { + requires 'Test::More'; +}; +FILE + + my $file = Module::CPANfile->load; + + my $prereq = $file->prereq; + is_deeply $prereq->as_string_hash, { + test => { + requires => { 'Test::More' => 0 }, + }, + runtime => { + requires => { 'Plack' => '0.9970', 'DBI' => 0 }, + }, + }; + + my $mirrors = $file->mirrors; + is_deeply $mirrors, [ 'http://www.cpan.org', 'http://backpan.cpan.org' ]; + + like $file->to_string, qr{mirror 'http://www.cpan.org';}; + like $file->to_string, qr{mirror 'http://backpan.cpan.org';}; +} + +done_testing; diff --git a/t/parse.t b/t/parse.t new file mode 100644 index 0000000..6948d26 --- /dev/null +++ b/t/parse.t @@ -0,0 +1,74 @@ +use strict; +use Module::CPANfile; +use Test::More; +use POSIX qw(locale_h); +use t::Utils; + +{ + # Use the traditional UNIX system locale to check the error message string. + my $old_locale = setlocale(LC_ALL); + setlocale(LC_ALL, 'C'); + eval { + my $file = Module::CPANfile->load('foo'); + }; + like $@, qr/No such file/; + setlocale(LC_ALL, $old_locale); +} + +{ + my $r = write_cpanfile(<<FILE); +foo(); +FILE + eval { Module::CPANfile->load }; + like $@, qr/cpanfile line 1/; +} + +{ + my $r = write_cpanfile("# %4N bug"); + eval { Module::CPANfile->load }; + is $@, ''; +} + +{ + my $r = write_cpanfile(<<FILE); +configure_requires 'ExtUtils::MakeMaker', 5.5; + +requires 'DBI'; +requires 'Plack', '0.9970'; +conflicts 'Moose', '< 0.8'; + +on 'test' => sub { + requires 'Test::More'; +}; + +on 'develop' => sub { + requires 'Catalyst::Runtime', '> 5.8000, < 5.9'; + recommends 'Catalyst::Plugin::Foo'; +}; + +test_requires 'Test::Warn', 0.1; +author_requires 'Module::Install', 0.99; +FILE + + my $file = Module::CPANfile->load; + my $prereq = $file->prereq; + + is_deeply $prereq->as_string_hash, { + configure => { + requires => { 'ExtUtils::MakeMaker' => '5.5' }, + }, + test => { + requires => { 'Test::More' => 0, 'Test::Warn' => '0.1' }, + }, + runtime => { + requires => { 'Plack' => '0.9970', 'DBI' => 0 }, + conflicts => { 'Moose' => '< 0.8' }, + }, + develop => { + requires => { 'Catalyst::Runtime' => '> 5.8000, < 5.9', 'Module::Install' => '0.99' }, + recommends => { 'Catalyst::Plugin::Foo' => 0 }, + } + }; +} + +done_testing; diff --git a/t/release-pod-syntax.t b/t/release-pod-syntax.t new file mode 100644 index 0000000..cdd6a6c --- /dev/null +++ b/t/release-pod-syntax.t @@ -0,0 +1,14 @@ +#!perl + +BEGIN { + unless ($ENV{RELEASE_TESTING}) { + require Test::More; + Test::More::plan(skip_all => 'these tests are for release candidate testing'); + } +} + +# This file was automatically generated by Dist::Zilla::Plugin::PodSyntaxTests. +use Test::More; +use Test::Pod 1.41; + +all_pod_files_ok(); diff --git a/t/requirement.t b/t/requirement.t new file mode 100644 index 0000000..32fd2ee --- /dev/null +++ b/t/requirement.t @@ -0,0 +1,81 @@ +use strict; +use Module::CPANfile; +use Test::More; +use t::Utils; + +subtest 'full set' => sub { + my $r = write_cpanfile(<<FILE); +requires 'Plack', '0.9970', + git => 'git://github.com/plack/Plack.git', ref => '0.9970'; +FILE + + my $file = Module::CPANfile->load; + is_deeply $file->prereq_specs, { + runtime => { + requires => { 'Plack' => '0.9970' }, + }, + }; + + my $req = $file->prereqs->requirements_for(runtime => 'requires'); + is $req->requirements_for_module('Plack'), '0.9970'; + + is_deeply $file->options_for_module('Plack'), { + git => 'git://github.com/plack/Plack.git', + ref => '0.9970', + }; +}; + +subtest 'drop version' => sub { + my $r = write_cpanfile(<<FILE); +requires 'Plack', # drop version + git => 'git://github.com/plack/Plack.git', ref => '0.9970'; +FILE + + my $file = Module::CPANfile->load; + is_deeply $file->prereq_specs, { + runtime => { + requires => { 'Plack' => 0 }, + }, + }; + + is_deeply $file->options_for_module('Plack'), { + git => 'git://github.com/plack/Plack.git', + ref => '0.9970', + }; +}; + +subtest 'no ref' => sub { + my $r = write_cpanfile(<<FILE); +requires 'Plack', '0.9970', git => 'git://github.com/plack/Plack.git'; +FILE + + my $file = Module::CPANfile->load; + is_deeply $file->prereq_specs, { + runtime => { + requires => { 'Plack' => '0.9970' }, + }, + }; + + is_deeply $file->options_for_module('Plack'), { + git => 'git://github.com/plack/Plack.git', + }; +}; + +subtest 'name and git' => sub { + my $r = write_cpanfile(<<FILE); +requires 'Plack', git => 'git://github.com/plack/Plack.git'; +FILE + + my $file = Module::CPANfile->load; + is_deeply $file->prereq_specs, { + runtime => { + requires => { 'Plack' => 0 }, + }, + }; + + is_deeply $file->options_for_module('Plack'), { + git => 'git://github.com/plack/Plack.git', + }; +}; + +done_testing; |