summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2014-08-29 04:48:18 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2014-08-29 04:48:18 +0000
commit6b1d736955543538c54f1d8033ce3bdcb175da91 (patch)
treef7f7e23929d493647c0e2f9b1a556d3101194538
downloadModule-CPANfile-tarball-6b1d736955543538c54f1d8033ce3bdcb175da91.tar.gz
Module-CPANfile-1.1000HEADModule-CPANfile-1.1000master
-rw-r--r--Changes125
-rw-r--r--LICENSE379
-rw-r--r--MANIFEST27
-rw-r--r--META.json78
-rw-r--r--META.yml44
-rw-r--r--Makefile.PL60
-rw-r--r--README103
-rw-r--r--cpanfile9
-rw-r--r--dist.ini4
-rw-r--r--lib/Module/CPANfile.pm323
-rw-r--r--lib/Module/CPANfile/Environment.pm173
-rw-r--r--lib/Module/CPANfile/Prereq.pm21
-rw-r--r--lib/Module/CPANfile/Prereqs.pm117
-rw-r--r--lib/Module/CPANfile/Requirement.pm25
-rw-r--r--lib/cpanfile-faq.pod128
-rw-r--r--lib/cpanfile.pod128
-rwxr-xr-xscript/cpanfile-dump131
-rwxr-xr-xscript/mymeta-cpanfile115
-rw-r--r--t/Utils.pm39
-rw-r--r--t/feature.t87
-rw-r--r--t/from_prereqs.t36
-rw-r--r--t/merge.t65
-rw-r--r--t/mirror.t38
-rw-r--r--t/parse.t74
-rw-r--r--t/release-pod-syntax.t14
-rw-r--r--t/requirement.t81
26 files changed, 2424 insertions, 0 deletions
diff --git a/Changes b/Changes
new file mode 100644
index 0000000..80d7a27
--- /dev/null
+++ b/Changes
@@ -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
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7c98903
--- /dev/null
+++ b/LICENSE
@@ -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);
+
+
+
diff --git a/README b/README
new file mode 100644
index 0000000..e484367
--- /dev/null
+++ b/README
@@ -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;