packages feed

hackage-cli (empty) → 0.0.3.6

raw patch · 36 files changed

+4298/−0 lines, 36 filesdep +Cabaldep +aesondep +basesetup-changed

Dependencies added: Cabal, aeson, base, bytestring, containers, deepseq, directory, filepath, hackage-cli, http-io-streams, io-streams, microlens, microlens-mtl, microlens-th, mtl, netrc, optparse-applicative, pretty, process-extras, semigroups, stringsearch, tagsoup, tar, tasty, tasty-golden, text, time, unordered-containers, zlib

Files

+ CHANGELOG.md view
@@ -0,0 +1,17 @@+# Pre-release versions (February 2022)++## 0.0.3.4++Builds with `Cabal-3.4` and `GHC 9.0`.++## 0.0.3.2++Builds with `Cabal-3.2` and `GHC 8.10`.++## 0.0.3.0++Builds with `Cabal-3.0` and `GHC 8.8`.++## 0.0.2.4++Builds with `Cabal-2.4` and `GHC 8.6`.
+ LICENSE view
@@ -0,0 +1,674 @@+              GNU GENERAL PUBLIC LICENSE+                Version 3, 29 June 2007++ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>+ Everyone is permitted to copy and distribute verbatim copies+ of this license document, but changing it is not allowed.++                     Preamble++  The GNU General Public License is a free, copyleft license for+software and other kinds of works.++  The licenses for most software and other practical works are designed+to take away your freedom to share and change the works.  By contrast,+the GNU General Public License is intended to guarantee your freedom to+share and change all versions of a program--to make sure it remains free+software for all its users.  We, the Free Software Foundation, use the+GNU General Public License for most of our software; it applies also to+any other work released this way by its authors.  You can apply it to+your programs, too.++  When we speak of free software, we are referring to freedom, not+price.  Our General Public Licenses are designed to make sure that you+have the freedom to distribute copies of free software (and charge for+them if you wish), 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 prevent others from denying you+these rights or asking you to surrender the rights.  Therefore, you have+certain responsibilities if you distribute copies of the software, or if+you modify it: responsibilities to respect the freedom of others.++  For example, if you distribute copies of such a program, whether+gratis or for a fee, you must pass on to the recipients the same+freedoms that you received.  You must make sure that they, too, receive+or can get the source code.  And you must show them these terms so they+know their rights.++  Developers that use the GNU GPL protect your rights with two steps:+(1) assert copyright on the software, and (2) offer you this License+giving you legal permission to copy, distribute and/or modify it.++  For the developers' and authors' protection, the GPL clearly explains+that there is no warranty for this free software.  For both users' and+authors' sake, the GPL requires that modified versions be marked as+changed, so that their problems will not be attributed erroneously to+authors of previous versions.++  Some devices are designed to deny users access to install or run+modified versions of the software inside them, although the manufacturer+can do so.  This is fundamentally incompatible with the aim of+protecting users' freedom to change the software.  The systematic+pattern of such abuse occurs in the area of products for individuals to+use, which is precisely where it is most unacceptable.  Therefore, we+have designed this version of the GPL to prohibit the practice for those+products.  If such problems arise substantially in other domains, we+stand ready to extend this provision to those domains in future versions+of the GPL, as needed to protect the freedom of users.++  Finally, every program is threatened constantly by software patents.+States should not allow patents to restrict development and use of+software on general-purpose computers, but in those that do, we wish to+avoid the special danger that patents applied to a free program could+make it effectively proprietary.  To prevent this, the GPL assures that+patents cannot be used to render the program non-free.++  The precise terms and conditions for copying, distribution and+modification follow.++                TERMS AND CONDITIONS++  0. Definitions.++  "This License" refers to version 3 of the GNU General Public License.++  "Copyright" also means copyright-like laws that apply to other kinds of+works, such as semiconductor masks.++  "The Program" refers to any copyrightable work licensed under this+License.  Each licensee is addressed as "you".  "Licensees" and+"recipients" may be individuals or organizations.++  To "modify" a work means to copy from or adapt all or part of the work+in a fashion requiring copyright permission, other than the making of an+exact copy.  The resulting work is called a "modified version" of the+earlier work or a work "based on" the earlier work.++  A "covered work" means either the unmodified Program or a work based+on the Program.++  To "propagate" a work means to do anything with it that, without+permission, would make you directly or secondarily liable for+infringement under applicable copyright law, except executing it on a+computer or modifying a private copy.  Propagation includes copying,+distribution (with or without modification), making available to the+public, and in some countries other activities as well.++  To "convey" a work means any kind of propagation that enables other+parties to make or receive copies.  Mere interaction with a user through+a computer network, with no transfer of a copy, is not conveying.++  An interactive user interface displays "Appropriate Legal Notices"+to the extent that it includes a convenient and prominently visible+feature that (1) displays an appropriate copyright notice, and (2)+tells the user that there is no warranty for the work (except to the+extent that warranties are provided), that licensees may convey the+work under this License, and how to view a copy of this License.  If+the interface presents a list of user commands or options, such as a+menu, a prominent item in the list meets this criterion.++  1. Source Code.++  The "source code" for a work means the preferred form of the work+for making modifications to it.  "Object code" means any non-source+form of a work.++  A "Standard Interface" means an interface that either is an official+standard defined by a recognized standards body, or, in the case of+interfaces specified for a particular programming language, one that+is widely used among developers working in that language.++  The "System Libraries" of an executable work include anything, other+than the work as a whole, that (a) is included in the normal form of+packaging a Major Component, but which is not part of that Major+Component, and (b) serves only to enable use of the work with that+Major Component, or to implement a Standard Interface for which an+implementation is available to the public in source code form.  A+"Major Component", in this context, means a major essential component+(kernel, window system, and so on) of the specific operating system+(if any) on which the executable work runs, or a compiler used to+produce the work, or an object code interpreter used to run it.++  The "Corresponding Source" for a work in object code form means all+the source code needed to generate, install, and (for an executable+work) run the object code and to modify the work, including scripts to+control those activities.  However, it does not include the work's+System Libraries, or general-purpose tools or generally available free+programs which are used unmodified in performing those activities but+which are not part of the work.  For example, Corresponding Source+includes interface definition files associated with source files for+the work, and the source code for shared libraries and dynamically+linked subprograms that the work is specifically designed to require,+such as by intimate data communication or control flow between those+subprograms and other parts of the work.++  The Corresponding Source need not include anything that users+can regenerate automatically from other parts of the Corresponding+Source.++  The Corresponding Source for a work in source code form is that+same work.++  2. Basic Permissions.++  All rights granted under this License are granted for the term of+copyright on the Program, and are irrevocable provided the stated+conditions are met.  This License explicitly affirms your unlimited+permission to run the unmodified Program.  The output from running a+covered work is covered by this License only if the output, given its+content, constitutes a covered work.  This License acknowledges your+rights of fair use or other equivalent, as provided by copyright law.++  You may make, run and propagate covered works that you do not+convey, without conditions so long as your license otherwise remains+in force.  You may convey covered works to others for the sole purpose+of having them make modifications exclusively for you, or provide you+with facilities for running those works, provided that you comply with+the terms of this License in conveying all material for which you do+not control copyright.  Those thus making or running the covered works+for you must do so exclusively on your behalf, under your direction+and control, on terms that prohibit them from making any copies of+your copyrighted material outside their relationship with you.++  Conveying under any other circumstances is permitted solely under+the conditions stated below.  Sublicensing is not allowed; section 10+makes it unnecessary.++  3. Protecting Users' Legal Rights From Anti-Circumvention Law.++  No covered work shall be deemed part of an effective technological+measure under any applicable law fulfilling obligations under article+11 of the WIPO copyright treaty adopted on 20 December 1996, or+similar laws prohibiting or restricting circumvention of such+measures.++  When you convey a covered work, you waive any legal power to forbid+circumvention of technological measures to the extent such circumvention+is effected by exercising rights under this License with respect to+the covered work, and you disclaim any intention to limit operation or+modification of the work as a means of enforcing, against the work's+users, your or third parties' legal rights to forbid circumvention of+technological measures.++  4. Conveying Verbatim Copies.++  You may convey 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;+keep intact all notices stating that this License and any+non-permissive terms added in accord with section 7 apply to the code;+keep intact all notices of the absence of any warranty; and give all+recipients a copy of this License along with the Program.++  You may charge any price or no price for each copy that you convey,+and you may offer support or warranty protection for a fee.++  5. Conveying Modified Source Versions.++  You may convey a work based on the Program, or the modifications to+produce it from the Program, in the form of source code under the+terms of section 4, provided that you also meet all of these conditions:++    a) The work must carry prominent notices stating that you modified+    it, and giving a relevant date.++    b) The work must carry prominent notices stating that it is+    released under this License and any conditions added under section+    7.  This requirement modifies the requirement in section 4 to+    "keep intact all notices".++    c) You must license the entire work, as a whole, under this+    License to anyone who comes into possession of a copy.  This+    License will therefore apply, along with any applicable section 7+    additional terms, to the whole of the work, and all its parts,+    regardless of how they are packaged.  This License gives no+    permission to license the work in any other way, but it does not+    invalidate such permission if you have separately received it.++    d) If the work has interactive user interfaces, each must display+    Appropriate Legal Notices; however, if the Program has interactive+    interfaces that do not display Appropriate Legal Notices, your+    work need not make them do so.++  A compilation of a covered work with other separate and independent+works, which are not by their nature extensions of the covered work,+and which are not combined with it such as to form a larger program,+in or on a volume of a storage or distribution medium, is called an+"aggregate" if the compilation and its resulting copyright are not+used to limit the access or legal rights of the compilation's users+beyond what the individual works permit.  Inclusion of a covered work+in an aggregate does not cause this License to apply to the other+parts of the aggregate.++  6. Conveying Non-Source Forms.++  You may convey a covered work in object code form under the terms+of sections 4 and 5, provided that you also convey the+machine-readable Corresponding Source under the terms of this License,+in one of these ways:++    a) Convey the object code in, or embodied in, a physical product+    (including a physical distribution medium), accompanied by the+    Corresponding Source fixed on a durable physical medium+    customarily used for software interchange.++    b) Convey the object code in, or embodied in, a physical product+    (including a physical distribution medium), accompanied by a+    written offer, valid for at least three years and valid for as+    long as you offer spare parts or customer support for that product+    model, to give anyone who possesses the object code either (1) a+    copy of the Corresponding Source for all the software in the+    product that is covered by this License, on a durable physical+    medium customarily used for software interchange, for a price no+    more than your reasonable cost of physically performing this+    conveying of source, or (2) access to copy the+    Corresponding Source from a network server at no charge.++    c) Convey individual copies of the object code with a copy of the+    written offer to provide the Corresponding Source.  This+    alternative is allowed only occasionally and noncommercially, and+    only if you received the object code with such an offer, in accord+    with subsection 6b.++    d) Convey the object code by offering access from a designated+    place (gratis or for a charge), and offer equivalent access to the+    Corresponding Source in the same way through the same place at no+    further charge.  You need not require recipients to copy the+    Corresponding Source along with the object code.  If the place to+    copy the object code is a network server, the Corresponding Source+    may be on a different server (operated by you or a third party)+    that supports equivalent copying facilities, provided you maintain+    clear directions next to the object code saying where to find the+    Corresponding Source.  Regardless of what server hosts the+    Corresponding Source, you remain obligated to ensure that it is+    available for as long as needed to satisfy these requirements.++    e) Convey the object code using peer-to-peer transmission, provided+    you inform other peers where the object code and Corresponding+    Source of the work are being offered to the general public at no+    charge under subsection 6d.++  A separable portion of the object code, whose source code is excluded+from the Corresponding Source as a System Library, need not be+included in conveying the object code work.++  A "User Product" is either (1) a "consumer product", which means any+tangible personal property which is normally used for personal, family,+or household purposes, or (2) anything designed or sold for incorporation+into a dwelling.  In determining whether a product is a consumer product,+doubtful cases shall be resolved in favor of coverage.  For a particular+product received by a particular user, "normally used" refers to a+typical or common use of that class of product, regardless of the status+of the particular user or of the way in which the particular user+actually uses, or expects or is expected to use, the product.  A product+is a consumer product regardless of whether the product has substantial+commercial, industrial or non-consumer uses, unless such uses represent+the only significant mode of use of the product.++  "Installation Information" for a User Product means any methods,+procedures, authorization keys, or other information required to install+and execute modified versions of a covered work in that User Product from+a modified version of its Corresponding Source.  The information must+suffice to ensure that the continued functioning of the modified object+code is in no case prevented or interfered with solely because+modification has been made.++  If you convey an object code work under this section in, or with, or+specifically for use in, a User Product, and the conveying occurs as+part of a transaction in which the right of possession and use of the+User Product is transferred to the recipient in perpetuity or for a+fixed term (regardless of how the transaction is characterized), the+Corresponding Source conveyed under this section must be accompanied+by the Installation Information.  But this requirement does not apply+if neither you nor any third party retains the ability to install+modified object code on the User Product (for example, the work has+been installed in ROM).++  The requirement to provide Installation Information does not include a+requirement to continue to provide support service, warranty, or updates+for a work that has been modified or installed by the recipient, or for+the User Product in which it has been modified or installed.  Access to a+network may be denied when the modification itself materially and+adversely affects the operation of the network or violates the rules and+protocols for communication across the network.++  Corresponding Source conveyed, and Installation Information provided,+in accord with this section must be in a format that is publicly+documented (and with an implementation available to the public in+source code form), and must require no special password or key for+unpacking, reading or copying.++  7. Additional Terms.++  "Additional permissions" are terms that supplement the terms of this+License by making exceptions from one or more of its conditions.+Additional permissions that are applicable to the entire Program shall+be treated as though they were included in this License, to the extent+that they are valid under applicable law.  If additional permissions+apply only to part of the Program, that part may be used separately+under those permissions, but the entire Program remains governed by+this License without regard to the additional permissions.++  When you convey a copy of a covered work, you may at your option+remove any additional permissions from that copy, or from any part of+it.  (Additional permissions may be written to require their own+removal in certain cases when you modify the work.)  You may place+additional permissions on material, added by you to a covered work,+for which you have or can give appropriate copyright permission.++  Notwithstanding any other provision of this License, for material you+add to a covered work, you may (if authorized by the copyright holders of+that material) supplement the terms of this License with terms:++    a) Disclaiming warranty or limiting liability differently from the+    terms of sections 15 and 16 of this License; or++    b) Requiring preservation of specified reasonable legal notices or+    author attributions in that material or in the Appropriate Legal+    Notices displayed by works containing it; or++    c) Prohibiting misrepresentation of the origin of that material, or+    requiring that modified versions of such material be marked in+    reasonable ways as different from the original version; or++    d) Limiting the use for publicity purposes of names of licensors or+    authors of the material; or++    e) Declining to grant rights under trademark law for use of some+    trade names, trademarks, or service marks; or++    f) Requiring indemnification of licensors and authors of that+    material by anyone who conveys the material (or modified versions of+    it) with contractual assumptions of liability to the recipient, for+    any liability that these contractual assumptions directly impose on+    those licensors and authors.++  All other non-permissive additional terms are considered "further+restrictions" within the meaning of section 10.  If the Program as you+received it, or any part of it, contains a notice stating that it is+governed by this License along with a term that is a further+restriction, you may remove that term.  If a license document contains+a further restriction but permits relicensing or conveying under this+License, you may add to a covered work material governed by the terms+of that license document, provided that the further restriction does+not survive such relicensing or conveying.++  If you add terms to a covered work in accord with this section, you+must place, in the relevant source files, a statement of the+additional terms that apply to those files, or a notice indicating+where to find the applicable terms.++  Additional terms, permissive or non-permissive, may be stated in the+form of a separately written license, or stated as exceptions;+the above requirements apply either way.++  8. Termination.++  You may not propagate or modify a covered work except as expressly+provided under this License.  Any attempt otherwise to propagate or+modify it is void, and will automatically terminate your rights under+this License (including any patent licenses granted under the third+paragraph of section 11).++  However, if you cease all violation of this License, then your+license from a particular copyright holder is reinstated (a)+provisionally, unless and until the copyright holder explicitly and+finally terminates your license, and (b) permanently, if the copyright+holder fails to notify you of the violation by some reasonable means+prior to 60 days after the cessation.++  Moreover, your license from a particular copyright holder is+reinstated permanently if the copyright holder notifies you of the+violation by some reasonable means, this is the first time you have+received notice of violation of this License (for any work) from that+copyright holder, and you cure the violation prior to 30 days after+your receipt of the notice.++  Termination of your rights under this section does not terminate the+licenses of parties who have received copies or rights from you under+this License.  If your rights have been terminated and not permanently+reinstated, you do not qualify to receive new licenses for the same+material under section 10.++  9. Acceptance Not Required for Having Copies.++  You are not required to accept this License in order to receive or+run a copy of the Program.  Ancillary propagation of a covered work+occurring solely as a consequence of using peer-to-peer transmission+to receive a copy likewise does not require acceptance.  However,+nothing other than this License grants you permission to propagate or+modify any covered work.  These actions infringe copyright if you do+not accept this License.  Therefore, by modifying or propagating a+covered work, you indicate your acceptance of this License to do so.++  10. Automatic Licensing of Downstream Recipients.++  Each time you convey a covered work, the recipient automatically+receives a license from the original licensors, to run, modify and+propagate that work, subject to this License.  You are not responsible+for enforcing compliance by third parties with this License.++  An "entity transaction" is a transaction transferring control of an+organization, or substantially all assets of one, or subdividing an+organization, or merging organizations.  If propagation of a covered+work results from an entity transaction, each party to that+transaction who receives a copy of the work also receives whatever+licenses to the work the party's predecessor in interest had or could+give under the previous paragraph, plus a right to possession of the+Corresponding Source of the work from the predecessor in interest, if+the predecessor has it or can get it with reasonable efforts.++  You may not impose any further restrictions on the exercise of the+rights granted or affirmed under this License.  For example, you may+not impose a license fee, royalty, or other charge for exercise of+rights granted under this License, and you may not initiate litigation+(including a cross-claim or counterclaim in a lawsuit) alleging that+any patent claim is infringed by making, using, selling, offering for+sale, or importing the Program or any portion of it.++  11. Patents.++  A "contributor" is a copyright holder who authorizes use under this+License of the Program or a work on which the Program is based.  The+work thus licensed is called the contributor's "contributor version".++  A contributor's "essential patent claims" are all patent claims+owned or controlled by the contributor, whether already acquired or+hereafter acquired, that would be infringed by some manner, permitted+by this License, of making, using, or selling its contributor version,+but do not include claims that would be infringed only as a+consequence of further modification of the contributor version.  For+purposes of this definition, "control" includes the right to grant+patent sublicenses in a manner consistent with the requirements of+this License.++  Each contributor grants you a non-exclusive, worldwide, royalty-free+patent license under the contributor's essential patent claims, to+make, use, sell, offer for sale, import and otherwise run, modify and+propagate the contents of its contributor version.++  In the following three paragraphs, a "patent license" is any express+agreement or commitment, however denominated, not to enforce a patent+(such as an express permission to practice a patent or covenant not to+sue for patent infringement).  To "grant" such a patent license to a+party means to make such an agreement or commitment not to enforce a+patent against the party.++  If you convey a covered work, knowingly relying on a patent license,+and the Corresponding Source of the work is not available for anyone+to copy, free of charge and under the terms of this License, through a+publicly available network server or other readily accessible means,+then you must either (1) cause the Corresponding Source to be so+available, or (2) arrange to deprive yourself of the benefit of the+patent license for this particular work, or (3) arrange, in a manner+consistent with the requirements of this License, to extend the patent+license to downstream recipients.  "Knowingly relying" means you have+actual knowledge that, but for the patent license, your conveying the+covered work in a country, or your recipient's use of the covered work+in a country, would infringe one or more identifiable patents in that+country that you have reason to believe are valid.++  If, pursuant to or in connection with a single transaction or+arrangement, you convey, or propagate by procuring conveyance of, a+covered work, and grant a patent license to some of the parties+receiving the covered work authorizing them to use, propagate, modify+or convey a specific copy of the covered work, then the patent license+you grant is automatically extended to all recipients of the covered+work and works based on it.++  A patent license is "discriminatory" if it does not include within+the scope of its coverage, prohibits the exercise of, or is+conditioned on the non-exercise of one or more of the rights that are+specifically granted under this License.  You may not convey a covered+work if you are a party to an arrangement with a third party that is+in the business of distributing software, under which you make payment+to the third party based on the extent of your activity of conveying+the work, and under which the third party grants, to any of the+parties who would receive the covered work from you, a discriminatory+patent license (a) in connection with copies of the covered work+conveyed by you (or copies made from those copies), or (b) primarily+for and in connection with specific products or compilations that+contain the covered work, unless you entered into that arrangement,+or that patent license was granted, prior to 28 March 2007.++  Nothing in this License shall be construed as excluding or limiting+any implied license or other defenses to infringement that may+otherwise be available to you under applicable patent law.++  12. No Surrender of Others' Freedom.++  If conditions are imposed on you (whether by court order, agreement or+otherwise) that contradict the conditions of this License, they do not+excuse you from the conditions of this License.  If you cannot convey a+covered work so as to satisfy simultaneously your obligations under this+License and any other pertinent obligations, then as a consequence you may+not convey it at all.  For example, if you agree to terms that obligate you+to collect a royalty for further conveying from those to whom you convey+the Program, the only way you could satisfy both those terms and this+License would be to refrain entirely from conveying the Program.++  13. Use with the GNU Affero General Public License.++  Notwithstanding any other provision of this License, you have+permission to link or combine any covered work with a work licensed+under version 3 of the GNU Affero General Public License into a single+combined work, and to convey the resulting work.  The terms of this+License will continue to apply to the part which is the covered work,+but the special requirements of the GNU Affero General Public License,+section 13, concerning interaction through a network will apply to the+combination as such.++  14. Revised Versions of this License.++  The Free Software Foundation may publish revised and/or new versions of+the GNU 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 that a certain numbered version of the GNU General+Public License "or any later version" applies to it, you have the+option of following the terms and conditions either of that numbered+version or of any later version published by the Free Software+Foundation.  If the Program does not specify a version number of the+GNU General Public License, you may choose any version ever published+by the Free Software Foundation.++  If the Program specifies that a proxy can decide which future+versions of the GNU General Public License can be used, that proxy's+public statement of acceptance of a version permanently authorizes you+to choose that version for the Program.++  Later license versions may give you additional or different+permissions.  However, no additional obligations are imposed on any+author or copyright holder as a result of your choosing to follow a+later version.++  15. Disclaimer of Warranty.++  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.++  16. Limitation of Liability.++  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS+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.++  17. Interpretation of Sections 15 and 16.++  If the disclaimer of warranty and limitation of liability provided+above cannot be given local legal effect according to their terms,+reviewing courts shall apply local law that most closely approximates+an absolute waiver of all civil liability in connection with the+Program, unless a warranty or assumption of liability accompanies a+copy of the Program in return for a fee.++              END OF TERMS AND CONDITIONS++     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 the public, 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+state 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) <year>  <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 3 of the License, 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, see <http://www.gnu.org/licenses/>.++Also add information on how to contact you by electronic and paper mail.++  If the program does terminal interaction, make it output a short+notice like this when it starts in an interactive mode:++    <program>  Copyright (C) <year>  <name of author>+    This program 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, your program's commands+might be different; for a GUI interface, you would use an "about box".++  You should also get your employer (if you work as a programmer) or school,+if any, to sign a "copyright disclaimer" for the program, if necessary.+For more information on this, and how to apply and follow the GNU GPL, see+<http://www.gnu.org/licenses/>.++  The GNU General Public License does not permit incorporating your program+into proprietary programs.  If your program is a subroutine library, you+may consider it more useful to permit linking proprietary applications with+the library.  If this is what you want to do, use the GNU Lesser General+Public License instead of this License.  But first, please read+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+ README.md view
@@ -0,0 +1,117 @@+# `hackage-cli`++A command-line tool to manage package metadata+[revisions](https://github.com/haskell-infra/hackage-trustees/blob/master/revisions-information.md)+on Hackage.++Caveat: this is a developer tool and work-in-progress.+Check known bugs at the [issue tracker](https://github.com/hackage-trustees/hackage-cli/issues).++## Command-line reference++```+hackage-cli - CLI tool for Hackage++Usage: hackage-cli [--version] [--verbose] [--hostname HOSTNAME] COMMAND++Available options:+  -h,--help                Show this help text+  --version                output version information and exit+  --verbose                enable verbose output+  --hostname HOSTNAME      Hackage hostname (default: "hackage.haskell.org")++Available commands:+  pull-cabal               download .cabal files for a package+  push-cabal               upload revised .cabal files+  sync-cabal               upadate/sync local .cabal file with latest revision+                           on Hackage+  push-candidate           upload package candidate(s)+  list-versions            list versions for a package+  check-revision           validate revision+  index-sha256sum          generate sha256sum-format file+  add-bound                add bound to the library section of a package. .cabal+                           file is edited in place++Each command has a sub-`--help` text. Hackage credentials are expected to be+stored in an `${HOME}/.netrc`-entry for the respective Hackage hostname. E.g.+"machine hackage.haskell.org login MyUserName password TrustNo1". All+interactions with Hackage occur TLS-encrypted via the HTTPS protocol.+```++(Section created 2015-04-26, last updated 2018-03-21.)++## How to: bulk revision++Suppose that all versions starting with `1.2.3` of the package `pkg-x`+on hackage need the additional bound `< 4.5.6` on their dependency+`pkg-y`, typically to prevent compilation attempts with `pkg-y-4.5.6`+and up that will fail.++With `hackage-cli` this can be done in a typical workflow that consists of++  1. downloading the `.cabal` files,+  2. adding bounds (with `hackage-cli`), or making other revisions (manually),+  3. re-uploading the modified `.cabal` files.++We walk through a typical workflow:++1. Download the relevant `pkg-x.cabal` files to a new temporary directory:+   ```+   $ cd $(mktemp)+   $ hackage-cli pull-cabal pkg-x '>= 1.2.3'+   ```++2. Adding bound `pkg-y < 4.5.6` to all of them:+   ```+   $ hackage-cli add-bound pkg-y '< 4.5.6' *.cabal+   ```+   This will write a new line+   ```+     build-depends: pkg-y < 4.5.6+   ```+   to the `library` section of each of the `.cabal` files.+   There must be exactly one `library` section, otherwise `hackage-cli` will crash+   or produce a garbage result.++   If this bound does not further constrain the existing version range+   for `pkg-y`, it will not be added unless `--force` is used.+   (E.g., one of the cabal files could already have a bound `pkg-y <+   3.0.0` or so---this file will remain unchanged.)++   You can of course add more bounds using `add-bound` or make further+   manual changes to `.cabal` files.++3. Review the changes via a trial upload:+   ```+   $ hackage-cli push-cabal --incr-rev *.cabal+   ```+   (The flag `--incr-rev` will increment the `x-revision` field by `1` during the upload.+   Existing revisions cannot be overwritten.)++   During (trial) upload you will see for each `.cabal` file a summary of what will be changed.+   Hackage might reject your revision, if it does not match the criteria+   of what it thinks is+   [legal](https://github.com/haskell-infra/hackage-trustees/blob/master/revisions-information.md).+   Note that the legality check of the Hackage server is neither+   complete nor sound, so some legal revisions might be rejected, and some illegal ones accepted.+   You bear the responsibility for correct revisions yourself.++4. Upload!+   ```+   $ hackage-cli push-cabal --incr-rev --publish *.cabal+   ```+   Adding the flag `--publish` will actually commit the revisions to Hackage.++(Section created 2022-02-21.)++## License++- Licensed under GPL-3.++- (C) 2015 Herbert Valerio Riedel.+- (C) 2016-2019 Herbert Valerio Riedel and Oleg Grenrus.+- (C) 2021-2022 Andreas Abel.++- Further contributors: Simon Jakobi, Kevin Buhr.++(Section created 2022-02-21.)
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ fixtures/SVGFonts.0.1.diff view
@@ -0,0 +1,2 @@+ERROR+Cannot add or remove 'if' conditionals
+ fixtures/SVGFonts.0.2.diff view
@@ -0,0 +1,2 @@+ERROR+Cannot add or remove 'if' conditionals
+ fixtures/SVGFonts.0.cabal view
@@ -0,0 +1,88 @@+Name:             SVGFonts+Version:          1.6.0.3+Synopsis:         Fonts from the SVG-Font format+Description:      Native font support for the diagrams framework (<http://projects.haskell.org/diagrams/>). Note that this package can be used with any diagrams backend, not just the SVG backend.  The SVG-font format is easy to parse+                  and was therefore chosen for a font library completely written in Haskell.+                  .+                  You can convert your own font to SVG with <http://fontforge.github.io/>,+                  or use the included LinLibertine and Bitstream fonts.+                  .+                  Features:+                  .+                  * Complete implementation of the features that Fontforge produces (though not the complete SVG format)+                  .+                  * Kerning (/i.e./ the two characters in \"VA\" are closer than the characters in \"VV\")+                  .+                  * Unicode+                  .+                  * Ligatures+                  .+                  * An example that shows how to do text boxes with syntax highlighting using highlighting-kate:+                    <http://hackage.haskell.org/package/highlighting-kate>+                  .+                  XML speed issues can be solved by trimming the svg file to only those characters that are used (or maybe binary xml one day).+                  .+                  Version 1.0 of this library supports texturing, though this only makes sense in a diagrams backend that does rasterization in Haskell, such as diagrams-rasterific.+                  .+                  Example:+                  .+                  >  # LANGUAGE NoMonomorphismRestriction #+                  >+                  > import Diagrams.Prelude+                  > import Diagrams.Backend.Rasterific.CmdLine+                  > import Graphics.SVGFonts+                  >+                  > main = defaultMain (text' "Hello World")+                  >+                  > text'   t = stroke (textSVG t 1) # fc purple # fillRule EvenOdd+                  > text''  t = stroke (textSVG' (TextOpts lin INSIDE_H KERN False 1 1) t) # fillRule EvenOdd+                  > text''' t =        (textSVG_ (TextOpts lin INSIDE_H KERN True  1 1) t) # fillRule EvenOdd+                  .+category:         Graphics+License:          BSD3+License-file:     LICENSE+Author:           Tillmann Vogt+Maintainer:       diagrams-discuss@googlegroups.com+build-type:       Simple+Cabal-Version:    >=1.18++data-files:       fonts/*.svg+extra-source-files: CHANGES.md, README.md, diagrams/*.svg+extra-doc-files:    diagrams/*.svg+Tested-with:      GHC == 7.6.3, GHC == 7.8.4, GHC == 7.10.2, GHC == 8.0.1, GHC == 8.2.1+source-repository head+  type: git+  location: https://github.com/diagrams/SVGFonts.git++Library+    hs-source-dirs: src+    ghc-options: -W -Wall -fno-warn-unused-do-bind+    other-modules: Paths_SVGFonts+    build-depends:+        attoparsec,+        base == 4.*,+        bytestring >= 0.10 && < 1.0,+        cereal,+        cereal-vector,+        containers >= 0.4 && < 0.6,+        data-default-class < 0.2,+        diagrams-core >= 1.3 && < 1.5,+        diagrams-lib >= 1.3 && < 1.5,+        directory >= 1.1,+        blaze-svg >= 0.3.3,+        blaze-markup >= 0.5,+        parsec,+        split,+        text,+        tuple,+        vector,+        xml+    exposed-modules:+        Graphics.SVGFonts+        Graphics.SVGFonts.Text+        Graphics.SVGFonts.ReadFont+        Graphics.SVGFonts.Fonts+        Graphics.SVGFonts.CharReference+        Graphics.SVGFonts.ReadPath+        Graphics.SVGFonts.WriteFont+    default-language: Haskell2010
+ fixtures/SVGFonts.1.0.diff view
@@ -0,0 +1,2 @@+ERROR+Cannot add or remove 'if' conditionals
+ fixtures/SVGFonts.1.2.diff view
@@ -0,0 +1,5 @@+OK+Normal+the library component's library dependency on 'diagrams-lib'+- <1.4++ >=0
+ fixtures/SVGFonts.1.cabal view
@@ -0,0 +1,92 @@+Name:             SVGFonts+Version:          1.6.0.3+x-revision: 1+Synopsis:         Fonts from the SVG-Font format+Description:      Native font support for the diagrams framework (<http://projects.haskell.org/diagrams/>). Note that this package can be used with any diagrams backend, not just the SVG backend.  The SVG-font format is easy to parse+                  and was therefore chosen for a font library completely written in Haskell.+                  .+                  You can convert your own font to SVG with <http://fontforge.github.io/>,+                  or use the included LinLibertine and Bitstream fonts.+                  .+                  Features:+                  .+                  * Complete implementation of the features that Fontforge produces (though not the complete SVG format)+                  .+                  * Kerning (/i.e./ the two characters in \"VA\" are closer than the characters in \"VV\")+                  .+                  * Unicode+                  .+                  * Ligatures+                  .+                  * An example that shows how to do text boxes with syntax highlighting using highlighting-kate:+                    <http://hackage.haskell.org/package/highlighting-kate>+                  .+                  XML speed issues can be solved by trimming the svg file to only those characters that are used (or maybe binary xml one day).+                  .+                  Version 1.0 of this library supports texturing, though this only makes sense in a diagrams backend that does rasterization in Haskell, such as diagrams-rasterific.+                  .+                  Example:+                  .+                  >  # LANGUAGE NoMonomorphismRestriction #+                  >+                  > import Diagrams.Prelude+                  > import Diagrams.Backend.Rasterific.CmdLine+                  > import Graphics.SVGFonts+                  >+                  > main = defaultMain (text' "Hello World")+                  >+                  > text'   t = stroke (textSVG t 1) # fc purple # fillRule EvenOdd+                  > text''  t = stroke (textSVG' (TextOpts lin INSIDE_H KERN False 1 1) t) # fillRule EvenOdd+                  > text''' t =        (textSVG_ (TextOpts lin INSIDE_H KERN True  1 1) t) # fillRule EvenOdd+                  .+category:         Graphics+License:          BSD3+License-file:     LICENSE+Author:           Tillmann Vogt+Maintainer:       diagrams-discuss@googlegroups.com+build-type:       Simple+Cabal-Version:    >=1.18++data-files:       fonts/*.svg+extra-source-files: CHANGES.md, README.md, diagrams/*.svg+extra-doc-files:    diagrams/*.svg+Tested-with:      GHC == 7.6.3, GHC == 7.8.4, GHC == 7.10.2, GHC == 8.0.1, GHC == 8.2.1+source-repository head+  type: git+  location: https://github.com/diagrams/SVGFonts.git++Library+    hs-source-dirs: src+    ghc-options: -W -Wall -fno-warn-unused-do-bind+    other-modules: Paths_SVGFonts+    build-depends:+        attoparsec,+        base == 4.*,+        bytestring >= 0.10 && < 1.0,+        cereal,+        cereal-vector,+        containers >= 0.4 && < 0.6,+        data-default-class < 0.2,+        diagrams-core >= 1.3 && < 1.5,+        diagrams-lib >= 1.3 && < 1.5,+        directory >= 1.1,+        blaze-svg >= 0.3.3,+        blaze-markup >= 0.5,+        parsec,+        split,+        text,+        tuple,+        vector,+        xml+    exposed-modules:+        Graphics.SVGFonts+        Graphics.SVGFonts.Text+        Graphics.SVGFonts.ReadFont+        Graphics.SVGFonts.Fonts+        Graphics.SVGFonts.CharReference+        Graphics.SVGFonts.ReadPath+        Graphics.SVGFonts.WriteFont+    default-language: Haskell2010++    if impl(ghc == 7.8.*)+      build-depends: diagrams-lib < 1.4
+ fixtures/SVGFonts.2.0.diff view
@@ -0,0 +1,2 @@+ERROR+Cannot add or remove 'if' conditionals
+ fixtures/SVGFonts.2.1.diff view
@@ -0,0 +1,5 @@+OK+Normal+the library component's library dependency on 'diagrams-lib'+- >=0++ <1.4
+ fixtures/SVGFonts.2.cabal view
@@ -0,0 +1,92 @@+Name:             SVGFonts+Version:          1.6.0.3+x-revision: 2+Synopsis:         Fonts from the SVG-Font format+Description:      Native font support for the diagrams framework (<http://projects.haskell.org/diagrams/>). Note that this package can be used with any diagrams backend, not just the SVG backend.  The SVG-font format is easy to parse+                  and was therefore chosen for a font library completely written in Haskell.+                  .+                  You can convert your own font to SVG with <http://fontforge.github.io/>,+                  or use the included LinLibertine and Bitstream fonts.+                  .+                  Features:+                  .+                  * Complete implementation of the features that Fontforge produces (though not the complete SVG format)+                  .+                  * Kerning (/i.e./ the two characters in \"VA\" are closer than the characters in \"VV\")+                  .+                  * Unicode+                  .+                  * Ligatures+                  .+                  * An example that shows how to do text boxes with syntax highlighting using highlighting-kate:+                    <http://hackage.haskell.org/package/highlighting-kate>+                  .+                  XML speed issues can be solved by trimming the svg file to only those characters that are used (or maybe binary xml one day).+                  .+                  Version 1.0 of this library supports texturing, though this only makes sense in a diagrams backend that does rasterization in Haskell, such as diagrams-rasterific.+                  .+                  Example:+                  .+                  >  # LANGUAGE NoMonomorphismRestriction #+                  >+                  > import Diagrams.Prelude+                  > import Diagrams.Backend.Rasterific.CmdLine+                  > import Graphics.SVGFonts+                  >+                  > main = defaultMain (text' "Hello World")+                  >+                  > text'   t = stroke (textSVG t 1) # fc purple # fillRule EvenOdd+                  > text''  t = stroke (textSVG' (TextOpts lin INSIDE_H KERN False 1 1) t) # fillRule EvenOdd+                  > text''' t =        (textSVG_ (TextOpts lin INSIDE_H KERN True  1 1) t) # fillRule EvenOdd+                  .+category:         Graphics+License:          BSD3+License-file:     LICENSE+Author:           Tillmann Vogt+Maintainer:       diagrams-discuss@googlegroups.com+build-type:       Simple+Cabal-Version:    >=1.18++data-files:       fonts/*.svg+extra-source-files: CHANGES.md, README.md, diagrams/*.svg+extra-doc-files:    diagrams/*.svg+Tested-with:      GHC == 7.6.3, GHC == 7.8.4, GHC == 7.10.2, GHC == 8.0.1, GHC == 8.2.1+source-repository head+  type: git+  location: https://github.com/diagrams/SVGFonts.git++Library+    hs-source-dirs: src+    ghc-options: -W -Wall -fno-warn-unused-do-bind+    other-modules: Paths_SVGFonts+    build-depends:+        attoparsec,+        base == 4.*,+        bytestring >= 0.10 && < 1.0,+        cereal,+        cereal-vector,+        containers >= 0.4 && < 0.6,+        data-default-class < 0.2,+        diagrams-core >= 1.3 && < 1.5,+        diagrams-lib >= 1.3 && < 1.5,+        directory >= 1.1,+        blaze-svg >= 0.3.3,+        blaze-markup >= 0.5,+        parsec,+        split,+        text,+        tuple,+        vector,+        xml+    exposed-modules:+        Graphics.SVGFonts+        Graphics.SVGFonts.Text+        Graphics.SVGFonts.ReadFont+        Graphics.SVGFonts.Fonts+        Graphics.SVGFonts.CharReference+        Graphics.SVGFonts.ReadPath+        Graphics.SVGFonts.WriteFont+    default-language: Haskell2010++    if impl(ghc == 7.8.*)+      build-depends: diagrams-lib -any
+ fixtures/deepseq.0.1.diff view
@@ -0,0 +1,2 @@+ERROR+Cannot add new library dependency on 'text' in library component
+ fixtures/deepseq.0.cabal view
@@ -0,0 +1,98 @@+cabal-version:  1.12+name:           deepseq+version:        1.4.4.0+-- NOTE: Don't forget to update ./changelog.md++license:        BSD3+license-file:   LICENSE+maintainer:     libraries@haskell.org+bug-reports:    https://github.com/haskell/deepseq/issues+synopsis:       Deep evaluation of data structures+category:       Control+description:+    This package provides methods for fully evaluating data structures+    (\"deep evaluation\"). Deep evaluation is often used for adding+    strictness to a program, e.g. in order to force pending exceptions,+    remove space leaks, or force lazy I/O to happen. It is also useful+    in parallel programs, to ensure pending work does not migrate to the+    wrong thread.+    .+    The primary use of this package is via the 'deepseq' function, a+    \"deep\" version of 'seq'. It is implemented on top of an 'NFData'+    typeclass (\"Normal Form Data\", data structures with no unevaluated+    components) which defines strategies for fully evaluating different+    data types. See module documentation in "Control.DeepSeq" for more+    details.++build-type:     Simple+tested-with:    GHC==8.6.1,+                GHC==8.4.3, GHC==8.4.2, GHC==8.4.1,+                GHC==8.2.2, GHC==8.2.1,+                GHC==8.0.2, GHC==8.0.1,+                GHC==7.10.3, GHC==7.10.2, GHC==7.10.1,+                GHC==7.8.4, GHC==7.8.3, GHC==7.8.2, GHC==7.8.1,+                GHC==7.6.3, GHC==7.6.2, GHC==7.6.1,+                GHC==7.4.2, GHC==7.4.1++extra-source-files: changelog.md++source-repository head+  type:     git+  location: https://github.com/haskell/deepseq.git++library+  default-language: Haskell2010+  other-extensions:+    BangPatterns+    CPP+    DefaultSignatures+    FlexibleContexts+    FlexibleInstances+    GADTs+    MultiParamTypeClasses+    Safe+    TypeOperators++  -- GHC.Generics lived in `ghc-prim` for GHC 7.2 & GHC 7.4+  if impl(ghc == 7.4.*)+    build-depends: ghc-prim == 0.2.*++  if impl(ghc>=7.6)+    other-extensions: PolyKinds++  if impl(ghc>=7.8)+    other-extensions: EmptyCase++  build-depends: base       >= 4.5 && < 4.13,+                 array      >= 0.4 && < 0.6+  ghc-options: -Wall++  exposed-modules: Control.DeepSeq+  other-modules:   Control.DeepSeq.BackDoor++test-suite deepseq-generics-tests+    default-language:    Haskell2010+    type:                exitcode-stdio-1.0+    hs-source-dirs:      . tests+    main-is:             Main.hs+    other-extensions:+        CPP+        BangPatterns+        DefaultSignatures+        DeriveDataTypeable+        DeriveGeneric+        FlexibleContexts+        Safe+        TupleSections+        TypeOperators++    ghc-options:         -Wall++    build-depends:+        array,+        base,+        ghc-prim,+        -- end of packages with inherited version constraints+        test-framework == 0.8.*,+        test-framework-hunit == 0.3.*,+        HUnit >= 1.2 && < 1.7
+ fixtures/deepseq.1.0.diff view
@@ -0,0 +1,2 @@+ERROR+Cannot remove existing library dependency on 'text' in library  component
+ fixtures/deepseq.1.cabal view
@@ -0,0 +1,100 @@+cabal-version:  1.12+name:           deepseq+version:        1.4.4.0+x-revision:     1+-- NOTE: Don't forget to update ./changelog.md++license:        BSD3+license-file:   LICENSE+maintainer:     libraries@haskell.org+bug-reports:    https://github.com/haskell/deepseq/issues+synopsis:       Deep evaluation of data structures+category:       Control+description:+    This package provides methods for fully evaluating data structures+    (\"deep evaluation\"). Deep evaluation is often used for adding+    strictness to a program, e.g. in order to force pending exceptions,+    remove space leaks, or force lazy I/O to happen. It is also useful+    in parallel programs, to ensure pending work does not migrate to the+    wrong thread.+    .+    The primary use of this package is via the 'deepseq' function, a+    \"deep\" version of 'seq'. It is implemented on top of an 'NFData'+    typeclass (\"Normal Form Data\", data structures with no unevaluated+    components) which defines strategies for fully evaluating different+    data types. See module documentation in "Control.DeepSeq" for more+    details.++build-type:     Simple+tested-with:    GHC==8.6.1,+                GHC==8.4.3, GHC==8.4.2, GHC==8.4.1,+                GHC==8.2.2, GHC==8.2.1,+                GHC==8.0.2, GHC==8.0.1,+                GHC==7.10.3, GHC==7.10.2, GHC==7.10.1,+                GHC==7.8.4, GHC==7.8.3, GHC==7.8.2, GHC==7.8.1,+                GHC==7.6.3, GHC==7.6.2, GHC==7.6.1,+                GHC==7.4.2, GHC==7.4.1++extra-source-files: changelog.md++source-repository head+  type:     git+  location: https://github.com/haskell/deepseq.git++library+  default-language: Haskell2010+  other-extensions:+    BangPatterns+    CPP+    DefaultSignatures+    FlexibleContexts+    FlexibleInstances+    GADTs+    MultiParamTypeClasses+    Safe+    TypeOperators++  -- GHC.Generics lived in `ghc-prim` for GHC 7.2 & GHC 7.4+  if impl(ghc == 7.4.*)+    build-depends: ghc-prim == 0.2.*++  if impl(ghc>=7.6)+    other-extensions: PolyKinds++  if impl(ghc>=7.8)+    other-extensions: EmptyCase++  build-depends: base       >= 4.5 && < 4.13,+                 array      >= 0.4 && < 0.6,+                 text       >= 1.2.3.0 && <1.4+  ghc-options: -Wall++  exposed-modules: Control.DeepSeq+  other-modules:   Control.DeepSeq.BackDoor++test-suite deepseq-generics-tests+    default-language:    Haskell2010+    type:                exitcode-stdio-1.0+    hs-source-dirs:      . tests+    main-is:             Main.hs+    other-extensions:+        CPP+        BangPatterns+        DefaultSignatures+        DeriveDataTypeable+        DeriveGeneric+        FlexibleContexts+        Safe+        TupleSections+        TypeOperators++    ghc-options:         -Wall++    build-depends:+        array,+        base,+        ghc-prim,+        -- end of packages with inherited version constraints+        test-framework == 0.8.*,+        test-framework-hunit == 0.3.*,+        HUnit >= 1.2 && < 1.7
+ fixtures/semigroups.0.1.diff view
@@ -0,0 +1,2 @@+ERROR+Cannot add or remove 'if' conditionals
+ fixtures/semigroups.0.2.diff view
@@ -0,0 +1,5 @@+OK+Normal+the library component's library dependency on 'containers'+- >=0.3 && <0.7++ >=0.3 && <0.8
+ fixtures/semigroups.0.cabal view
@@ -0,0 +1,173 @@+name:          semigroups+category:      Algebra, Data, Data Structures, Math+version:       0.18.5+license:       BSD3+cabal-version: >= 1.10+license-file:  LICENSE+author:        Edward A. Kmett+maintainer:    Edward A. Kmett <ekmett@gmail.com>+stability:     provisional+homepage:      http://github.com/ekmett/semigroups/+bug-reports:   http://github.com/ekmett/semigroups/issues+copyright:     Copyright (C) 2011-2015 Edward A. Kmett+synopsis:      Anything that associates+description:+    In mathematics, a semigroup is an algebraic structure consisting of a set together with an associative binary operation. A semigroup generalizes a monoid in that there might not exist an identity element. It also (originally) generalized a group (a monoid with all inverses) to a type where every element did not have to have an inverse, thus the name semigroup.+build-type:    Simple+extra-source-files: .travis.yml README.markdown CHANGELOG.markdown+tested-with:   GHC == 7.0.4+             , GHC == 7.2.2+             , GHC == 7.4.2+             , GHC == 7.6.3+             , GHC == 7.8.4+             , GHC == 7.10.3+             , GHC == 8.0.2+             , GHC == 8.2.2+             , GHC == 8.4.3+             , GHC == 8.6.1++source-repository head+  type: git+  location: git://github.com/ekmett/semigroups.git++flag hashable+  description:+    You can disable the use of the `hashable` package using `-f-hashable`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+    .+    If disabled we will not supply instances of `Hashable`+    .+    Note: `-f-hashable` implies `-f-unordered-containers`, as we are necessarily not able to supply those instances as well.+  default: True+  manual: True++flag binary+  description:+    You can disable the use of the `binary` package using `-f-binary`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag bytestring+  description:+    You can disable the use of the `bytestring` package using `-f-bytestring`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag bytestring-builder+  description:+    Decides whether to use an older version of bytestring along with bytestring-builder or just a newer version of bytestring.+    .+    This flag normally toggles automatically but you can use `-fbytestring-builder` or `-f-bytestring-builder` to explicitly change it.+  default: False+  manual: False++flag containers+  description:+    You can disable the use of the `containers` package using `-f-containers`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag deepseq+  description:+    You can disable the use of the `deepseq` package using `-f-deepseq`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag tagged+  description:+    You can disable the use of the `tagged` package using `-f-tagged`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag text+  description:+    You can disable the use of the `text` package using `-f-text`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag transformers+  description:+    You can disable the use of the `transformers` and `transformers-compat` packages using `-f-transformers`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag unordered-containers+  description:+    You can disable the use of the `unordered-containers` package using `-f-unordered-containers`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++library+  default-language: Haskell98+  hs-source-dirs: src+  ghc-options: -Wall++  build-depends: base >= 2 && < 5++  if impl(ghc >= 7.2)+    exposed-modules:+      Data.Semigroup.Generic++  -- legacy configuration+  if impl(ghc < 7.11.20151002)+    -- starting with GHC 8 these modules are provided by `base`+    hs-source-dirs: src-ghc7+    exposed-modules:+      Data.Semigroup+      Data.List.NonEmpty++    -- Not needed anymore since GHC 7.10+    if impl(ghc < 7.10)+      build-depends: nats >= 0.1 && < 2++    if impl(ghc >= 7.2 && < 7.5)+      build-depends: ghc-prim++    if flag(binary)+      build-depends: binary++    if flag(bytestring)+      if flag(bytestring-builder)+        build-depends: bytestring         >= 0.9    && < 0.10.4,+                       bytestring-builder >= 0.10.4 && < 1+      else+        build-depends: bytestring         >= 0.10.4 && < 1++    if flag(containers)+      build-depends: containers >= 0.3 && < 0.7++    if flag(deepseq)+      build-depends: deepseq >= 1.1 && < 1.5++    if flag(tagged)+      build-depends: tagged >= 0.4.4 && < 1++    if flag(text)+      build-depends: text >= 0.10 && < 2++    if flag(hashable)+      build-depends: hashable >= 1.1  && < 1.3++    if flag(hashable) && flag(unordered-containers)+      build-depends: unordered-containers >= 0.2  && < 0.3++    if flag(transformers)+      build-depends: transformers        >= 0.2 && < 0.6+                   , transformers-compat >= 0.5 && < 1
+ fixtures/semigroups.1.0.diff view
@@ -0,0 +1,2 @@+ERROR+Cannot add or remove 'if' conditionals
+ fixtures/semigroups.1.2.diff view
@@ -0,0 +1,2 @@+ERROR+Cannot add or remove 'if' conditionals
+ fixtures/semigroups.1.cabal view
@@ -0,0 +1,176 @@+name:          semigroups+category:      Algebra, Data, Data Structures, Math+version:       0.18.5+license:       BSD3+cabal-version: >= 1.10+license-file:  LICENSE+author:        Edward A. Kmett+maintainer:    Edward A. Kmett <ekmett@gmail.com>+stability:     provisional+homepage:      http://github.com/ekmett/semigroups/+bug-reports:   http://github.com/ekmett/semigroups/issues+copyright:     Copyright (C) 2011-2015 Edward A. Kmett+synopsis:      Anything that associates+description:+    In mathematics, a semigroup is an algebraic structure consisting of a set together with an associative binary operation. A semigroup generalizes a monoid in that there might not exist an identity element. It also (originally) generalized a group (a monoid with all inverses) to a type where every element did not have to have an inverse, thus the name semigroup.+build-type:    Simple+extra-source-files: .travis.yml README.markdown CHANGELOG.markdown+tested-with:   GHC == 7.0.4+             , GHC == 7.2.2+             , GHC == 7.4.2+             , GHC == 7.6.3+             , GHC == 7.8.4+             , GHC == 7.10.3+             , GHC == 8.0.2+             , GHC == 8.2.2+             , GHC == 8.4.3+             , GHC == 8.6.1++source-repository head+  type: git+  location: git://github.com/ekmett/semigroups.git++flag hashable+  description:+    You can disable the use of the `hashable` package using `-f-hashable`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+    .+    If disabled we will not supply instances of `Hashable`+    .+    Note: `-f-hashable` implies `-f-unordered-containers`, as we are necessarily not able to supply those instances as well.+  default: True+  manual: True++flag binary+  description:+    You can disable the use of the `binary` package using `-f-binary`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag bytestring+  description:+    You can disable the use of the `bytestring` package using `-f-bytestring`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag bytestring-builder+  description:+    Decides whether to use an older version of bytestring along with bytestring-builder or just a newer version of bytestring.+    .+    This flag normally toggles automatically but you can use `-fbytestring-builder` or `-f-bytestring-builder` to explicitly change it.+  default: False+  manual: False++flag containers+  description:+    You can disable the use of the `containers` package using `-f-containers`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag deepseq+  description:+    You can disable the use of the `deepseq` package using `-f-deepseq`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag tagged+  description:+    You can disable the use of the `tagged` package using `-f-tagged`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag text+  description:+    You can disable the use of the `text` package using `-f-text`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag transformers+  description:+    You can disable the use of the `transformers` and `transformers-compat` packages using `-f-transformers`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag unordered-containers+  description:+    You can disable the use of the `unordered-containers` package using `-f-unordered-containers`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++library+  default-language: Haskell98+  hs-source-dirs: src+  ghc-options: -Wall++  build-depends: base >= 2 && < 5++  if impl(ghc >= 7.2)+    exposed-modules:+      Data.Semigroup.Generic++  -- legacy configuration+  if impl(ghc < 7.11.20151002)+    -- starting with GHC 8 these modules are provided by `base`+    hs-source-dirs: src-ghc7+    exposed-modules:+      Data.Semigroup+      Data.List.NonEmpty++    -- Not needed anymore since GHC 7.10+    if impl(ghc < 7.10)+      build-depends: nats >= 0.1 && < 2++    if impl(ghc >= 7.2 && < 7.5)+      build-depends: ghc-prim++    if flag(binary)+      build-depends: binary++    if flag(bytestring)+      if flag(bytestring-builder)+        build-depends: bytestring         >= 0.9    && < 0.10.4,+                       bytestring-builder >= 0.10.4 && < 1+      else+        build-depends: bytestring         >= 0.10.4 && < 1++    if flag(containers)+      build-depends: containers >= 0.3 && < 0.8++    if flag(deepseq)+      build-depends: deepseq >= 1.1 && < 1.5++    if flag(tagged)+      build-depends: tagged >= 0.4.4 && < 1++    if flag(text)+      build-depends: text >= 0.10 && < 2++    if flag(hashable)+      build-depends: hashable >= 1.1  && < 1.3++    if flag(hashable) && flag(unordered-containers)+      build-depends: unordered-containers >= 0.2  && < 0.3++    if flag(transformers)+      build-depends: transformers        >= 0.2 && < 0.6+                   , transformers-compat >= 0.5 && < 1++    if impl(ghc==8.0.1)+      build-depends: base <0
+ fixtures/semigroups.2.0.diff view
@@ -0,0 +1,5 @@+OK+Normal+the library component's library dependency on 'containers'+- >=0.3 && <0.8++ >=0.3 && <0.7
+ fixtures/semigroups.2.1.diff view
@@ -0,0 +1,2 @@+ERROR+Cannot add or remove 'if' conditionals
+ fixtures/semigroups.2.cabal view
@@ -0,0 +1,173 @@+name:          semigroups+category:      Algebra, Data, Data Structures, Math+version:       0.18.5+license:       BSD3+cabal-version: >= 1.10+license-file:  LICENSE+author:        Edward A. Kmett+maintainer:    Edward A. Kmett <ekmett@gmail.com>+stability:     provisional+homepage:      http://github.com/ekmett/semigroups/+bug-reports:   http://github.com/ekmett/semigroups/issues+copyright:     Copyright (C) 2011-2015 Edward A. Kmett+synopsis:      Anything that associates+description:+    In mathematics, a semigroup is an algebraic structure consisting of a set together with an associative binary operation. A semigroup generalizes a monoid in that there might not exist an identity element. It also (originally) generalized a group (a monoid with all inverses) to a type where every element did not have to have an inverse, thus the name semigroup.+build-type:    Simple+extra-source-files: .travis.yml README.markdown CHANGELOG.markdown+tested-with:   GHC == 7.0.4+             , GHC == 7.2.2+             , GHC == 7.4.2+             , GHC == 7.6.3+             , GHC == 7.8.4+             , GHC == 7.10.3+             , GHC == 8.0.2+             , GHC == 8.2.2+             , GHC == 8.4.3+             , GHC == 8.6.1++source-repository head+  type: git+  location: git://github.com/ekmett/semigroups.git++flag hashable+  description:+    You can disable the use of the `hashable` package using `-f-hashable`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+    .+    If disabled we will not supply instances of `Hashable`+    .+    Note: `-f-hashable` implies `-f-unordered-containers`, as we are necessarily not able to supply those instances as well.+  default: True+  manual: True++flag binary+  description:+    You can disable the use of the `binary` package using `-f-binary`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag bytestring+  description:+    You can disable the use of the `bytestring` package using `-f-bytestring`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag bytestring-builder+  description:+    Decides whether to use an older version of bytestring along with bytestring-builder or just a newer version of bytestring.+    .+    This flag normally toggles automatically but you can use `-fbytestring-builder` or `-f-bytestring-builder` to explicitly change it.+  default: False+  manual: False++flag containers+  description:+    You can disable the use of the `containers` package using `-f-containers`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag deepseq+  description:+    You can disable the use of the `deepseq` package using `-f-deepseq`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag tagged+  description:+    You can disable the use of the `tagged` package using `-f-tagged`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag text+  description:+    You can disable the use of the `text` package using `-f-text`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag transformers+  description:+    You can disable the use of the `transformers` and `transformers-compat` packages using `-f-transformers`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++flag unordered-containers+  description:+    You can disable the use of the `unordered-containers` package using `-f-unordered-containers`.+    .+    Disabling this is an unsupported configuration, but it may be useful for accelerating builds in sandboxes for expert users.+  default: True+  manual: True++library+  default-language: Haskell98+  hs-source-dirs: src+  ghc-options: -Wall++  build-depends: base >= 2 && < 5++  if impl(ghc >= 7.2)+    exposed-modules:+      Data.Semigroup.Generic++  -- legacy configuration+  if impl(ghc < 7.11.20151002)+    -- starting with GHC 8 these modules are provided by `base`+    hs-source-dirs: src-ghc7+    exposed-modules:+      Data.Semigroup+      Data.List.NonEmpty++    -- Not needed anymore since GHC 7.10+    if impl(ghc < 7.10)+      build-depends: nats >= 0.1 && < 2++    if impl(ghc >= 7.2 && < 7.5)+      build-depends: ghc-prim++    if flag(binary)+      build-depends: binary++    if flag(bytestring)+      if flag(bytestring-builder)+        build-depends: bytestring         >= 0.9    && < 0.10.4,+                       bytestring-builder >= 0.10.4 && < 1+      else+        build-depends: bytestring         >= 0.10.4 && < 1++    if flag(containers)+      build-depends: containers >= 0.3 && < 0.8++    if flag(deepseq)+      build-depends: deepseq >= 1.1 && < 1.5++    if flag(tagged)+      build-depends: tagged >= 0.4.4 && < 1++    if flag(text)+      build-depends: text >= 0.10 && < 2++    if flag(hashable)+      build-depends: hashable >= 1.1  && < 1.3++    if flag(hashable) && flag(unordered-containers)+      build-depends: unordered-containers >= 0.2  && < 0.3++    if flag(transformers)+      build-depends: transformers        >= 0.2 && < 0.6+                   , transformers-compat >= 0.5 && < 1
+ fixtures/tree-diff.0.1.diff view
@@ -0,0 +1,13 @@+OK+Trivial+tested-with+- ghc ==7.8.4: ghc ==7.10.3: ghc ==8.0.2: ghc ==8.2.2: ghc ==8.4.4: ghc ==8.6.2++ ghc ==7.8.4: ghc ==7.10.3: ghc ==8.0.2: ghc ==8.2.2: ghc ==8.4.4: ghc ==8.6.3+Normal+the library component's library dependency on 'ansi-terminal'+- >=0.6.3.1 && <0.9++ >=0.6.3.1 && <0.10+Normal+the test suite 'test' component's library dependency on 'tasty'+- >=0.11.2.5 && <1.2++ >=0.11.2.5 && <1.3
+ fixtures/tree-diff.0.cabal view
@@ -0,0 +1,129 @@+cabal-version:       >=1.10+name:                tree-diff+version:             0.0.2++synopsis:            Diffing of (expression) trees.+category:            Data, Testing+description:+  Common diff algorithm works on list structures:+  .+  @+  diff :: Eq a => [a] -> [a] -> [Edit a]+  @+  .+  This package works on trees.+  .+  @+  treeDiff :: Eq a => Tree a -> Tree a -> Edit (EditTree a)+  @+  .+  This package also provides a way to diff arbitrary ADTs,+  using @Generics@-derivable helpers.+  .+  This package differs from <http://hackage.haskell.org/package/gdiff gdiff>,+  in a two ways: @tree-diff@ doesn't have patch function,+  and the "edit-script" is a tree itself, which is useful for pretty-printing.+  .+  @+  >>> prettyEditExpr $ ediff (Foo 42 [True, False] "old") (Foo 42 [False, False, True] "new")+  Foo+    {fooBool = [-True, +False, False, +True],+     fooInt = 42,+     fooString = -"old" +"new"}+  @+homepage:            https://github.com/phadej/tree-diff+bug-reports:         https://github.com/phadej/tree-diff/issues+license:             BSD3+license-file:        LICENSE+author:              Oleg Grenrus <oleg.grenrus@iki.fi>+maintainer:          Oleg.Grenrus <oleg.grenrus@iki.fi>+copyright:           (c) 2017-2018 Oleg Grenrus+build-type:          Simple+extra-source-files:  README.md ChangeLog.md+tested-with:+  GHC==7.8.4,+  GHC==7.10.3,+  GHC==8.0.2,+  GHC==8.2.2,+  GHC==8.4.4,+  GHc==8.6.2++extra-source-files:+  fixtures/exfoo.expr++source-repository head+  type:      git+  location:  https://github.com/phadej/tree-diff.git++library+  exposed-modules:+    Data.TreeDiff+    Data.TreeDiff.List+    Data.TreeDiff.Tree+    Data.TreeDiff.Expr+    Data.TreeDiff.Class+    Data.TreeDiff.Pretty+    Data.TreeDiff.Parser+    Data.TreeDiff.Golden+    Data.TreeDiff.QuickCheck+  build-depends:+    base                 >=4.7      && <4.13,+    aeson                >=1.2.1.0  && <1.5,+    ansi-wl-pprint       >=0.6.8.1  && <0.7,+    ansi-terminal        >=0.6.3.1  && <0.9,+    base-compat          >=0.9.3    && <0.11,+    bytestring           >=0.10.4.0 && <0.11,+    containers           >=0.5.5.1  && <0.7,+    generics-sop         >=0.3.1.0  && <0.5,+    hashable             >=1.2.6.1  && <1.3,+    MemoTrie             >=0.6.8    && <0.7,+    parsec               >=3.1.11   && <3.2,+    parsers              >=0.12.7   && <0.13,+    pretty               >=1.1.1.1  && <1.2,+    QuickCheck           >=2.10.0.1 && <2.13,+    scientific           >=0.3.5.2  && <0.4,+    tagged               >=0.8.5    && <0.9,+    text                 >=1.2.2.2  && <1.3,+    time                 >=1.4.2    && <1.9,+    unordered-containers >=0.2.8.0  && <0.3,+    uuid-types           >=1.0.3    && <1.1,+    vector               >=0.12     && <0.13++  if !impl(ghc >= 8.0)+    build-depends:+      semigroups         >=0.18.3   && <0.19++  if !impl(ghc >= 7.10)+    build-depends:+      void               >=0.7.2    && <0.8,+      nats               >=1.1.1    && <1.2,+      transformers       >=0.3.0.0  && <0.6++  other-extensions:+    ConstraintKinds+    CPP+    DefaultSignatures+    FlexibleContexts+    GADTs+    RankNTypes+    ScopedTypeVariables+  hs-source-dirs:      src+  default-language:    Haskell2010++test-suite test+  default-language:    Haskell2010+  type:                exitcode-stdio-1.0+  main-is:             Tests.hs+  hs-source-dirs:      tests+  ghc-options:         -Wall -threaded+  build-depends:+    base, tree-diff,+    base-compat,+    QuickCheck,+    ansi-terminal,+    ansi-wl-pprint,+    parsec,+    trifecta             >=1.7.1.1  && <2.1,+    tasty                >=0.11.2.5 && <1.2,+    tasty-golden         >=2.3.1.1  && <2.4,+    tasty-quickcheck     >=0.9.1    && <0.11
+ fixtures/tree-diff.1.0.diff view
@@ -0,0 +1,13 @@+OK+Trivial+tested-with+- ghc ==7.8.4: ghc ==7.10.3: ghc ==8.0.2: ghc ==8.2.2: ghc ==8.4.4: ghc ==8.6.3++ ghc ==7.8.4: ghc ==7.10.3: ghc ==8.0.2: ghc ==8.2.2: ghc ==8.4.4: ghc ==8.6.2+Normal+the library component's library dependency on 'ansi-terminal'+- >=0.6.3.1 && <0.10++ >=0.6.3.1 && <0.9+Normal+the test suite 'test' component's library dependency on 'tasty'+- >=0.11.2.5 && <1.3++ >=0.11.2.5 && <1.2
+ fixtures/tree-diff.1.cabal view
@@ -0,0 +1,130 @@+cabal-version:       >=1.10+name:                tree-diff+version:             0.0.2+x-revision:          1++synopsis:            Diffing of (expression) trees.+category:            Data, Testing+description:+  Common diff algorithm works on list structures:+  .+  @+  diff :: Eq a => [a] -> [a] -> [Edit a]+  @+  .+  This package works on trees.+  .+  @+  treeDiff :: Eq a => Tree a -> Tree a -> Edit (EditTree a)+  @+  .+  This package also provides a way to diff arbitrary ADTs,+  using @Generics@-derivable helpers.+  .+  This package differs from <http://hackage.haskell.org/package/gdiff gdiff>,+  in a two ways: @tree-diff@ doesn't have patch function,+  and the "edit-script" is a tree itself, which is useful for pretty-printing.+  .+  @+  >>> prettyEditExpr $ ediff (Foo 42 [True, False] "old") (Foo 42 [False, False, True] "new")+  Foo+    {fooBool = [-True, +False, False, +True],+     fooInt = 42,+     fooString = -"old" +"new"}+  @+homepage:            https://github.com/phadej/tree-diff+bug-reports:         https://github.com/phadej/tree-diff/issues+license:             BSD3+license-file:        LICENSE+author:              Oleg Grenrus <oleg.grenrus@iki.fi>+maintainer:          Oleg.Grenrus <oleg.grenrus@iki.fi>+copyright:           (c) 2017-2018 Oleg Grenrus+build-type:          Simple+extra-source-files:  README.md ChangeLog.md+tested-with:+  GHC==7.8.4,+  GHC==7.10.3,+  GHC==8.0.2,+  GHC==8.2.2,+  GHC==8.4.4,+  GHc==8.6.3++extra-source-files:+  fixtures/exfoo.expr++source-repository head+  type:      git+  location:  https://github.com/phadej/tree-diff.git++library+  exposed-modules:+    Data.TreeDiff+    Data.TreeDiff.List+    Data.TreeDiff.Tree+    Data.TreeDiff.Expr+    Data.TreeDiff.Class+    Data.TreeDiff.Pretty+    Data.TreeDiff.Parser+    Data.TreeDiff.Golden+    Data.TreeDiff.QuickCheck+  build-depends:+    base                 >=4.7      && <4.13,+    aeson                >=1.2.1.0  && <1.5,+    ansi-wl-pprint       >=0.6.8.1  && <0.7,+    ansi-terminal        >=0.6.3.1  && <0.10,+    base-compat          >=0.9.3    && <0.11,+    bytestring           >=0.10.4.0 && <0.11,+    containers           >=0.5.5.1  && <0.7,+    generics-sop         >=0.3.1.0  && <0.5,+    hashable             >=1.2.6.1  && <1.3,+    MemoTrie             >=0.6.8    && <0.7,+    parsec               >=3.1.11   && <3.2,+    parsers              >=0.12.7   && <0.13,+    pretty               >=1.1.1.1  && <1.2,+    QuickCheck           >=2.10.0.1 && <2.13,+    scientific           >=0.3.5.2  && <0.4,+    tagged               >=0.8.5    && <0.9,+    text                 >=1.2.2.2  && <1.3,+    time                 >=1.4.2    && <1.9,+    unordered-containers >=0.2.8.0  && <0.3,+    uuid-types           >=1.0.3    && <1.1,+    vector               >=0.12     && <0.13++  if !impl(ghc >= 8.0)+    build-depends:+      semigroups         >=0.18.3   && <0.19++  if !impl(ghc >= 7.10)+    build-depends:+      void               >=0.7.2    && <0.8,+      nats               >=1.1.1    && <1.2,+      transformers       >=0.3.0.0  && <0.6++  other-extensions:+    ConstraintKinds+    CPP+    DefaultSignatures+    FlexibleContexts+    GADTs+    RankNTypes+    ScopedTypeVariables+  hs-source-dirs:      src+  default-language:    Haskell2010++test-suite test+  default-language:    Haskell2010+  type:                exitcode-stdio-1.0+  main-is:             Tests.hs+  hs-source-dirs:      tests+  ghc-options:         -Wall -threaded+  build-depends:+    base, tree-diff,+    base-compat,+    QuickCheck,+    ansi-terminal,+    ansi-wl-pprint,+    parsec,+    trifecta             >=1.7.1.1  && <2.1,+    tasty                >=0.11.2.5 && <1.3,+    tasty-golden         >=2.3.1.1  && <2.4,+    tasty-quickcheck     >=0.9.1    && <0.11
+ hackage-cli.cabal view
@@ -0,0 +1,122 @@+cabal-version:       2.4+name:                hackage-cli+version:             0.0.3.6++synopsis:            CLI tool for Hackage+description:+  With @hackage-cli@ you can manage @.cabal@ files,+  e.g. (bulk-)upload revised variants.+homepage:            https://github.com/hackage-trustees/hackage-cli+bug-reports:         https://github.com/hackage-trustees/hackage-cli/issues+license:             GPL-3.0-or-later+license-file:        LICENSE+author:              Herbert Valerio Riedel+maintainer:          Andreas Abel+category:            Development+build-type:          Simple++tested-with:+  -- Keep in descending order.+  GHC == 9.2.2+  GHC == 9.0.2+  GHC == 8.10.7+  GHC == 8.8.4+  GHC == 8.6.5+  GHC == 8.4.4+  GHC == 8.2.2++extra-source-files:+  CHANGELOG.md+  README.md+  fixtures/*.diff+  fixtures/*.cabal++source-repository head+  Type:     git+  Location: https://github.com/hackage-trustees/hackage-cli.git++library cabal-revisions+  X-SPDX-License-Identifier: BSD-3-Clause+  default-language:    Haskell2010+  hs-source-dirs:      lib+  ghc-options:         -Wall -Wcompat++  build-depends:+    , base         >= 4.10.0.0 && < 4.17+    , bytestring   >= 0.10.4.0 && < 0.12+    , Cabal        >= 3.4      && < 3.7+    , containers   >= 0.5.0.0  && < 0.7+    , mtl          >= 2.2.2    && < 2.4+    , pretty      ^>= 1.1.2++  exposed-modules:+    Distribution.Server.Util.CabalRevisions++test-suite cabal-revisions-tests+  default-language:    Haskell2010+  hs-source-dirs:      tests+  ghc-options:         -Wall -Wcompat+  type:                exitcode-stdio-1.0+  main-is:             Tests.hs+  build-depends:+    , base+    , bytestring+    , Cabal+    , cabal-revisions+    , tasty         >= 1.0      && < 1.5+        -- tasty-1.0 for stack-8.2.2.yaml+    , tasty-golden ^>= 2.3.2+    , filepath     ^>= 1.4.0.0++executable hackage-cli+  default-language:    Haskell2010+  other-extensions:+    CPP+    LambdaCase+    OverloadedStrings+    RecordWildCards+    TemplateHaskell++  hs-source-dirs:      src+  main-is:             Main.hs++  other-modules:+    IndexShaSum+    CabalEdit+    Paths_hackage_cli++  autogen-modules:+    Paths_hackage_cli++  build-depends:+    , base+    , bytestring+    , Cabal+    , cabal-revisions+    , containers+    , mtl++  build-depends:+    , aeson                  >= 1.2.4.0 && < 2.1+        -- aeson-1.2.4.0 for stack-8.2.2.yaml+    , deepseq               ^>= 1.4.0.0+    , directory             ^>= 1.2.0.1 || ^>= 1.3.0.0+    , filepath              ^>= 1.4.0.0+    , http-io-streams       ^>= 0.1.0.0+    , io-streams            ^>= 1.5.0.1+    , microlens              >= 0.4.8.3  && < 4.13+    , microlens-mtl          >= 0.1.11.1 && < 0.3+    , microlens-th           >= 0.4.1.3  && < 0.5+    , netrc                 ^>= 0.2.0.0+    , optparse-applicative   >= 0.14    && < 0.18+    , process-extras        ^>= 0.7.4+    , semigroups             >= 0.18.3  && < 0.21+    , stringsearch          ^>= 0.3.6+    , tagsoup               ^>= 0.14+    , tar                   ^>= 0.5+    , text                   >= 1.2     && < 2.1+    , time                   >= 1.5.0.1 && < 1.13+    , unordered-containers  ^>= 0.2.7+    , zlib                  ^>= 0.6.1++  ghc-options: -Wall -Wcompat -threaded
+ lib/Distribution/Server/Util/CabalRevisions.hs view
@@ -0,0 +1,771 @@+{-# LANGUAGE CPP                        #-}+{-# LANGUAGE FlexibleContexts           #-}+{-# LANGUAGE FunctionalDependencies     #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings          #-}+{-# LANGUAGE ScopedTypeVariables        #-}+{-# LANGUAGE TypeFamilies               #-}++-- |+-- Module      :  Distribution.Server.Util.CabalRevisions+-- Copyright   :  Duncan Coutts et al.+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Maintainer  :  libraries@haskell.org+-- Stability   :  provisional+-- Portability :  portable+--+-- Validation and helpers for Cabal revision handling+module Distribution.Server.Util.CabalRevisions+    ( diffCabalRevisions+    , diffCabalRevisions'+    , Change(..)+    , insertRevisionField+    ) where++-- NB: This module avoids to import any hackage-server modules+import Distribution.CabalSpecVersion (CabalSpecVersion(..), cabalSpecLatest, showCabalSpecVersion)+import Distribution.Types.Dependency+import Distribution.Types.ExeDependency+import Distribution.Types.PkgconfigDependency+import Distribution.Types.PkgconfigVersionRange+import Distribution.Types.LegacyExeDependency+import Distribution.Types.UnqualComponentName+import Distribution.Types.CondTree+import Distribution.Types.ForeignLib+import Distribution.Package+import Distribution.Pretty (Pretty (..), prettyShow)+import Distribution.Version+import Distribution.Compiler (CompilerFlavor)+import Distribution.FieldGrammar (prettyFieldGrammar)+import Distribution.Fields.Pretty (PrettyField (..), showFields)+import Distribution.PackageDescription+import Distribution.PackageDescription.Parsec (parseGenericPackageDescription, runParseResult)+import Distribution.PackageDescription.FieldGrammar (sourceRepoFieldGrammar)+import Distribution.PackageDescription.Check+import Distribution.Parsec (showPWarning, showPError, PWarning (..))+import Distribution.Utils.ShortText+import Text.PrettyPrint as Doc+         ((<+>), colon, text, Doc, hsep, punctuate)++import Control.Applicative+import Control.Monad+import Control.Monad.Except  (ExceptT, runExceptT, throwError)+import Control.Monad.Writer (MonadWriter(..), Writer, runWriter)+import Data.Foldable (for_)+import Data.List+         ((\\), deleteBy, intercalate)+import Data.ByteString.Lazy (ByteString)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy.Char8 as LBS8+import qualified Data.Char as Char+import qualified Data.Semigroup as S+import qualified Data.Monoid as M+import qualified Data.Set as Set+import qualified Data.Map.Strict as Map+import Data.Proxy (Proxy(Proxy))++import qualified Control.Monad.Fail as Fail++-- | Entry point to cabal revision validator+--+-- This takes an original and a revised @.cabal@ decoded as Unicode+-- 'String' and performs validations. Returns either a validation+-- error or a list of detected changes.+diffCabalRevisions :: BS.ByteString -> BS.ByteString -> Either String [Change]+diffCabalRevisions = diffCabalRevisions' True++-- | Like 'diffCabalRevisions' but only optionally check @x-revision@ field modifications.+diffCabalRevisions'+    :: Bool                    -- ^ check @x-revision@+    -> BS.ByteString           -- ^ old revision+    -> BS.ByteString           -- ^ new revision+    -> Either String [Change]+diffCabalRevisions' checkXRevision oldVersion newRevision = runCheck $+    checkCabalFileRevision checkXRevision oldVersion newRevision++newtype CheckM a = CheckM { unCheckM :: ExceptT String (Writer [Change]) a }+    deriving (Functor, Applicative)++runCheck :: CheckM () -> Either String [Change]+runCheck c = case runWriter . runExceptT . unCheckM $ c of+               (Left err, _      ) -> Left err+               (Right (), changes)+                 | foldMap changeSeverity changes /= Trivial -> Right changes+                 | otherwise ->+                   Left "Only trivial changes, don't bother making this revision."++changeSeverity :: Change -> Severity+changeSeverity (Change s _ _ _) = s++instance Monad CheckM where+  return         = Control.Applicative.pure+  CheckM m >>= f = CheckM (m >>= unCheckM . f)++#if !MIN_VERSION_base(4,13,0)+  fail = Fail.fail+#endif++instance Fail.MonadFail CheckM where+  fail           = CheckM . throwError++-- | If we have only 'Trivial' changes, then there is no point to make+-- a revision. In other words for changes to be accepted, there should+-- be at least one 'Normal' change.+data Severity+    = Normal+    | Trivial+  deriving (Eq, Ord, Show, Enum, Bounded)++instance S.Semigroup Severity where+    Normal  <> _ = Normal+    Trivial <> x = x++-- | "Max" monoid.+instance M.Monoid Severity where+    mempty = Trivial+    mappend = (S.<>)++data Change = Change Severity String String String -- severity, what, from, to+  deriving Show++++logChange :: Change -> CheckM ()+logChange change = CheckM (tell [change])++type Check a = a -> a -> CheckM ()++checkCabalFileRevision :: Bool -> Check BS.ByteString+checkCabalFileRevision checkXRevision old new = do+    (pkg,  warns)  <- parseCabalFile old+    (pkg', warns') <- parseCabalFile new++    let pkgid    = packageId pkg+        filename = prettyShow pkgid ++ ".cabal"++    checkGenericPackageDescription checkXRevision pkg pkg'+    checkParserWarnings filename warns warns'+    checkPackageChecks  pkg   pkg'++  where+    parseCabalFile fileContent =+      case runParseResult $ parseGenericPackageDescription fileContent of+        (warnings,  Right pkg) -> return (pkg, warnings)+        (_warnings, Left (_mver, errs)) -> do+            for_ errs $ \err -> fail (showPError "-" err)+            fail "no better error"++    -- new PWarning isn't Eq+    differenceBy :: (a -> a -> Bool) -> [a] -> [a] -> [a]+    differenceBy eq = foldl (flip $ deleteBy eq)++    -- things can move, pos can change+    eqPWarning :: PWarning -> PWarning -> Bool+    eqPWarning (PWarning t _pos s) (PWarning t' _pos' s') =+        t == t' && s == s'++    checkParserWarnings :: FilePath -> Check [PWarning]+    checkParserWarnings filename warns warns' =+      case differenceBy eqPWarning warns' warns of+        []       -> return ()+        newwarns -> fail $ "New parse warning: "+                        ++ unlines (map (showPWarning filename) newwarns)++    checkPackageChecks pkg pkg' =+      let checks  = checkPackage pkg  Nothing+          checks' = checkPackage pkg' Nothing+       in case checks' \\ checks of+            []        -> return ()+            newchecks -> fail $ unlines (map explanation newchecks)++checkGenericPackageDescription :: Bool -> Check GenericPackageDescription+checkGenericPackageDescription checkXRevision+    (GenericPackageDescription descrA _versionA flagsA libsA sublibsA flibsA exesA testsA benchsA)+    (GenericPackageDescription descrB _versionB flagsB libsB sublibsB flibsB exesB testsB benchsB) = do++    checkPackageDescriptions checkXRevision descrA descrB++    checkList "Cannot add or remove flags" checkFlag flagsA flagsB++    checkMaybe "Cannot add or remove library sections"+      (checkCondTree checkLibrary)+      (withComponentName' (CLibName LMainLibName) <$> libsA)+      (withComponentName' (CLibName LMainLibName) <$> libsB)++    checkListAssoc "Cannot add or remove sub-library sections"+      (checkCondTree checkLibrary)+      (withComponentName (CLibName . LSubLibName) <$> sublibsA)+      (withComponentName (CLibName . LSubLibName) <$> sublibsB)++    checkListAssoc "Cannot add or remove foreign-library sections"+      (checkCondTree checkForeignLib)+      (withComponentName CFLibName <$> flibsA)+      (withComponentName CFLibName <$> flibsB)++    checkListAssoc "Cannot add or remove executable sections"+      (checkCondTree checkExecutable)+      (withComponentName CExeName <$> exesA)+      (withComponentName CExeName <$> exesB)++    checkListAssoc "Cannot add or remove test-suite sections"+      (checkCondTree checkTestSuite)+      (withComponentName CTestName <$> testsA)+      (withComponentName CTestName <$> testsB)++    checkListAssoc "Cannot add or remove benchmark sections"+      (checkCondTree checkBenchmark)+      (withComponentName CBenchName <$> benchsA)+      (withComponentName CBenchName <$> benchsB)+  where+    withComponentName  f (name, condTree) = (name, (f name, condTree))+    withComponentName' f        condTree  = (f,             condTree)+++checkFlag :: Check PackageFlag+checkFlag flagOld flagNew = do+    -- This check is applied via 'checkList' and for simplicity we+    -- disallow renaming/reordering flags (even though reordering+    -- would be fine semantically)+    checkSame "Cannot change ordering of flags"+              (flagName flagOld) (flagName flagNew)++    -- Automatic flags' defaults may be changed as they don't make new+    -- configurations reachable by the solver that weren't before+    --+    -- Moreover, automatic flags may be converted into manual flags+    -- but not the other way round.+    --+    -- NB: We always allow to change the flag description as it has+    --     purely informational value+    when (flagManual flagOld) $ do+        checkSame "Cannot change the default of a manual flag"+                  (flagDefault flagOld) (flagDefault flagNew)++        checkSame "Cannot change a manual flag into an automatic flag"+                  (flagManual flagOld) (flagManual flagNew)++    let fname = unFlagName (flagName flagOld)++    changesOk ("type of flag '" ++ fname ++ "'")+              (\b -> if b then "manual" else "automatic")+              (flagManual flagOld) (flagManual flagNew)++    changesOk ("default of flag '" ++ fname ++ "'") prettyShow+              (flagDefault flagOld) (flagDefault flagNew)++    changesOk ("description of flag '" ++ fname ++ "'") id+              (flagDescription flagOld) (flagDescription flagNew)++checkPackageDescriptions :: Bool -> Check PackageDescription+checkPackageDescriptions checkXRevision+  pdA@(PackageDescription+     { specVersion     = _specVersionA+     , package         = packageIdA+     , licenseRaw      = licenseRawA+     , licenseFiles    = licenseFilesA+     , copyright       = copyrightA+     , maintainer      = maintainerA+     , author          = authorA+     , stability       = stabilityA+     , testedWith      = testedWithA+     , homepage        = homepageA+     , pkgUrl          = pkgUrlA+     , bugReports      = bugReportsA+     , sourceRepos     = sourceReposA+     , synopsis        = synopsisA+     , description     = descriptionA+     , category        = categoryA+     , customFieldsPD  = customFieldsPDA+     , buildTypeRaw    = buildTypeRawA+     , setupBuildInfo  = setupBuildInfoA+     , library         = _libraryA+     , subLibraries    = _subLibrariesA+     , executables     = _executablesA+     , foreignLibs     = _foreignLibsA+     , testSuites      = _testSuitesA+     , benchmarks      = _benchmarksA+     , dataFiles       = dataFilesA+     , dataDir         = dataDirA+     , extraSrcFiles   = extraSrcFilesA+     , extraTmpFiles   = extraTmpFilesA+     , extraDocFiles   = extraDocFilesA+     })+  pdB@(PackageDescription+     { specVersion     = _specVersionB+     , package         = packageIdB+     , licenseRaw      = licenseRawB+     , licenseFiles    = licenseFilesB+     , copyright       = copyrightB+     , maintainer      = maintainerB+     , author          = authorB+     , stability       = stabilityB+     , testedWith      = testedWithB+     , homepage        = homepageB+     , pkgUrl          = pkgUrlB+     , bugReports      = bugReportsB+     , sourceRepos     = sourceReposB+     , synopsis        = synopsisB+     , description     = descriptionB+     , category        = categoryB+     , customFieldsPD  = customFieldsPDB+     , buildTypeRaw    = buildTypeRawB+     , setupBuildInfo  = setupBuildInfoB+     , library         = _libraryB+     , subLibraries    = _subLibrariesB+     , executables     = _executablesB+     , foreignLibs     = _foreignLibsB+     , testSuites      = _testSuitesB+     , benchmarks      = _benchmarksB+     , dataFiles       = dataFilesB+     , dataDir         = dataDirB+     , extraSrcFiles   = extraSrcFilesB+     , extraTmpFiles   = extraTmpFilesB+     , extraDocFiles   = extraDocFilesB+     })+  = do+  checkSame "Don't be silly! You can't change the package name!"+            (packageName packageIdA) (packageName packageIdB)+  checkSame "You can't change the package version!"+            (packageVersion packageIdA) (packageVersion packageIdB)+  checkSame "Cannot change the license"+            (licenseRawA, licenseFilesA) (licenseRawB, licenseFilesB)+  changesOk "copyright"  fromShortText copyrightA copyrightB+  changesOk "maintainer" fromShortText maintainerA maintainerB+  changesOk "author"     fromShortText authorA authorB+  checkSame "The stability field is unused, don't bother changing it."+            stabilityA stabilityB+  changesOk' Trivial "tested-with" (show . ppTestedWith) testedWithA testedWithB+  changesOk "homepage" fromShortText homepageA homepageB+  checkSame "The package-url field is unused, don't bother changing it."+            pkgUrlA pkgUrlB+  changesOk "bug-reports" fromShortText bugReportsA bugReportsB+  changesOkList changesOk "source-repository" (showFields (const []) . (:[]) . ppSourceRepo)+            sourceReposA sourceReposB+  changesOk "synopsis"    fromShortText synopsisA synopsisB+  changesOk "description" fromShortText descriptionA descriptionB+  changesOk "category"    fromShortText categoryA categoryB+  checkSame "Cannot change the build-type"+            buildTypeRawA buildTypeRawB+  checkSame "Cannot change the data files"+            (dataFilesA, dataDirA) (dataFilesB, dataDirB)+  checkSame "Changing extra-tmp-files is a bit pointless at this stage"+            extraTmpFilesA extraTmpFilesB+  checkSame "Changing extra-source-files would not make sense!"+            extraSrcFilesA extraSrcFilesB+  checkSame "You can't change the extra-doc-files."+            extraDocFilesA extraDocFilesB++  checkSame "Cannot change custom/extension fields"+            (filter (\(f,_) -> not (f `elem` ["x-revision","x-curation"])) customFieldsPDA)+            (filter (\(f,_) -> not (f `elem` ["x-revision","x-curation"])) customFieldsPDB)++  checkSpecVersionRaw pdA pdB+  checkSetupBuildInfo setupBuildInfoA setupBuildInfoB++  when checkXRevision $ checkRevision customFieldsPDA customFieldsPDB+  checkCuration customFieldsPDA customFieldsPDB++checkSpecVersionRaw :: Check PackageDescription+checkSpecVersionRaw pdA pdB+  | range110To120 specVersionA+  , range110To120 specVersionB+  = changesOk "cabal-version" showCabalSpecVersion specVersionA specVersionB++  | otherwise+  = checkSame "Cannot change the Cabal spec version"+              specVersionA specVersionB+  where+    specVersionA = specVersion pdA+    specVersionB = specVersion pdB++    -- nothing interesting changed within the  Cabal >=1.10 && <1.21 range+    -- therefore we allow to change the spec version within this interval+    range110To120 v = CabalSpecV1_10 >= v && v <= CabalSpecV1_20++checkRevision :: Check [(String, String)]+checkRevision customFieldsA customFieldsB =+    checkSame ("The new x-revision must be " ++ show expectedRevision)+              newRevision expectedRevision+  where+    oldRevision = getRevision customFieldsA+    newRevision = getRevision customFieldsB+    expectedRevision = oldRevision + 1++    getRevision customFields =+      case lookup "x-revision" customFields of+        Just s  | [(n,"")] <- reads s -> n :: Int+        _                             -> 0++checkCuration :: Check [(String, String)]+checkCuration customFieldsA customFieldsB =+    checkNotPresent "Revised metadata must not contain an x-curation field as revisions necessarily imply curation, and revising an uncurated package adopts it into the curated layer." oldCuration newCuration+  where+    oldCuration = lookup "x-curation" customFieldsA+    newCuration = lookup "x-curation" customFieldsB++++checkCondTree :: (ComponentName -> Check a) -> Check (ComponentName, CondTree ConfVar [Dependency] a)+checkCondTree checkElem (componentName, condNodeA)+                        (_            , condNodeB) =+    checkCondNode condNodeA condNodeB+  where+    checkCondNode (CondNode dataA constraintsA componentsA)+                  (CondNode dataB constraintsB componentsB) = do+      checkDependencies componentName constraintsA constraintsB+      checkList "Cannot add or remove 'if' conditionals"+                checkComponent componentsA componentsB+      checkElem componentName dataA dataB++    checkComponent (CondBranch condA ifPartA thenPartA)+                   (CondBranch condB ifPartB thenPartB) = do+      checkSame "Cannot change the 'if' condition expressions"+                condA condB+      checkCondNode ifPartA ifPartB+      checkMaybe "Cannot add or remove the 'else' part in conditionals"+                 checkCondNode thenPartA thenPartB++checkDependencies :: forall d vr. (Pretty d, IsDependency vr d) => ComponentName -> Check [d]+checkDependencies componentName ds1 ds2 = do+    forM_ removed $ \dep -> do+        fail (unwords [ "Cannot remove existing", depKind, "on"+                      , depKeyShow dproxy (depKey dep), "in", cnameStr, " component"])++    forM_ added $ \dep ->+        if depInAddWhitelist dep+           then logChange (Change Normal (unwords ["added the", cnameStr, "component's"+                                                  , depKind, "on"]) "" (prettyShow dep))+           else fail (unwords [ "Cannot add new", depKind, "on"+                              , depKeyShow dproxy (depKey dep)+                              , "in", cnameStr, "component"])++    forM_ changed $ \(depk, (verA, verB)) -> do+        changesOk (unwords ["the", cnameStr, "component's", depKind, "on"+                           , depKeyShow dproxy depk])+                   prettyShow verA verB+  where+    (removed, changed, added) = computeCanonDepChange ds1 ds2++    dproxy :: Proxy d+    dproxy = Proxy++    cnameStr = showComponentName componentName++    depKind = depTypeName dproxy ++ " dependency"++class (Ord (DepKey d), Pretty vr, Eq vr) => IsDependency vr d | d -> vr where+    type DepKey d++    depTypeName    :: Proxy d -> String+    depKey         :: d -> DepKey d+    depKeyShow     :: Proxy d -> DepKey d -> String+    depVerRg       :: d -> vr+    reconstructDep :: DepKey d -> vr -> d++    depInAddWhitelist :: d -> Bool+    depInAddWhitelist _ = False++    intersectVr :: Proxy d -> vr -> vr -> vr++instance IsDependency VersionRange Dependency where+    type DepKey Dependency = PackageName++    depTypeName Proxy             = "library"+    depKey (Dependency pkgname _ _) = pkgname+    depKeyShow Proxy              = prettyShow''+    depVerRg (Dependency _ vr _)  = vr+    reconstructDep                = \n vr -> Dependency n vr mainLibSet++    depInAddWhitelist (Dependency pn _ _) = pn `elem`+    -- Special case: there are some pretty weird broken packages out there, see+    --   https://github.com/haskell/hackage-server/issues/303+    -- which need us to add a new dep on `base`+            [ mkPackageName "base"++    -- See also https://github.com/haskell/hackage-server/issues/472+    --+    -- this is mostly to allow to add dependencies on `base-orphans == 0`+    -- as otherwise we have no way to express when a package is+    -- incompatible with the recently introduced `base-orphans` package+    -- which started adopting orphan instances; in the long-term we need a+    -- more general approach to this, as otherwise we'll end up adding+    -- ad-hoc exceptions like this one. See e.g.+    --   https://github.com/haskell/cabal/issues/3061+    --+            , mkPackageName "base-orphans"+            ]++    intersectVr _ = intersectVersionRanges+++instance IsDependency VersionRange ExeDependency where+    type DepKey ExeDependency = (PackageName,UnqualComponentName)++    depTypeName Proxy                   = "tool"+    depKey (ExeDependency pkgname cn _) = (pkgname,cn)+    depKeyShow Proxy (pkgname,cn)       = concat ["'", prettyShow pkgname, ":", prettyShow cn, "'"]+    depVerRg (ExeDependency _ _ vr)     = vr+    reconstructDep (pkgname,cn)         = ExeDependency pkgname cn++    intersectVr _ = intersectVersionRanges++instance IsDependency VersionRange LegacyExeDependency where+    type DepKey LegacyExeDependency = String++    depTypeName Proxy                      = "legacy-tool"+    depKey (LegacyExeDependency tname _)   = tname+    depKeyShow Proxy tname                 = "'" ++ tname ++ "'"+    depVerRg (LegacyExeDependency _ vr)    = vr+    reconstructDep                         = LegacyExeDependency++    intersectVr _ = intersectVersionRanges++    depInAddWhitelist (LegacyExeDependency pn _) = pn `elem`+    -- list of trusted tools cabal supports w/o explicit build-tools+    -- c.f. Distribution.Simple.BuildToolDepends.desugarBuildTool+    -- and 'knownSuffixHandlers' in "Distribution.Client.Init.Heuristics"+            [ "alex"+            , "c2hs"+            , "cpphs"+            , "greencard"+            , "happy"+            , "hsc2hs"+            ]++instance IsDependency PkgconfigVersionRange PkgconfigDependency where+    type DepKey PkgconfigDependency = PkgconfigName++    depTypeName Proxy                      = "pkg-config"+    depKey (PkgconfigDependency pkgname _) = pkgname+    depKeyShow Proxy                       = prettyShow''+    depVerRg (PkgconfigDependency _ vr)    = vr+    reconstructDep                         = PkgconfigDependency++    intersectVr _ = PcIntersectVersionRanges+++-- The result tuple represents the 3 canonicalised dependency+-- (removed deps (old ranges), retained deps (old & new ranges), added deps (new ranges))+-- or expressed as set-operations: (A \ B, (A ∩ B), B \ A)+computeCanonDepChange :: forall vr d. IsDependency vr d => [d] -> [d] -> ([d],[(DepKey d,(vr,vr))],[d])+computeCanonDepChange depsA depsB+    = ( mapToDeps (a `Map.difference` b)+      , Map.toList $ Map.intersectionWith (,) a b+      , mapToDeps (b `Map.difference` a)+      )+  where+    a = depsToMapWithCanonVerRange depsA+    b = depsToMapWithCanonVerRange depsB++    depsToMapWithCanonVerRange+        = Map.fromListWith (flip $ intersectVr (Proxy :: Proxy d)) .+          map (\d -> (depKey d, depVerRg d))++    mapToDeps+        = map (\(pkgname, verrange) -> reconstructDep pkgname verrange) . Map.toList+++checkSetupBuildInfo :: Check (Maybe SetupBuildInfo)+checkSetupBuildInfo Nothing  Nothing = return ()+checkSetupBuildInfo (Just _) Nothing =+    fail "Cannot remove a 'custom-setup' section"++checkSetupBuildInfo Nothing (Just (SetupBuildInfo setupDependsA _internalA)) =+    logChange $ Change Normal+                       ("added a 'custom-setup' section with 'setup-depends'")+                       "[implicit]" (intercalate ", " (map prettyShow setupDependsA))++checkSetupBuildInfo (Just (SetupBuildInfo setupDependsA _internalA))+                    (Just (SetupBuildInfo setupDependsB _internalB)) = do+    forM_ removed $ \dep ->+      logChange $ Change Normal ("removed 'custom-setup' dependency on") (prettyShow dep) ""+    forM_ added $ \dep ->+      logChange $ Change Normal ("added 'custom-setup' dependency on") "" (prettyShow dep)+    forM_ changed $ \(pkgn, (verA, verB)) ->+        changesOk ("the 'custom-setup' dependency on " ++ prettyShow'' pkgn)+                  prettyShow verA verB+  where+    (removed, changed, added) =+      computeCanonDepChange setupDependsA setupDependsB++checkLibrary :: ComponentName -> Check Library+checkLibrary componentName+             (Library modulesA reexportedA requiredSigsA exposedSigsA+                      exposedA visibilityA buildInfoA)+             (Library modulesB reexportedB requiredSigsB exposedSigsB+                      exposedB visibilityB buildInfoB) = do+  checkSame "Cannot change the exposed modules" modulesA modulesB+  checkSame "Cannot change the re-exported modules" reexportedA reexportedB+  checkSame "Cannot change the required signatures" requiredSigsA requiredSigsB+  checkSame "Cannot change the exposed signatures"  exposedSigsA  exposedSigsB+  checkSame "Cannot change the package exposed status" exposedA exposedB+  checkSame "Cannot change the package visibility" visibilityA visibilityB+  checkBuildInfo componentName buildInfoA buildInfoB++checkForeignLib :: ComponentName -> Check ForeignLib+checkForeignLib componentName+             (ForeignLib nameA typeA optionsA buildInfoA verA verLinuxA modDefA)+             (ForeignLib nameB typeB optionsB buildInfoB verB verLinuxB modDefB) = do+  checkSame "Cannot change the foreign library name" nameA nameB+  checkSame "Cannot change the foreign library type" typeA typeB+  checkSame "Cannot change the foreign library options" optionsA optionsB+  checkSame "Cannot change the foreign library version" verA verB+  checkSame "Cannot change the foreign library version for Linux" verLinuxA verLinuxB+  checkSame "Cannot change the module definition files" modDefA modDefB+  checkBuildInfo componentName buildInfoA buildInfoB++checkExecutable :: ComponentName -> Check Executable+checkExecutable componentName+                (Executable _nameA pathA scopeA buildInfoA)+                (Executable _nameB pathB scopeB buildInfoB) = do+  checkSame "Cannot change build information" pathA pathB+  checkSame "Cannot change executable scope" scopeA scopeB+  checkBuildInfo componentName buildInfoA buildInfoB++checkTestSuite :: ComponentName -> Check TestSuite+checkTestSuite componentName+               (TestSuite _nameA interfaceA buildInfoA)+               (TestSuite _nameB interfaceB buildInfoB) = do+  checkSame "Cannot change test-suite type" interfaceA interfaceB+  checkBuildInfo componentName buildInfoA buildInfoB++checkBenchmark :: ComponentName -> Check Benchmark+checkBenchmark componentName+               (Benchmark _nameA interfaceA buildInfoA)+               (Benchmark _nameB interfaceB buildInfoB) = do+  checkSame "Cannot change benchmark type" interfaceA interfaceB+  checkBuildInfo componentName buildInfoA buildInfoB++checkBuildInfo :: ComponentName -> Check BuildInfo+checkBuildInfo componentName biA biB = do+    -- @other-extension@+    changesOkSet ("'other-extensions' in " ++ showComponentName componentName ++ " component")+              prettyShow+              (Set.fromList $ otherExtensions biA) (Set.fromList $ otherExtensions biB)++    -- @buildable@+    changesOk ("'buildable' in " ++ showComponentName componentName ++ " component") prettyShow+              (buildable biA) (buildable biB)++    -- @build-tool-depends@+    checkDependencies componentName+        (buildToolDepends biA)+        (buildToolDepends biB)++    -- @build-tools@+    checkDependencies componentName+        (buildTools biA)+        (buildTools biB)++    -- @pkgconfig-depends@+    checkDependencies componentName+        (pkgconfigDepends biA)+        (pkgconfigDepends biB)++    checkSame "Cannot change build information (just the dependency version constraints)"+              (biA { targetBuildDepends = [], otherExtensions = [], buildTools = [], buildToolDepends = [], pkgconfigDepends = [], buildable = True })+              (biB { targetBuildDepends = [], otherExtensions = [], buildTools = [], buildToolDepends = [], pkgconfigDepends = [], buildable = True })++changesOk' :: Eq a => Severity -> String -> (a -> String) -> Check a+changesOk' rel what render a b+  | a == b    = return ()+  | otherwise = logChange (Change rel what (render a) (render b))++changesOk :: Eq a => String -> (a -> String) -> Check a+changesOk = changesOk' Normal++changesOkList :: (String -> (a -> String) -> Check a)+              -> String -> (a -> String) -> Check [a]+changesOkList changesOkElem what render = go+  where+    go []     []     = return ()+    go (a:_)  []     = logChange (Change Normal ("removed " ++ what) (render a) "")+    go []     (b:_)  = logChange (Change Normal ("added "   ++ what) "" (render b))+    go (a:as) (b:bs) = changesOkElem what render a b >> go as bs++changesOkSet :: Ord a => String -> (a -> String) -> Check (Set.Set a)+changesOkSet what render old new = do+    unless (Set.null removed) $+        logChange (Change Normal ("removed " ++ what) (renderSet removed) "")+    unless (Set.null added) $+        logChange (Change Normal ("added " ++ what) "" (renderSet added))+    return ()+  where+    added   = new Set.\\ old+    removed = old Set.\\ new+    renderSet = intercalate ", " . map render . Set.toList+++-- | Single-quote-wrapping 'prettyShow'+prettyShow'' :: Pretty a => a -> String+prettyShow'' x = "'" ++ prettyShow x ++ "'"++checkSame :: Eq a => String -> Check a+checkSame msg x y | x == y    = return ()+                  | otherwise = fail msg++checkList :: String -> Check a -> Check [a]+checkList _   _         []     []     = return ()+checkList msg checkElem (x:xs) (y:ys) = checkElem x y+                                     >> checkList msg checkElem xs ys+checkList msg _         _      _      = fail msg++checkListAssoc :: Eq b => String -> Check a -> Check [(b,a)]+checkListAssoc _   _         [] [] = return ()+checkListAssoc msg checkElem ((kx,x):xs) ((ky,y):ys)+                       | kx == ky  = checkElem x y+                                  >> checkListAssoc msg checkElem xs ys+                       | otherwise = fail msg+checkListAssoc msg _         _  _  = fail msg++checkNotPresent :: String -> Check (Maybe String)+checkNotPresent msg _ (Just _) = fail msg+checkNotPresent _ _ Nothing = return ()++checkMaybe :: String -> Check a -> Check (Maybe a)+checkMaybe _   _     Nothing  Nothing  = return ()+checkMaybe _   check (Just x) (Just y) = check x y+checkMaybe msg _     _        _        = fail msg++ppTestedWith :: [(CompilerFlavor, VersionRange)] -> Doc+ppTestedWith = hsep . punctuate colon . map (uncurry ppPair)+  where+    ppPair compiler vr = text (prettyShow compiler) <+> text (prettyShow vr)++ppSourceRepo :: SourceRepo -> PrettyField ()+ppSourceRepo repo = PrettySection () "source-repository" [pretty kind] $+    prettyFieldGrammar cabalSpecLatest (sourceRepoFieldGrammar kind) repo+  where+    kind = repoKind repo++-- TODO: Verify that we don't need to worry about UTF8+-- | Insert or update \"x-revision:\" field+insertRevisionField :: Int -> ByteString -> ByteString+insertRevisionField rev+    | rev == 1  = LBS8.unlines . insertAfterVersion . LBS8.lines+    | otherwise = LBS8.unlines . replaceRevision    . LBS8.lines+  where+    replaceRevision [] = []+    replaceRevision (ln:lns)+      | isField (LBS8.pack "x-revision") ln+      = LBS8.pack ("x-revision: " ++ show rev) : lns++      | otherwise+      = ln : replaceRevision lns++    insertAfterVersion [] = []+    insertAfterVersion (ln:lns)+      | isField (LBS8.pack "version") ln+      = ln : LBS8.pack ("x-revision: " ++ show rev) : lns++      | otherwise+      = ln : insertAfterVersion lns++    isField nm ln+      | LBS8.isPrefixOf nm (LBS8.map Char.toLower ln)+      , let (_, t) = LBS8.span (\c -> c == ' ' || c == '\t')+                             (LBS8.drop (LBS8.length nm) ln)+      , Just (':',_) <- LBS8.uncons t+                  = True+      | otherwise = False
+ src/CabalEdit.hs view
@@ -0,0 +1,98 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}++-- |+-- Copyright   :  Herbert Valerio Riedel+-- SPDX-License-Identifier: GPL-3.0-or-later+module CabalEdit+    ( cabalEditXRev+    , cabalSplitAtField+    , PkgRev+    ) where++import           Data.ByteString                        (ByteString)+import qualified Data.ByteString.Char8                  as BS8+#if !MIN_VERSION_base(4,11,0)+import           Data.Semigroup                         ((<>))+#endif+import qualified Distribution.Fields.Field              as C+import qualified Distribution.Fields.Parser             as C+import qualified Distribution.Parsec.Position           as C (Position(Position))++type PkgRev  = Word++-- | Insert or replace existing "x-revision" line+--+-- __NOTE__: This uses 'cabalSplitAtField' and therefore currently+-- supports only simplified (i.e. without use of @{@ @}@ layout+-- tokens) Cabal file grammar+cabalEditXRev :: PkgRev -> ByteString -> ByteString+cabalEditXRev xrev oldcab = pre1 <> mid1 <> xrevLine <> post1+  where+    (pre0,_,post0)    = either (error . show) id $ cabalSplitAtField "x-revision" oldcab+    (pre1,mid1,post1) = either (error . show) id $ cabalSplitAtField "version" (pre0 <> post0)++    ls = BS8.lines oldcab++    xrevLine = "x-revision: " <> BS8.pack (show xrev) <> if isCRLF then "\r\n" else "\n"++    -- simple heuristic+    isCRLF = case ls of+        []     -> False+        ("":_) -> False+        (l1:_) -> BS8.last l1 == '\r'+++-- | Split a cabal file into three fragments: before the (first+-- occurence of a field), the field, and the rest after the field.+--+-- This should, in spirit, follow the invariant (assuming there are no parsing failures):+--+-- > cabalSplitAtField someField (pre <> mid <> post) == Right (pre, mid, post)+--+-- If field not found, the middle and trailing fragment will be empty 'ByteString's+--+-- This operation is quite universal; it can be used to remove fields,+-- or insert content before a field or after a field etc+--+-- __NOTE__: This doesn't properly handle layout-mode (i.e. as controlled by the @{@ and @}@ tokens) yet+cabalSplitAtField :: ByteString -- ^ fieldname+                  -> ByteString -- ^ @.cabal@ description+                  -> Either String (ByteString,ByteString,ByteString) -- ^ pre, mid, post+cabalSplitAtField fname cab0 = do+    fields <- either (Left . show) Right $ C.readFields cab0++    case [ (npos, vals) | C.Field (C.Name (C.Position npos _) n) vals <- fields, n == fname ] of+      [] -> pure (cab0, BS8.empty, BS8.empty)+      (npos, vals):_ -> case vals of+                          [] -> pure $! go (npos-1) 1++                          _ -> let C.FieldLine (C.Position pos2 _) _ = last vals+                               in  pure $! go (npos-1) (1+pos2-npos)++  where+    cab0lines = BS8.lines cab0++    -- TODO: detect '{'s in `mid` and fixup k to include the closing '}'+    --+    -- NB: it's not enough to simply look at the next field's line+    -- number, as we don't want 'mid' to include trailing comments+    -- which conventionally belong to the next fields; i.e. in case of+    -- something like+    --+    --   name: foo+    --   version: 1.2.3+    --   x-revision: {+    --   42+    --   }+    --   -- for reasons this must remain Custom instead of Simple+    --   build-type: Custm+    --   synopsis: ...+    --+    -- We want the middle of 'cabalSplitAtField "x-revision"' to stop+    -- right after the '}' and *not* include the comment+    --+    go j k = (BS8.unlines pre, BS8.unlines mid, BS8.unlines post)+      where+        (pre, midpost) = splitAt j cab0lines+        (mid,post)     = splitAt k midpost
+ src/IndexShaSum.hs view
@@ -0,0 +1,161 @@+{-# LANGUAGE BangPatterns       #-}+{-# LANGUAGE CPP                #-}+{-# LANGUAGE OverloadedStrings  #-}+{-# LANGUAGE RecordWildCards    #-}+{-# LANGUAGE StandaloneDeriving #-}++-- |+-- Module      :  IndexShaSum+-- Copyright   :  Herbert Valerio Riedel+-- SPDX-License-Identifier: GPL-3.0-or-later+--+module IndexShaSum (run, IndexShaSumOptions(..)) where++import qualified Codec.Archive.Tar      as Tar+import qualified Codec.Compression.GZip as GZip+import           Control.Monad+import qualified Data.Aeson             as J+import qualified Data.Aeson.Types       as J+import           Data.ByteString        (ByteString)+import qualified Data.ByteString        as BS+import qualified Data.ByteString.Lazy   as BSL+import qualified Data.ByteString.Short  as BSS+import           Data.Maybe+import           Data.Set               (Set)+import qualified Data.Set               as Set+import           Data.String+import           Data.Text              (Text)+import           Data.Text.Encoding     as T+import           System.FilePath++#if !MIN_VERSION_base(4,11,0)+import           Data.Semigroup         ((<>))+#endif++#if MIN_VERSION_aeson(2,0,0)+import qualified Data.Aeson.Key         as Key+import qualified Data.Aeson.KeyMap      as KeyMap+type Key = J.Key++keyToText :: Key -> Text+keyToText = Key.toText+#else+import qualified Data.HashMap.Strict    as KeyMap+type Key = Text++keyToText :: Key -> Text+keyToText = id+#endif++data IndexShaSumOptions = IndexShaSumOptions+    { optFlatStyle   :: Bool+    , optISSIndexTar :: FilePath+    , optBaseDir     :: Maybe FilePath+    } deriving Show++type SrcTarName   = BSS.ShortByteString -- with .tar.gz suffix+type SrcTarSha256 = BSS.ShortByteString -- base16++run :: IndexShaSumOptions -> IO ()+run (IndexShaSumOptions {..}) = do+    idx <- readTarEntries optISSIndexTar+    forM_ (collect idx) (uncurry printSumLine)+  where+    printSumLine fn sh256 = BS.putStr line+      where+        line = mconcat [BSS.fromShort (fixupSum sh256), "  " , bdirpfx, BSS.fromShort fn', "\n"]+        bdirpfx = maybe "" fromString optBaseDir+        fn' = if optFlatStyle then fn else unFlat fn++    -- | Missing checksums are denoted by a 0-checksum+    fixupSum x+       | BSS.null x  = BSS.toShort (BS.replicate 64 48)+       | otherwise   = x++    collect :: [Tar.Entry] -> [(SrcTarName,SrcTarSha256)]+    collect = go mempty mempty++    go :: Set SrcTarName -> Set SrcTarName -> [Tar.Entry] -> [(SrcTarName,SrcTarSha256)]+    go !seen1 !seen2 []+      | missingCabs <- Set.difference seen1 seen2+      , not (Set.null missingCabs) = error "missing .cabal file(s)"+      | otherwise -- append files with missing checksum+      = [ (missingSum, "") | missingSum <- Set.toList (Set.difference seen2 seen1) ]+    go !seen1 !seen2 (e:es)+      | takeExtension fn == ".cabal"+      , [pn,pv,_cn] <- splitDirectories fn+      = let fn' = fromString (pn ++ "-" ++ pv ++ ".tar.gz")+        in go seen1 (Set.insert fn' seen2) es+      | takeFileName fn == "package.json"+      , Tar.NormalFile bs _sz <- Tar.entryContent e+      = let (fn',cksum) = fromMaybe undefined (decodePkgJsonFile bs)+        in if Set.member fn' seen1+           then go seen1 seen2 es+           else ((fn',cksum) : go (Set.insert fn' seen1) seen2 es)+      | otherwise = go seen1 seen2 es+      where+        fn = Tar.entryPath e++-- | Convert to non-flat layout (i.e. @<name>/<ver>/<name>-<ver>.tar.gz@)+unFlat :: SrcTarName -> SrcTarName+unFlat fn0 = BSS.toShort $ mconcat [pn <> "/" <> pv <> "/" <> fn0']+  where+    fn0' = BSS.fromShort fn0++    Just base = stripSuffixBS ".tar.gz" fn0'++    (pn_, pv) = BS.spanEnd (\c -> (c >= 0x30 && c <= 0x3a) || c == 0x2e) base+    Just (pn, 0x2d) = BS.unsnoc pn_++-- | Read tarball lazily (and possibly decompress)+readTarEntries :: FilePath -> IO [Tar.Entry]+readTarEntries idxtar = do+    es <- case takeExtension idxtar of+            ".gz"  -> Tar.read . GZip.decompress <$> BSL.readFile idxtar+            ".tar" -> Tar.read                   <$> BSL.readFile idxtar+            ext    -> error ("unknown extension " ++ show ext)++    return (Tar.foldEntries (:) [] (\err -> error ("readTarEntries " ++ show err)) es)++-- | Decode and extract source-tarball filename and sha256 checksum from TUF @package.json@+decodePkgJsonFile :: BSL.ByteString -> Maybe (SrcTarName, SrcTarSha256)+decodePkgJsonFile bs = do+    metainfo <- J.decode' bs+    [(fn,s256)] <- packagejson2sha metainfo++    return $! strictPair (BSS.toShort $ normaliseFn fn) (BSS.toShort s256)+  where+    normaliseFn fn = fromMaybe fn $ stripPrefixBS "<repo>/package/" fn++    packagejson2sha :: J.Value -> Maybe [(ByteString, ByteString)]+    packagejson2sha = J.parseMaybe go1+      where+        go1 :: J.Value -> J.Parser [(ByteString, ByteString)]+        go1 = J.withObject "PackageJson" $ \o -> do+            signed   <- o      J..: "signed"+            targets  <- signed J..: "targets"+            J.withObject "PackageJson.signed.targets" go2 targets++        go2 :: J.Object -> J.Parser [(ByteString, ByteString)]+        go2 m = forM (KeyMap.toList m) $ \(k,v) -> do+            J.withObject ".targets{}" (go3 k) v++        go3 :: Key -> J.Object -> J.Parser (ByteString, ByteString)+        go3 k o = do+            hashes <- o J..: "hashes"+            sh256 <- hashes J..: "sha256"+            return (T.encodeUtf8 (keyToText k), T.encodeUtf8 sh256)++strictPair :: a -> b -> (a,b)+strictPair !a !b = (a,b)++stripPrefixBS :: ByteString -> ByteString -> Maybe ByteString+stripPrefixBS pfx b+  | BS.isPrefixOf pfx b = Just $ BS.drop (BS.length pfx) b+  | otherwise           = Nothing+++stripSuffixBS :: ByteString -> ByteString -> Maybe ByteString+stripSuffixBS sfx b+  | BS.isSuffixOf sfx b = Just $ BS.take (BS.length b - BS.length sfx) b+  | otherwise           = Nothing
+ src/Main.hs view
@@ -0,0 +1,958 @@+{-# LANGUAGE BangPatterns      #-}+{-# LANGUAGE CPP               #-}+{-# LANGUAGE LambdaCase        #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards   #-}+{-# LANGUAGE TemplateHaskell   #-}+{-# LANGUAGE ViewPatterns      #-}++-- |+-- Module      :  Main+-- Copyright   :  Herbert Valerio Riedel, Andreas Abel+-- SPDX-License-Identifier: GPL-3.0-or-later+--+module Main where++import qualified Data.Aeson                           as J+import qualified Data.Aeson.Types                     as J+import qualified Data.ByteString.Builder              as Builder+import           Control.DeepSeq+import           Control.Exception+import           Control.Monad+import           Control.Monad.State.Strict+import           Data.Bits+import           Data.ByteString                        (ByteString)+import qualified Data.ByteString                        as BS+import qualified Data.ByteString.Char8                  as BS8+import qualified Data.ByteString.Lazy                   as BSL+import qualified Data.ByteString.Search                 as BSS+import           Data.Char                              (isSpace)+import           Data.Foldable                          (toList)+import qualified Data.List                              as List+import           Data.Maybe+import           Data.Time.Clock.POSIX                  (getPOSIXTime)+#if !MIN_VERSION_base(4,11,0)+import           Data.Semigroup                         (Semigroup(..))+#endif++import qualified Distribution.Package                   as C+import qualified Distribution.PackageDescription        as C+import qualified Distribution.PackageDescription.Parsec as C+import qualified Distribution.Parsec                    as C+import qualified Distribution.Fields                    as C+import qualified Distribution.Fields.Field              as C (fieldAnn)+import qualified Distribution.Pretty                    as C+import qualified Distribution.Verbosity                 as C+import qualified Distribution.Version                   as C+import qualified Distribution.Text                      as C++import           Lens.Micro+import           Lens.Micro.Mtl+import           Lens.Micro.TH++import           Network.Http.Client+import           Network.NetRc+import           Numeric.Natural                        (Natural)+import           Options.Applicative                    as OA+import           System.Directory+import           System.Environment                     (lookupEnv)+import           System.Exit                            (ExitCode (..), exitFailure)+import           System.FilePath+import           System.IO.Error                        (tryIOError, isDoesNotExistError)+import qualified System.IO.Streams                      as Streams+import           System.Process.ByteString              (readProcessWithExitCode)+import           Text.HTML.TagSoup+import           Text.Printf                            (printf)+import qualified Paths_hackage_cli+import qualified Data.Version as V++import qualified Distribution.Types.BuildInfo.Lens                 as LC+import qualified Distribution.Types.GenericPackageDescription.Lens as LC++-- import Cabal++import Distribution.Server.Util.CabalRevisions+import IndexShaSum+import CabalEdit++type PkgName = ByteString+type PkgVer  = ByteString++showVersion :: C.Version -> String+showVersion = C.display++pkgVerToVersion :: PkgVer -> C.Version+pkgVerToVersion = fromMaybe (error "invalid version") . C.simpleParse . BS8.unpack++pkgVerInRange :: PkgVer -> C.VersionRange -> Bool+pkgVerInRange v vr = pkgVerToVersion v `C.withinRange` vr++type HIO = StateT HConn IO++data HConn = HConn+    { _hcMkConn :: IO Connection+    , _hcConn   :: Maybe Connection+    , _hcReqCnt :: Natural -- ^ requests submitted on current 'Connection'+    , _hcRspCnt :: Natural -- ^ responses read from current 'Connection'+    }++makeLenses ''HConn++-- | Requests that can be issued in current connection before exhausting the 50-req/conn server limit+hcReqLeft :: SimpleGetter HConn Natural -- (Natural -> f Natural) -> HConn -> f HConn+hcReqLeft = hcReqCnt . to f+  where+    f n | n > lim   = 0+        | otherwise = lim - n+    lim = 50++setUA :: RequestBuilder ()+setUA = setHeader "User-Agent" uaStr+  where+    uaStr = "hackage-cli/" <> BS8.pack (V.showVersion Paths_hackage_cli.version)++hackageSendGET :: ByteString -> ByteString -> HIO ()+hackageSendGET p a = do+    q1 <- liftIO $ buildRequest $ do+        http GET p+        setUA+        setAccept a++    lft <- use hcReqLeft+    unless (lft > 0) $+        fail "hackageSendGET: request budget exhausted for current connection"++    c <- openHConn+    liftIO $ sendRequest c q1 emptyBody+    hcReqCnt += 1++hackagePutTgz :: ByteString -> ByteString -> HIO ByteString+hackagePutTgz p tgz = do+    q1 <- liftIO $ buildRequest $ do+        http PUT p+        setUA+        -- setAccept "application/json" -- wishful thinking+        setContentType "application/x-tar"+        -- setContentEncoding "gzip"+        setContentLength (fromIntegral $ BS.length tgz)++    lft <- use hcReqLeft+    unless (lft > 0) $+        fail "hackagePutTgz: request budget exhausted for current connection"++    c <- openHConn+    liftIO $ sendRequest c q1 (bsBody tgz)+    resp <- liftIO $ try (receiveResponse c concatHandler')+    closeHConn+    hcReqCnt += 1++    case resp of+        Right bs -> -- do+            -- liftIO $ BS.writeFile "raw.out" bs+            return bs++        Left e@HttpClientError {} -> -- do+            return (BS8.pack $ show e)++hackageRecvResp :: HIO ByteString+hackageRecvResp = do+    c <- openHConn+    reqCnt <- use hcReqCnt+    rspCnt <- use hcRspCnt+    unless (reqCnt > rspCnt) $+        fail "hackageRecvResp: not response available to receive"++    resp <- liftIO $ receiveResponse c concatHandler'+    hcRspCnt += 1++    return resp++-- | Whether to operate in review/test mode or publish the revision for real+data DryWetRun = DryRun -- ^ review/test mode+               | WetRun -- ^ publish++hackagePostCabal :: (ByteString,ByteString) -> (PkgName,PkgVer) -> ByteString -> DryWetRun -> HIO ByteString+hackagePostCabal cred (pkgn,pkgv) rawcab dry = do+    when (boundary `BS.isInfixOf` rawcab) $ fail "WTF... cabal file contains boundary-pattern"++    q1 <- liftIO $ buildRequest $ do+        http POST urlpath+        setUA+        uncurry setAuthorizationBasic cred+        setAccept "application/json" -- wishful thinking+        setContentType ("multipart/form-data; boundary="<>boundary) -- RFC2388+        setContentLength bodyLen++    c <- reOpenHConn++    liftIO $ sendRequest c q1 (bsBody body)++    resp <- liftIO $ try (receiveResponse c concatHandler')+    closeHConn++    case resp of+        Right bs -> -- do+            -- liftIO $ BS.writeFile "raw.out" bs+            return (BS8.unlines [ h2 <> ":\n" <> renderTags ts | (h2, ts) <- scrape200 bs ])++        Left e@HttpClientError {} -> -- do+            -- Hackage currently timeouts w/ 503 guru meditation errors,+            -- which usually means that the transaction has succeeded+            -- liftIO $ BS.writeFile "raw.out" bs+            return (BS8.pack $ show e)+  where+    urlpath = mconcat [ "/package/", pkgn, "-", pkgv, "/", pkgn, ".cabal/edit" ]++    isDry DryRun = True+    isDry WetRun = False++    body = Builder.toLazyByteString $ multiPartBuilder boundary+           [ ("cabalfile",[],[],rawcab)+           , if isDry dry+             then ("review", [],[],"Review changes")+             else ("publish",[],[],"Publish new revision")+           ]++    bodyLen = fromIntegral $ BSL.length body++    boundary = "4d5bb1565a084d78868ff0178bdf4f61"++    -- scrape200 :: ByteString -> (Bool, h2parts)+    scrape200 html = h2parts+      where+        tags = parseTags (html :: ByteString)++        h2parts = [ (t,map cleanText $ takeWhile (/= TagClose "form") xs)+                  | (TagOpen "h2" _: TagText t: TagClose "h2": xs) <- partitions (== TagOpen "h2" []) tags+                  , t /= "Advice on adjusting version constraints" ]++        cleanText (TagText t)+          | t' == "", '\n' `BS8.elem` t = TagText "\n"+          | otherwise               = TagText t+          where+            t' = fst . BS8.spanEnd (=='\n') . BS8.dropWhile (=='\n') $ t+        cleanText x = x++class ToBuilder a where+  toBuilder :: a -> Builder.Builder++instance ToBuilder ByteString where+  toBuilder = Builder.byteString++instance ToBuilder BSL.ByteString where+  toBuilder = Builder.lazyByteString++bsBody :: ToBuilder a => a -> Streams.OutputStream Builder.Builder -> IO ()+bsBody bs = Streams.write (Just (toBuilder bs))++-- | Upload a candidate to Hackage+--+-- This is a bit overkill, as one could easily just use @curl(1)@ for this:+--+-- > curl --form package=@"$PKGID".tar.gz -u "${CREDS}" https://hackage.haskell.org/packages/candidates/+--+hackagePushCandidate :: (ByteString,ByteString) -> (FilePath,ByteString) -> HIO ByteString+hackagePushCandidate cred (tarname,rawtarball) = do+    when (boundary `BS.isInfixOf` rawtarball) $ fail "WTF... tarball contains boundary-pattern"++    q1 <- liftIO $ buildRequest $ do+        http POST urlpath+        setUA+        uncurry setAuthorizationBasic cred+        setAccept "application/json" -- wishful thinking+        setContentType ("multipart/form-data; boundary="<>boundary) -- RFC2388+        setContentLength bodyLen++    c <- reOpenHConn++    liftIO $ sendRequest c q1 (bsBody body)++    resp <- liftIO $ try (receiveResponse c (\r is -> (,) r <$> concatHandler r is))+    closeHConn++    case resp of+        Right (rc,bs) -> do+            return (BS8.pack (show rc) <> bs)+        Left (HttpClientError code bs) -> return (BS8.pack ("code=" <> show code <> "\n") <> bs)+            -- Hackage currently timeouts w/ 503 guru meditation errors,+            -- which usually means that the transaction has succeeded+  where+    urlpath = "/packages/candidates/"++    body = Builder.toLazyByteString $+           multiPartBuilder boundary [ ("package", [("filename", BS8.pack tarname)]+                                     , ["Content-Type: application/gzip"], rawtarball)]+    bodyLen = fromIntegral $ BSL.length body++    boundary = "4d5bb1565a084d78868ff0178bdf4f61"++-- | Simplified RFC2388 multipart/form-data formatter+--+-- TODO: make a streaming-variant+multiPartBuilder :: ByteString -> [(ByteString,[(ByteString,ByteString)],[ByteString],ByteString)] -> Builder.Builder+multiPartBuilder boundary mparts = mconcat $ concatMap mkPart mparts ++ trailer+  where+    mkPart (name, xprops, xhdrs, payload)+        = [ dash, bs boundary, crlf+          , bs"Content-Disposition: form-data; name=\"", bs name, bs"\""+          ] +++          concat [ [bs "; ", bs k, bs"=\"", bs v, bs"\"" ] | (k,v) <- xprops ] ++ [ crlf ] +++          concat [ [bs h, crlf] | h <- xhdrs ] +++          [ crlf+          , bs payload, crlf+          ]++    trailer = [ dash, bs boundary, dash, crlf ]++    crlf = bs"\r\n"+    dash = bs"--"+    bs = Builder.byteString++fetchVersions :: PkgName -> HIO [(PkgVer,PkgVerStatus)]+fetchVersions pkgn = do+    hackageSendGET ("/package/" <> pkgn <> "/preferred") "application/json"+    resp <- hackageRecvResp+    case decodeVersions resp of+        Right xs -> pure xs+        Left err -> fail err++fetchCabalFile :: PkgName -> PkgVer -> HIO ByteString+fetchCabalFile pkgn pkgv = do+    hackageSendGET urlpath "text/plain"+    hackageRecvResp+  where+    urlpath = mconcat ["/package/", pkgn, "-", pkgv, "/", pkgn, ".cabal"]++fetchCabalFiles :: PkgName -> [PkgVer] -> HIO [(PkgVer,ByteString)]+fetchCabalFiles pkgn pkgvs0 = do+    -- HTTP pipelining+    tmp <- go [] pkgvs0+    return (concat . reverse $ tmp)+  where+    go acc [] = pure acc+    go acc vs0 = do+        (_,lft) <- getHConn+        let (vs,vs') = nsplitAt lft vs0+        when (null vs) $ fail "fetchCabalFiles: the impossible happened"++        -- HTTP-pipeline requests; compensates a bit for SSL-induced latency+        mcabs <- forM (mkPipeline 4 vs) $ \case+            Left pkgv -> do -- request+                let urlpath = mconcat ["/package/", pkgn, "-", pkgv, "/", pkgn, ".cabal"]+                -- liftIO $ putStrLn $ show urlpath+                hackageSendGET urlpath "text/plain"+                return Nothing++            Right pkgv -> do -- response+                -- liftIO $ putStrLn ("read " ++ show pkgv)+                resp <- hackageRecvResp+                return $ Just (pkgv, resp)++        go (catMaybes mcabs : acc) vs'++-- Left means request; Right means receive+mkPipeline :: Natural -> [a] -> [Either a a]+mkPipeline maxQ vs+  | not postCond = error "mkPipeline: internal error" -- paranoia+  | otherwise    = concat [ map Left rqs1+                          , concat [ [Left v1, Right v2] | (v1,v2) <- zip rqs2 res2 ]+                          , map Right res3+                          ]+  where+    (rqs1,rqs2) = nsplitAt n vs+    (res2,res3) = nsplitAt m vs++    postCond = sameLen rqs2 res2 && sameLen rqs1 res3++    l = nlength vs+    n = min l maxQ+    m = l-n++    sameLen [] []         = True+    sameLen (_:xs) (_:ys) = sameLen xs ys+    sameLen [] (_:_)      = False+    sameLen (_:_) []      = False++fetchAllCabalFiles :: PkgName -> C.VersionRange -> HIO [(PkgVer,Maybe ByteString)]+fetchAllCabalFiles pkgn vrange = do+    vs <- fetchVersions pkgn+    liftIO $ putStrLn ("Found " ++ show (length vs) ++ " package versions for " ++ show pkgn ++ ", downloading now...")++    let (wanted,unwanted) = List.partition (`pkgVerInRange` vrange) (map fst vs)++    fetched <- map (fmap Just) <$> fetchCabalFiles pkgn wanted+    pure (List.sortOn (pkgVerToVersion . fst) (fetched ++ [ (v,Nothing) | v <- unwanted ]))++data PkgVerStatus = Normal | UnPreferred | Deprecated deriving (Eq,Show)+instance NFData PkgVerStatus where rnf !_ = ()++decodeVersions :: ByteString -> Either String [(PkgVer,PkgVerStatus)]+decodeVersions bs = do+    obj <- J.eitherDecode' (BSL.fromStrict bs)+    flip J.parseEither obj $ \o -> do+        normal <- o J..:? "normal-version"+        unpreferred <- o J..:? "unpreferred-version"+        deprecated <- o J..:? "deprecated-version"+        pure $ toPairs Normal normal ++ toPairs UnPreferred unpreferred ++ toPairs Deprecated deprecated+  where+    toPairs :: PkgVerStatus -> Maybe [String] -> [(PkgVer,PkgVerStatus)]+    toPairs s (Just vs) = [(BS8.pack v, s) | v <- vs]+    toPairs _ _ = []++closeHConn :: HIO ()+closeHConn = do+    mhc <- use hcConn+    forM_ mhc $ \hc -> do+        liftIO $ closeConnection hc+        hcConn   .= Nothing++        reqCnt <- use hcReqCnt+        rspCnt <- use hcRspCnt+        unless (reqCnt == rspCnt) $+            liftIO $ putStrLn $ concat ["warning: req-cnt=", show reqCnt, " rsp-cnt=", show rspCnt]++        hcReqCnt .= 0+        hcRspCnt .= 0++openHConn :: HIO Connection+openHConn = use hcConn >>= \case+    Just c -> return c+    Nothing -> do+        mkConn <- use hcMkConn+        c <- liftIO mkConn+        hcConn   .= Just c+        hcReqCnt .= 0 -- redundant+        hcRspCnt .= 0 -- redundant+        return c++reOpenHConn :: HIO Connection+reOpenHConn = closeHConn >> openHConn++getHConn :: HIO (Connection,Natural)+getHConn = do+    lft <- use hcReqLeft+    c <- if lft > 0 then openHConn else reOpenHConn+    (,) c <$> use hcReqLeft++nlength :: [a] -> Natural+nlength = fromIntegral . length++nsplitAt :: Natural -> [a] -> ([a],[a])+nsplitAt n = splitAt i+  where+    i = fromMaybe (error "nsplitAt: overflow") $ toIntegralSized n++findLibrarySection :: [C.Field C.Position] -> Maybe (Int, Int)+findLibrarySection [] = Nothing+findLibrarySection (C.Section (C.Name (C.Position row _) "library") [] fs : _) =+    Just (row, findIndent fs)+  where+    findIndent [] = 4+    findIndent (f : _) = case C.fieldAnn f of+        C.Position _ col -> pred col+findLibrarySection (_ : fs) = findLibrarySection fs++----------------------------------------------------------------------------+-- CLI Interface++data Options = Options+  { optVerbose :: !Bool+  , optHost    :: !Hostname+  , optCommand :: !Command+  } deriving Show++data PullCOptions = PullCOptions+  { optPlCPkgName :: !PkgName+  , optPlCIncrRev :: !Bool+  , optPlCForce   :: !Bool+  , optPlCPkgVers :: Maybe C.VersionRange+  } deriving Show++data SyncCOptions = SyncCOptions+  { optSyCFile    :: FilePath+  , optSyCIncrRev :: !Bool+  , optSyCForce   :: !Bool+  } deriving Show++data ListCOptions = ListCOptions+  { optLCPkgName :: !PkgName+  , optNoAnn     :: !Bool+  , optRevUrls   :: !Bool+  } deriving Show++data PushCOptions = PushCOptions+  { optPsCIncrRev :: !Bool+  , optPsCPublish :: !Bool+  , optPsCFiles   :: [FilePath]+  } deriving Show++data PushPCOptions = PushPCOptions+  { optPPCFiles :: [FilePath]+  } deriving Show++data CheckROptions = CheckROptions+  { optCRNew  :: FilePath+  , optCROrig :: FilePath+  } deriving Show++data AddBoundOptions = AddBoundOptions+  { optABPackageName  :: C.PackageName+  , optABVersionRange :: C.VersionRange+  , optForce          :: Bool              -- ^ Disable the check whether bound is subsumed by existing constraints.+  , optABMessage      :: [String]+  , optABFiles        :: [FilePath]+  } deriving Show++data Command+    = ListCabal !ListCOptions+    | PullCabal !PullCOptions+    | PushCabal !PushCOptions+    | SyncCabal !SyncCOptions+    | PushCandidate !PushPCOptions+    | CheckRevision !CheckROptions+    | IndexShaSum   !IndexShaSumOptions+    | AddBound !AddBoundOptions+    deriving Show++optionsParserInfo :: ParserInfo Options+optionsParserInfo+    = info (helper <*> verOption <*> oParser)+           (fullDesc+            <> header "hackage-cli - CLI tool for Hackage"+            <> footer footerStr)+  where+    footerStr = unwords+        [ "Each command has a sub-`--help` text. Hackage credentials are expected to be"+        , "stored in an `${HOME}/.netrc`-entry (or `.netrc.gpg`) for the respective Hackage hostname."+        , "E.g. \"machine hackage.haskell.org login MyUserName password TrustNo\"."+        , "All interactions with Hackage occur TLS-encrypted via the HTTPS protocol."+        ]++    bstr = BS8.pack <$> str++    prsc :: C.Parsec s => OA.ReadM s+    prsc = OA.eitherReader C.eitherParsec++    vrange = do+        s <- str+        case C.simpleParse s of+            Nothing -> fail "invalid version range"+            Just vr -> pure vr++    listcoParser = ListCabal <$>+        (ListCOptions <$> OA.argument bstr (metavar "PKGNAME" <> action "file")+                      <*> switch (long "no-annotations" <> help "don't add preferred-versions annotations")+                      <*> switch (long "rev-urls" <> help "list revision URLs"))++    pullcoParser = PullCabal <$>+        (PullCOptions <$> OA.argument bstr (metavar "PKGNAME" <> action "file")+                      <*> switch (long "incr-rev" <> help "increment x-revision field")+                      <*> switch (long "force" <> help "force overwriting existing files")+                      <*> optional (OA.argument vrange (metavar "VERSION-CONSTRAINT")))++    synccoParser = SyncCabal <$>+        (SyncCOptions <$> OA.argument str (metavar "CABALFILE" <> action "file")+                      <*> switch (long "incr-rev" <> help "increment x-revision field")+                      <*> switch (long "force" <> help "force overwriting local file with older revision"))++    pushcoParser = PushCabal <$> (PushCOptions+                             <$> switch (long "incr-rev" <> help "increment x-revision field")+                             <*> switch (long "publish"  <> help "publish revision (review-mode)")+                             <*> some (OA.argument str (metavar "CABALFILES..." <> action "file")))++    pushpcoParser = PushCandidate <$> (PushPCOptions <$> some (OA.argument str (metavar "TARBALLS..." <> action "file")))++    checkrevParsser = CheckRevision <$> (CheckROptions <$> OA.argument str (metavar "NEWCABAL" <> action "file")+                                                       <*> OA.argument str (metavar "OLDCABAL" <> action "file"))+++    indexssParser = IndexShaSum <$> (IndexShaSumOptions <$> switch (long "flat" <> help "flat filesystem layout (used by mirrors)")+                                                        <*> OA.argument str (metavar "INDEX-TAR" <> action "file")+                                                        <*> optional (OA.argument str (metavar "BASEDIR" <> action "directory")))++    addboundParser = AddBound <$> (AddBoundOptions <$> OA.argument prsc (metavar "DEPENDENCY")+                                                   <*> OA.argument prsc (metavar "VERSIONRANGE")+                                                   <*> OA.switch (long "force" <> help "Add bound even if it is already subsumed by existing constraints.")+                                                   <*> many (OA.option str (OA.short 'm' <> OA.long "message" <> metavar "MSG" <> help "Use given MSG as a comment. If multiple -m options are given, their values are concatenated with 'unlines'."))+                                                   <*> some (OA.argument str (metavar "CABALFILES..." <> action "file")))++    oParser+        = Options <$> switch (long "verbose" <> help "Enable verbose output.")+                  <*> option bstr (long "hostname"  <> metavar "HOSTNAME" <> value "hackage.haskell.org"+                                   <> help "Hackage hostname" <> showDefault)+                  <*> subparser (mconcat [ command "pull-cabal" (info (helper <*> pullcoParser)+                                                   (progDesc "Download .cabal files for a package."))+                                         , command "push-cabal" (info (helper <*> pushcoParser)+                                                   (progDesc "Upload revised .cabal files."))+                                         , command "sync-cabal" (info (helper <*> synccoParser)+                                                   (progDesc "Update/sync local .cabal file with latest revision on Hackage."))+                                         , command "push-candidate" (info (helper <*> pushpcoParser)+                                                   (progDesc "Upload package candidate(s)."))+                                         , command "list-versions" (info (helper <*> listcoParser)+                                                   (progDesc "List versions for a package."))+                                         , command "check-revision" (info (helper <*> checkrevParsser)+                                                   (progDesc "Validate revision."))+                                         , command "index-sha256sum" (info (helper <*> indexssParser)+                                                   (progDesc "Generate sha256sum-format file."))+                                         , command "add-bound" (info (helper <*> addboundParser)+                                                   (progDesc "Add bound to the library section of a package, unless the bound is redundant. The .cabal file is edited in place."))+                                         ])++    verOption = infoOption verMsg (long "version" <> help "Output version information and exit.")+      where+        verMsg = "hackage-cli " <> V.showVersion Paths_hackage_cli.version++----------------------------------------------------------------------------++main :: IO ()+main = do+    opts <- execParser optionsParserInfo+    mainWithOptions opts++mainWithOptions :: Options -> IO ()+mainWithOptions Options {..} = do+   case optCommand of+       PullCabal (PullCOptions {..}) -> do+           let pkgn = optPlCPkgName++           cs <- runHConn (fetchAllCabalFiles pkgn (fromMaybe C.anyVersion optPlCPkgVers))++           forM_ cs $ \(v,mraw) -> case mraw of+             Nothing -> putStrLn ("skipped excluded " ++ BS8.unpack v)+             Just raw0 -> do+               let fn = BS8.unpack $ pkgn <> "-" <> v <> ".cabal"++               let raw | optPlCIncrRev = incrXrev raw0+                       | otherwise     = raw0++               doesFileExist fn >>= \case+                   False -> do+                       BS.writeFile fn raw+                       putStrLn ("saved " ++ fn ++ " (" ++ show (BS.length raw) ++ " bytes)")+                   True ->+                       if optPlCForce+                       then do+                         BS.writeFile fn raw+                         putStrLn ("overwritten " ++ fn ++ " (" ++ show (BS.length raw) ++ " bytes)")+                       else+                         putStrLn ("WARNING: skipped existing " ++ fn ++ " (use --force to overwrite)")++           return ()++       SyncCabal (SyncCOptions {..}) -> do+           (pkgn,pkgv,xrev) <- pkgDescToPkgIdXrev <$> C.readGenericPackageDescription C.deafening optSyCFile+           cab0 <- BS.readFile optSyCFile++           BS8.putStrLn $ mconcat [ "local :  "+                                  , pkgn, "-", pkgv, "-r", BS8.pack (show xrev)+                                  , "   ('", BS8.pack optSyCFile, "')"+                                  ]++           cab' <- runHConn (fetchCabalFile pkgn pkgv)++           let (pkgn',pkgv',xrev') = pkgDescToPkgIdXrev $ parseGenericPackageDescription' cab'++           BS8.putStrLn $ mconcat [ "remote:  "+                                  , pkgn', "-", pkgv', "-r", BS8.pack (show xrev')+                                  ]++           let cab'' = cabalEditXRev (xrev'+1) cab'+               bakfn = optSyCFile <> "~"++           case () of+             _ | optSyCIncrRev, cab'' == cab0 -> do+                     putStrLn "INFO: local and (incremented) latest remote .cabal revision are already identical! Nothing to do."++             _ | not optSyCForce, xrev' < xrev -> do+                     putStrLn "ERROR: Local file has higher revision number than Hackage - aborting! (use --force to allow this)"+                     exitFailure++             _ | optSyCIncrRev, cab'' /= cab0 -> do+                     when (cab' == cab0) $ do+                         putStrLn "NOTE: local and (non-incremented) latest remote .cabal revision are identical."++                     BS.writeFile bakfn cab0+                     putStrLn ("INFO: saved backup of original local file to " <> bakfn)++                     BS.writeFile optSyCFile cab''+                     BS8.putStrLn $ mconcat [ "local :  "+                                            , pkgn, "-", pkgv, "-r", BS8.pack (show $ xrev'+1)+                                            , "   ('", BS8.pack optSyCFile, "')"+                                            ]++             _ | cab' == cab0 -> do+                     putStrLn "INFO: local and latest remote .cabal revision are already identical! Nothing to do."++             _ -> do+                     BS.writeFile bakfn cab0+                     putStrLn ("INFO: saved backup of original local file to " <> bakfn)++                     BS.writeFile optSyCFile cab'+                     BS8.putStrLn $ mconcat [ "local :  "+                                            , pkgn, "-", pkgv, "-r", BS8.pack (show $ xrev')+                                            , "   ('", BS8.pack optSyCFile, "')"+                                            ]+++       ListCabal (ListCOptions {..}) -> do+           let pkgn = optLCPkgName++           vs <- runHConn (fetchVersions pkgn)++           unless optNoAnn $+             putStrLn $ concat [ "Found ", show (length vs), " package versions for "+                               , show pkgn, " ([U]npreferred, [D]eprecated):"+                               ]++           if optRevUrls then do+             forM_ vs $ \(v,_) -> do+                 let pid = pkgn <> "-" <> v+                 BS8.putStrLn $ mconcat [ " - https://hackage.haskell.org/package/", pid, "/revisions/" ]+           else do+             forM_ vs $ \(v,unp) -> do+                 let status = case unp of+                         _ | optNoAnn -> ""+                         Normal      -> "    "+                         Deprecated  -> "[D] "+                         UnPreferred -> "[U] "+                 BS8.putStrLn $ status <> pkgn <> "-" <> v+           return ()++       PushCabal (PushCOptions {..}) -> do+           (username,password) <- maybe (fail "missing Hackage credentials") return =<< getHackageCreds+           putStrLn $ "Using Hackage credentials for username " ++ show username++           forM_ optPsCFiles $ \fn -> do+               (pkgn,pkgv,xrev) <- pkgDescToPkgIdXrev <$> C.readGenericPackageDescription C.deafening fn+               putStrLn $ concat [ "Pushing ", show fn+                                 , " (", BS8.unpack pkgn, "-", BS8.unpack pkgv, "~", show xrev, ")"+                                 , if not optPsCPublish then " [review-mode]" else "", " ..."+                                 ]++               let editCab | optPsCIncrRev = cabalEditXRev (xrev+1)+                           | otherwise    = id++               rawcab <- editCab <$> BS.readFile fn+               (dt,tmp) <- timeIt $ runHConn (hackagePostCabal (username,password) (pkgn,pkgv) rawcab+                                                               (if optPsCPublish then WetRun else DryRun))++               printf "Hackage response was (after %.3f secs):\n" dt+               putStrLn (replicate 80 '=')+               BS8.putStrLn (tidyHtml tmp)+               putStrLn (replicate 80 '=')++       PushCandidate (PushPCOptions {..}) -> do+           (username,password) <- maybe (fail "missing Hackage credentials") return =<< getHackageCreds+           putStrLn $ "Using Hackage credentials for username " ++ show username++           forM_ optPPCFiles $ \fn -> do+               putStrLn $ "reading " ++ show fn ++ " ..."+               rawtar <- BS.readFile fn+               putStrLn $ "uplading to Hackage..."+               tmp <- runHConn (hackagePushCandidate (username,password) (takeFileName fn, rawtar))++               putStrLn "Hackage response was:"+               putStrLn (replicate 80 '=')+               BS8.putStrLn tmp+               putStrLn (replicate 80 '=')+++       CheckRevision (CheckROptions {..}) -> do+           old <- BS.readFile optCROrig+           new <- BS.readFile optCRNew++           case diffCabalRevisions old new of+               Left err -> do+                   putStrLn "change not allowed:"+                   putStrLn err+                   exitFailure++               Right [] -> do+                   putStrLn "no-op change detected"+                   exitFailure++               Right changes -> do+                   putStrLn "change allowed:"+                   forM_ changes $ \(Change _ what old' new') -> do+                       putStrLn $ "what: " ++ what+                       putStrLn $ " old: " ++ old'+                       putStrLn $ " new: " ++ new'++           return ()++       IndexShaSum opts -> IndexShaSum.run opts+++       AddBound AddBoundOptions {..} -> forM_ optABFiles $ \fp -> do+           old <- BS.readFile fp++           -- idea is simple:+           -- - .cabal is line oriented file+           -- - find "library" section start+           -- - bonus: look of an indentation used from the next field/section there+           -- - insert data into a bytestring "manually"+           fs <- either (exitFailureWith . show) return $ C.readFields old+           (lin, indent) <- maybe+               (exitFailureWith $ "Cannot find library section in " ++ fp)+               return+               (findLibrarySection fs)++           let msgLines  = map ("-- " ++) optABMessage+               bdLine    = "build-depends: " ++ C.prettyShow optABPackageName ++ " " ++ C.prettyShow optABVersionRange+               midLines  = [ BS8.pack $ replicate indent ' ' ++ l+                           | l <- msgLines ++ [bdLine]+                           ] ++ [""] -- also add an empty line separator+               (preLines, postLines) = splitAt lin $ BS8.lines old+               new = BS8.unlines (preLines ++ midLines ++ postLines)++           -- interpretation of version ranges+           let oldGpd = parseGenericPackageDescription' old+               newGpd = parseGenericPackageDescription' new++               oldRange = extractRange oldGpd optABPackageName+               newRange = extractRange newGpd optABPackageName++               oldRange' = C.intersectVersionRanges oldRange optABVersionRange++               -- Canonical forms (semantics)+               oldSem  = C.toVersionIntervals oldRange   -- existing range+               oldSem' = C.toVersionIntervals oldRange'  -- range after adding the bound (theory)+               newSem  = C.toVersionIntervals newRange   -- range after adding the bound (practice)++           -- Necessity check: does the addition of the bound change the semantics?+           -- if not, it can be skipped.++           if not optForce && oldSem' == oldSem then do++             putStrLn $ concat [ "Skipping ", fp, ": bound already subsumed by existing constraints (use --force to add nevertheless)." ]++           else do+             -- sanity check: did the addition have the intended outcome?+             unless (newSem == oldSem') $+                exitFailureWith $ unwords+                    [ "Edit failed, version ranges don't match: "+                    , C.prettyShow oldRange+                    , "&&"+                    , C.prettyShow optABVersionRange+                    , "=/="+                    , C.prettyShow newRange+                    ]++             -- write new version+             putStrLn $ unwords [ "Adding bound to", fp ]+             BS.writeFile fp new++   return ()+  where+    mkHConn = do+        sslCtx <- baselineContextSSL+        pure $ HConn (openConnectionSSL sslCtx optHost 443) Nothing 0 0++    runHConn act = do+        hc0 <- mkHConn+        flip evalStateT hc0 $ do+            res <- act+            closeHConn+            return res++    getNetrcContents :: IO (Maybe ByteString)+    getNetrcContents = do+        mhome <- lookupEnv "HOME"+        case mhome of+            Nothing -> return Nothing+            Just "" -> return Nothing+            Just ho -> do+                let fnGpg = ho ++ "/.netrc.gpg"+                let fn = ho ++ "/.netrc"+                gpgExists <- doesFileExist fnGpg+                if gpgExists+                then readGpg fnGpg+                else readPlain fn+      where+        readGpg fn = do+            (ec, out, err) <- readProcessWithExitCode "gpg" ["--decrypt", fn] ""+            case ec of+                ExitSuccess   -> return (Just out)+                ExitFailure _ -> BS.putStr err >> return Nothing++        readPlain fn = do+            ret <- tryIOError (BS.readFile fn)+            case ret of+                Left e | isDoesNotExistError e -> return Nothing+                       | otherwise             -> ioError e+                Right b -> return $! Just b++    getHackageCreds :: IO (Maybe (ByteString,ByteString))+    getHackageCreds = do+        getNetrcContents >>= \case+            Nothing -> pure Nothing+            Just contents -> case parseNetRc "netrc" contents of+                Left _ -> fail "Invalid ${HOME}/.netrc(.gpg) found"+                Right NetRc {..} ->+                    evaluate $ (\NetRcHost{..} -> (nrhLogin,nrhPassword))+                               <$> listToMaybe (filter ((== optHost) . nrhName) nrHosts)+++    pkgDescToPkgIdXrev pdesc = force (BS8.pack pkgn, BS8.pack $ showVersion pkgv, read xrev :: PkgRev)+      where+        C.PackageIdentifier (C.unPackageName -> pkgn) pkgv = C.package . C.packageDescription $ pdesc+        xrev = fromMaybe "0" . lookup "x-revision" . C.customFieldsPD . C.packageDescription $ pdesc++    incrXrev :: ByteString -> ByteString+    incrXrev cabdata0 = cabalEditXRev (xrev0+1) cabdata0+      where+        pdesc0 = parseGenericPackageDescription' cabdata0+        (_,_,xrev0) = pkgDescToPkgIdXrev pdesc0++    exitFailureWith e = do+        putStrLn e+        exitFailure++    parseGenericPackageDescription' bs =+        case snd $ C.runParseResult $ C.parseGenericPackageDescription bs of+            Left (_, es) -> error $ List.intercalate "\n" $ map (C.showPError "<.cabal>") $ toList es+            Right x      -> x++    extractRange gpd pkgName = case vss of+        []     -> C.noVersion+        (v:vs) -> List.foldl' C.intersectVersionRanges v vs+      where+        vss = gpd ^.. LC.condLibrary . _Just . condTreeDataL . LC.targetBuildDepends . traverse . to ext . _Just+        ext (C.Dependency pkgName' vr _)+           | pkgName == pkgName' = Just vr+           | otherwise           = Nothing++    condTreeDataL :: Functor f => (a -> f a) -> C.CondTree v c a -> f (C.CondTree v c a)+    condTreeDataL f (C.CondNode x c cs) = f x <&> \y -> C.CondNode y c cs+++-- | Try to clean-up HTML fragments to be more readable+tidyHtml :: ByteString -> ByteString+tidyHtml =+    replace "&amp;"  "&"     . -- must be last entity substitution+    replace "&gt;"   ">"     .+    replace "&lt;"   "<"     .+    replace "<p>"    ""      . -- tags must be replaced before entities+    replace "</p>"   "\n"    .+    replace "<li>"   "\n * " .+    replace "</li>"  ""      .+    replace "</pre>" "`"     .+    replace "<pre>"  "`"     .+    stripEnd+  where+    stripEnd = fst . BS8.spanEnd isSpace++    replace :: ByteString -> ByteString -> ByteString -> ByteString+    replace old new = BSL.toStrict . BSS.replace old new+++timeIt :: IO a -> IO (Double, a)+timeIt act = do+    t0  <- getTime+    res <- act+    t1  <- getTime+    let !dt = t1 - t0+    pure (dt, res)+  where+    getTime :: IO Double+    getTime = realToFrac `fmap` getPOSIXTime
+ tests/Tests.hs view
@@ -0,0 +1,61 @@+{-# LANGUAGE TupleSections #-}+module Main (main) where+++import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as LBS+import Distribution.Server.Util.CabalRevisions (diffCabalRevisions', Change (..))+import Distribution.Simple.Utils (toUTF8BS)+import System.FilePath ((</>), (-<.>))+import Test.Tasty (defaultMain, testGroup, TestTree)+import Test.Tasty.Golden (goldenVsStringDiff)++main :: IO ()+main = defaultMain $ testGroup "Fixtures"+    -- basic sanity tests+    [ golden "tree-diff" 0 1+    , golden "deepseq"   0 1++    -- adding a new conditional section with restricted bounds+    -- TODO: this is not allowed+    , golden "SVGFonts" 0 2++    , golden "semigroups" 0 2+    ]++golden :: String -> Int -> Int -> TestTree+golden name mi ma = case pairs [mi .. ma] of+    []       -> golden' name mi ma+    [(x, y)] -> golden' name x y+    ps       -> testGroup name+        [ golden' name x y+        | (x, y) <- ps+        ]++golden' :: String -> Int -> Int -> TestTree+golden' name mi ma = goldenVsStringDiff name' diff gold $ do+    o <- BS.readFile orig+    e <- BS.readFile edit+    return $ LBS.fromStrict $ toUTF8BS $ unlines $ case diffCabalRevisions' False o e of+        Left err      -> [ "ERROR", err ]+        Right changes -> "OK" : concatMap showChange changes+  where+    name' = unwords [ name, show mi, "->", show ma ]+    orig = "fixtures" </> name -<.> (show mi ++ ".cabal")+    edit = "fixtures" </> name -<.> (show ma ++ ".cabal")+    gold = "fixtures" </> name -<.> (show mi ++ "." ++ show ma ++ ".diff")++    diff ref new = ["diff", "-u", ref, new]++    showChange (Change sev what from to) =+        [ show sev+        , what+        , "- " ++ from+        , "+ " ++ to+        ]++-- cartesian product without diagonal+-- the idea is that revisions should be transitive and symmetric+pairs :: [a] -> [(a,a)]+pairs []     = []+pairs (x:xs) = map (x,) xs ++ map (,x) xs ++ pairs xs