[med-svn] [yaggo] 05/07: New upstream version 1.5.9
Andreas Tille
tille at debian.org
Thu Oct 5 20:00:31 UTC 2017
This is an automated email from the git hooks/post-receive script.
tille pushed a commit to branch master
in repository yaggo.
commit f058ab7ec993c2d5750aaae417de5624e4c3ad67
Author: Andreas Tille <tille at debian.org>
Date: Thu Oct 5 21:47:20 2017 +0200
New upstream version 1.5.9
---
.gitignore | 3 +
COPYING | 674 ++++++++++++++++++
Makefile | 12 +
README | 1 +
README.md | 107 +++
Rakefile | 51 ++
bin/create_yaggo_one_file | 45 ++
bin/yaggo | 22 +
debian/changelog | 14 -
debian/clean | 1 -
debian/compat | 1 -
debian/control | 25 -
debian/copyright | 21 -
debian/docs | 1 -
debian/install | 2 -
debian/rules | 18 -
debian/source/format | 1 -
debian/watch | 2 -
lib/yaggo/dsl.rb | 573 ++++++++++++++++
lib/yaggo/general.rb | 127 ++++
lib/yaggo/library.rb | 197 ++++++
lib/yaggo/main.rb | 152 +++++
lib/yaggo/man_page.rb | 449 ++++++++++++
lib/yaggo/parser.rb | 404 +++++++++++
lib/yaggo/stub.rb | 42 ++
lib/yaggo/version.rb | 1 +
lib/yaggo/zsh_completion.rb | 94 +++
license-header.txt | 14 +
setup.rb | 1585 +++++++++++++++++++++++++++++++++++++++++++
test/Makefile | 20 +
test/count.cpp | 33 +
test/count_cmdline.yaggo | 84 +++
test/test_errno.cc | 12 +
test/test_errno.yaggo | 7 +
34 files changed, 4709 insertions(+), 86 deletions(-)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..17bec8c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+yaggo-*.tar.gz
+.tup
+pkg
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/COPYING
@@ -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>.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..73d681a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,12 @@
+prefix ?= /usr/local
+
+all: bin/create_yaggo_one_file
+ ruby bin/create_yaggo_one_file ./yaggo
+
+install: all
+ mkdir -p $(prefix)/bin
+ mkdir -p $(prefix)/share/doc/yaggo
+ mkdir -p $(prefix)/share/man/man1
+ cp ./yaggo $(prefix)/bin
+ cp ./README.md $(prefix)/share/doc/yaggo
+ ./yaggo -m $(prefix)/share/man/man1/yaggo.1
diff --git a/README b/README
new file mode 100644
index 0000000..96dc92f
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+See README.md
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9ab9d10
--- /dev/null
+++ b/README.md
@@ -0,0 +1,107 @@
+# What is yaggo?
+
+Yaggo is a tool to generate command line parsers for C++. Yaggo stands
+for "Yet Another GenGetOpt" and is inspired by [GNU Gengetopt](https://www.gnu.org/software/gengetopt/gengetopt.html).
+
+It reads a configuration file describing the switches and argument for
+a C++ program and it generates one header file that parses the command
+line using getopt_long(3). See the Example section below for more details.
+
+# Installation
+
+## Quick and easy
+
+Download the standalone script from the [release](https://github.com/gmarcais/yaggo/releases)
+and copy it into a directory in your PATH (e.g. `~/bin`)
+
+From the source tree, the same is achieved with:
+
+```Shell
+make DEST=$HOME/bin
+```
+
+## As a gem
+
+Download the gem from the [release](https://github.com/gmarcais/yaggo/releases) and install it
+with `sudo gem install ./yaggo-1.5.8.gem` (adjust the version!).
+
+Similarly, from the source tree, first generate the gem
+and then install it. For example here with version 1.5.3:
+
+```Shell
+rake gem
+sudo gem install ./pkg/yaggo-1.5.3.gem
+```
+
+# Documentation
+
+After installation, documentation is available with `yaggo --man`.
+
+# Simple example
+
+Given the following configuration file 'parser.yaggo':
+
+```Ruby
+purpose "Demonstrate yaggo capabilities"
+description "This simple configuration file shows some of the capabilities of yaggo.
+This is supposed to be a longer description of the program.
+"
+
+option("f", "flag") {
+ description "This is a flag"
+ off
+}
+option("i", "int") {
+ description "This take an integer"
+ int
+ default 20
+}
+arg("path") {
+ description "Path to file"
+ c_string
+}
+```
+
+The following C++ program ('parser.cc') does switch parsing, generate
+appropriate errors and has an automatically generated help (accessible
+with '-h' or '--help').
+
+```C
+#include <iostream>
+#include "parser.hpp"
+
+int main(int argc, char* argv[]) {
+ parser args(argc, argv); // Does all the parsing
+
+ std::cout << "--flag " << (args.flag_flag ? "not passed" : "passed") << "\n"
+ << "--int: " << args.int_arg << "\n"
+ << "path: " << args.path_arg << "\n";
+
+ return 0;
+}
+```
+
+All of this is compiled with:
+
+```Shell
+yaggo parser.yaggo
+g++ -o parser parser.cc
+```
+
+Then, './parser --help' returns:
+
+```
+Usage: parser [options] path:string
+
+Demonstrate yaggo capabilities
+
+This simple configuration file shows some of the capabilities of yaggo.
+This is supposed to be a longer description of the program.
+
+Options (default value in (), *required):
+ -f, --flag This is a flag (false)
+ -i, --int=int This take an integer (20)
+ -U, --usage Usage
+ -h, --help This message
+ -V, --version Version
+```
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..99ab75c
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+require 'rubygems'
+require 'rubygems/package_task'
+load 'lib/yaggo/version.rb'
+load 'bin/create_yaggo_one_file'
+
+spec = Gem::Specification.new do |s|
+ s.name = "yaggo"
+ s.version = $yaggo_version
+ s.platform = Gem::Platform::RUBY
+ s.authors = ["Guillaume Marçais"]
+ s.email = ["gmarcais at umd.edu"]
+ s.homepage = "https://github.com/gmarcais/yaggo"
+ s.summary = "Yet Another Generator for getopt"
+ s.licenses = ['GPL-3.0']
+ s.description = "Yaggo defines a DSL to generate GNU compatible command line parsers for C++ using getopt."
+
+ s.required_rubygems_version = ">= 1.3.6"
+
+ # If you need to check in files that aren't .rb files, add them here
+ s.files = Dir["{lib}/**/*.rb", "bin/*", "LICENSE", "*.md"]
+ s.require_path = 'lib'
+
+ # If you need an executable, add it here
+ s.executables = ["yaggo"]
+end
+
+Gem::PackageTask.new(spec) do |pkg|
+ pkg.need_zip = false
+ pkg.need_tar = true
+end
+
+desc "Run yaggo"
+task :yaggo do |t|
+ ARGV.shift if ARGV[0] == "yaggo"
+ ruby("-Ilib", "./bin/yaggo", *ARGV)
+end
+
+task :default => :yaggo
+
+desc "Create a distribution tarball"
+task :dist do |t|
+ system("tar", "-zc", "-f", "yaggo-#{spec.version}.tar.gz",
+ "--transform", "s|^|yaggo-#{spec.version}/|",
+ "README", "COPYING", "setup.rb", "bin", "lib")
+end
+
+desc "Create a single file executable"
+task :exec do |t|
+ create_binary("lib/yaggo/main.rb", "yaggo")
+end
diff --git a/bin/create_yaggo_one_file b/bin/create_yaggo_one_file
new file mode 100644
index 0000000..15bd522
--- /dev/null
+++ b/bin/create_yaggo_one_file
@@ -0,0 +1,45 @@
+#! /usr/bin/env ruby
+
+def create_binary src, dest
+ to_load = []
+ loaded = {}
+ open(dest, "w", 0755) do |wfd|
+ wfd.puts(<<'EOS')
+#! /usr/bin/env ruby
+
+if !$load_self
+ $load_self = true
+ load(__FILE__)
+ main
+ exit(0)
+end
+
+EOS
+
+ open(src, "r") do |rfd|
+ rfd.each_line { |l|
+ if l =~ /^\s*require\s+['"]yaggo\/(\w+)['"]\s*$/
+ to_load << $1
+ else
+ wfd.print(l)
+ end
+ }
+ end
+
+ to_load.each { |f|
+ next if loaded[f]
+ wfd.puts("", "# Loading yaggo/#{f}", "")
+ open(File.join("lib", "yaggo", f + ".rb"), "r") { |nfd|
+ nfd.each_line { |l|
+ wfd.print(l) unless l =~ /^\s*require\s+['"]yaggo\/(\w+)['"]\s*$/
+ }
+ }
+ loaded[f] = true
+ }
+ end
+end
+
+if __FILE__ == $0
+ dest = ARGV.shift || "yaggo"
+ create_binary("lib/yaggo/main.rb", dest)
+end
diff --git a/bin/yaggo b/bin/yaggo
new file mode 100755
index 0000000..12605b9
--- /dev/null
+++ b/bin/yaggo
@@ -0,0 +1,22 @@
+#! /usr/bin/env ruby
+
+# Yaggo. Yet Another GenGetOpt. Generate command line switch parsers
+# using getopt_long.
+# Copyright (C) 2011 Guillaume Marcais.
+#
+# 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/>.
+
+require 'yaggo/main.rb'
+main
diff --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index 84f3c5f..0000000
--- a/debian/changelog
+++ /dev/null
@@ -1,14 +0,0 @@
-yaggo (1.5.9-1) unstable; urgency=medium
-
- * New upstream version
- * debhelper 10
- * cme fix dpkg-control
- * d/watch: version=4
-
- -- Andreas Tille <tille at debian.org> Mon, 16 Jan 2017 17:26:36 +0100
-
-yaggo (1.5.4-1) unstable; urgency=low
-
- * Initial release (Closes: #764211)
-
- -- Andreas Tille <tille at debian.org> Mon, 06 Oct 2014 12:13:09 +0200
diff --git a/debian/clean b/debian/clean
deleted file mode 100644
index 19d4f94..0000000
--- a/debian/clean
+++ /dev/null
@@ -1 +0,0 @@
-yaggo
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index f599e28..0000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-10
diff --git a/debian/control b/debian/control
deleted file mode 100644
index 6def99b..0000000
--- a/debian/control
+++ /dev/null
@@ -1,25 +0,0 @@
-Source: yaggo
-Maintainer: Debian Med Packaging Team <debian-med-packaging at lists.alioth.debian.org>
-Uploaders: Andreas Tille <tille at debian.org>
-Section: misc
-Priority: optional
-Build-Depends: debhelper (>= 10),
- ruby,
- help2man
-Standards-Version: 3.9.8
-Vcs-Browser: https://anonscm.debian.org/viewvc/debian-med/trunk/packages/yaggo/trunk/
-Vcs-Svn: svn://anonscm.debian.org/debian-med/trunk/packages/yaggo/trunk/
-Homepage: https://github.com/gmarcais/yaggo
-
-Package: yaggo
-Architecture: all
-Depends: ${shlibs:Depends},
- ${misc:Depends},
- ruby | ruby-interpreter
-Description: generate command line parser using getopt_long
- Yaggo is a tool to generate command line parsers for C++. Yaggo stands
- for "Yet Another GenGetOpt" and is inspired by GNU Gengetopt.
- .
- It reads a configuration file describing the switches and argument for
- a C++ program and it generates one header file that parses the command
- line using getopt_long(3).
diff --git a/debian/copyright b/debian/copyright
deleted file mode 100644
index ff303e7..0000000
--- a/debian/copyright
+++ /dev/null
@@ -1,21 +0,0 @@
-Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Name: yaggo
-Source: https://github.com/gmarcais/yaggo/releases
-
-Files: *
-Copyright: 2012-2014 Guillaume Marçais <gmarcais at umd.edu>
-License: GPL-3+
-
-Files: setup.rb
-Copyright: 2000-2005 Minero Aoki
-License: LGPL-2.1
- On Debian systems you can find the full text of the Lesser General
- Public License version 2.1 at /usr/share/common-licenses/LGPL-2.1.
-
-Files: debian/*
-Copyright: 2014 Andreas Tille <tille at debian.org>
-License: GPL-3+
-
-License: GPL-3+
- On Debian systems you can find the full text of the GNU General Public
- License version 3 at /usr/share/common-licenses/GPL-3.
diff --git a/debian/docs b/debian/docs
deleted file mode 100644
index b43bf86..0000000
--- a/debian/docs
+++ /dev/null
@@ -1 +0,0 @@
-README.md
diff --git a/debian/install b/debian/install
deleted file mode 100644
index f17862a..0000000
--- a/debian/install
+++ /dev/null
@@ -1,2 +0,0 @@
-yaggo usr/bin
-
diff --git a/debian/rules b/debian/rules
deleted file mode 100755
index 40b9ae0..0000000
--- a/debian/rules
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/make -f
-
-pkg := $(shell dpkg-parsechangelog | sed -n 's/^Source: //p')
-version=$(shell dpkg-parsechangelog | grep Version: | cut -f2 -d' ' | cut -f1 -d- )
-mandir=$(CURDIR)/debian/$(pkg)/usr/share/man/man1/
-HELP2MAN = help2man --no-info --version-string="$(version)"
-
-%:
- dh $@
-
-override_dh_auto_install:
- dh_auto_install -- prefix=$(CURDIR)/debian/$(pkg)/usr
-
-override_dh_installman:
- mkdir -p $(mandir)
- $(HELP2MAN) \
- --name='generate command line parser using getopt_long' \
- $(CURDIR)/$(pkg) > $(mandir)/$(pkg).1
diff --git a/debian/source/format b/debian/source/format
deleted file mode 100644
index 163aaf8..0000000
--- a/debian/source/format
+++ /dev/null
@@ -1 +0,0 @@
-3.0 (quilt)
diff --git a/debian/watch b/debian/watch
deleted file mode 100644
index f4e919b..0000000
--- a/debian/watch
+++ /dev/null
@@ -1,2 +0,0 @@
-version=4
-https://github.com/gmarcais/yaggo/releases .*/archive/v(\d[\d.-]+)\.(?:tar(?:\.gz|\.bz2)?|tgz)
diff --git a/lib/yaggo/dsl.rb b/lib/yaggo/dsl.rb
new file mode 100644
index 0000000..6186bc7
--- /dev/null
+++ b/lib/yaggo/dsl.rb
@@ -0,0 +1,573 @@
+# This file is part of Yaggo.
+
+# Yaggo 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.
+
+# Yaggo 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 Yaggo. If not, see <http://www.gnu.org/licenses/>.
+
+
+##############################
+# Process an input files. Define the Domain Specific Language.
+##############################
+$options = []
+$opt_hash = {}
+$args = []
+
+class NoTarget
+ def description str; $description = str; end
+ def description= str; $description = str; end
+ def method_missing(m, *args)
+ raise "'#{m}' used outside of option or arg description"
+ end
+end
+$target = NoTarget.new
+
+def output str; $output = str; end
+def name str; $klass = str; end
+def purpose str; $purpose = str; end
+def package str; $package = str; end
+def usage str; $usage = str; end
+def text str; $after_text = str; end
+def description str; $target.description = str; end
+def version str; $version = str; end
+def posix *args; $posix = true; end
+def license str; $license = str; end
+$global_variables = [:output, :name, :purpose, :package,
+ :description, :version, :license, :posix]
+output = name = purpose = package = description = version = license = nil
+
+# def set_type t
+# raise "More than 1 type specified: '#{$target.type}' and '#{t}'" unless $target.type.nil?
+# $target.type = t
+# end
+
+def int32; $target.type = :int32; end
+def int64; $target.type = :int64; end
+def uint32; $target.type = :uint32; end
+def uint64; $target.type = :uint64; end
+def int; $target.type = :int; end
+def long; $target.type = :long; end
+def double; $target.type = :double; end
+def string; $target.type = :string; end
+def c_string; $target.type = :c_string; end
+def flag; $target.type = :flag; end
+def enum(*argv); $target.type = :enum; $target.enum = argv; end
+
+def suffix; $target.suffix = true; end
+def required; $target.required = true; end
+def hidden; $target.hidden = true; end
+def secret; $target.secret = true; end
+def on; $target.on; end
+def off; $target.off; end
+def no; $target.no; end
+def default str; $target.default = str; end
+def typestr str; $target.typestr = str; end
+def multiple; $target.multiple = true; end
+def at_least n; $target.at_least = n; end
+def conflict *a; $target.conflict= a; end
+def imply *a; $target.imply= a; end
+def access *types; $target.access= types; end
+# Define the following local variables and check their value after
+# yielding the block to catch syntax such as default="value".
+$option_variables = [:default, :typestr, :at_least]
+default = typestr = at_least = nil
+$main_binding = binding
+
+def default_val(val, type, *argv)
+ case type
+ when :string, :c_string
+ "\"#{val || $type_default[type]}\""
+ when :uint32, :uint64, :int32, :int64, :int, :long, :double
+ val ? "(#{$type_to_C_type[type]})#{val}" : $type_default[type]
+ else
+ val.to_s || $type_default[type]
+ end
+end
+
+class BaseOptArg
+ def at_least=(n)
+ multiple = true
+ nb = case n
+ when Integer
+ n
+ when String
+ n =~ /^\d+$/ ? n.to_i : nil
+ else
+ nil
+ end
+ raise "Invalid minimum number for at_least (#{n})" if nb.nil?
+ self.multiple = true
+ @at_least = nb
+ end
+
+ def type=(t)
+ raise "More than 1 type specified: '#{type}' and '#{t}'" unless @type.nil? || @type == t
+ @type = t
+ end
+
+ def suffix=(t)
+ case type
+ when nil
+ raise "A numerical type must be specify before suffix"
+ when :flag, :string, :c_string
+ raise "Suffix is meaningless with the type #{type}"
+ end
+ @suffix = t
+ end
+
+ def access=(types)
+ types.all? { |t| ["read", "write", "exec"].include?(t) } or
+ raise "Invalid access type(s): #{types.join(", ")}"
+ @access_types = types
+ end
+
+ def check
+ if !@access_types.empty? && @type != :c_string
+ raise "Access checking is valid only with a path (a c_string)"
+ end
+ end
+end
+
+class Option < BaseOptArg
+ attr_accessor :description, :required, :typestr
+ attr_accessor :hidden, :secret, :conflict, :multiple, :access_types, :noflag
+ attr_reader :long, :short, :var, :type, :at_least, :default, :suffix, :enum
+ attr_reader :imply
+
+ def initialize(long, short)
+ @long, @short = long, short
+ @var = (@long || @short).gsub(/[^a-zA-Z0-9_]/, "_")
+ @type = nil
+ @no = false # Also generate the --noswitch for a flag
+ @default = nil
+ @suffix = false
+ @at_least = nil
+ @conflict = []
+ @enum = []
+ @imply = []
+ @access_types = []
+ end
+
+ def on
+ self.type = :flag
+ self.default = "true"
+ end
+
+ def off
+ self.type = :flag
+ self.default = "false"
+ end
+
+ def no
+ self.type = :flag
+ self.noflag = true
+ end
+
+ def tf_to_on_off v
+ case v
+ when "true"
+ "on"
+ when "false"
+ "off"
+ else
+ v
+ end
+ end
+
+ def convert_int(x, signed = true)
+ x =~ /^([+-]?\d+)([kMGTPE]?)$/ or return nil
+ v = $1.to_i
+ return nil if v < 0 && !signed
+ case $2
+ when "k"
+ v *= 1000
+ when "M"
+ v *= 1000_000
+ when "G"
+ v *= 1000_000_000
+ when "T"
+ v *= 1000_000_000_000
+ when "P"
+ v *= 1000_000_000_000_000
+ when "E"
+ v *= 1000_000_000_000_000_000
+ end
+ return v
+ end
+
+ def convert_double(x)
+ x =~ /^([+-]?[\d]+(?:\.\d*))?(?:([afpnumkMGTPE])|([eE][+-]?\d+))?$/ or return nil
+ v = "#{$1}#{$3}".to_f
+ case $2
+ when "a"
+ v *= 1e-18
+ when "f"
+ v *= 1e-15
+ when "p"
+ v *= 1e-12
+ when "n"
+ v *= 1e-9
+ when "u"
+ v *= 1e-6
+ when "m"
+ v *= 1e-3
+ when "k"
+ v *= 1e3
+ when "M"
+ v *= 1e6
+ when "G"
+ v *= 1e9
+ when "T"
+ v *= 1e12
+ when "P"
+ v *= 1e15
+ when "E"
+ v *= 1e18
+ end
+ return v
+ end
+
+ def default=(v)
+ type.nil? and raise "A type must be specified before defining a default value"
+ unless default.nil?
+ if type == :flag
+ v1, v2 = tf_to_on_off(default), tf_to_on_off(v)
+ else
+ v1, v2 = default, v
+ end
+ raise "More than 1 default value specified: '#{v1}' and '#{v2}'"
+ end
+ pref = "Option #{long || ""}|#{short || ""}:"
+ bv = v # Backup v for display
+ case @type
+ when nil
+ raise "#{pref} No type specified"
+ when :uint32, :uint64
+ (Integer === v && v >= 0) || (String === v && v = convert_int(v, false)) or
+ raise "#{pref} Invalid unsigned integer '#{bv}'"
+ when :int32, :int64, :int, :long
+ (Integer === v) || (String === v && v = convert_int(v, true)) or
+ raise "#{pref} Invalid integer #{bv}"
+ when :double
+ (Float === v) || (String === v && v = convert_double(v)) or
+ raise "#{pref} Invalid double #{bv}"
+ when :enum
+ v = v.to_i if v =~ /^\d+$/
+ case v
+ when Integer
+ (v >= 0 && v < @enum.size) or
+ raise "Default is out of range [0, #{@enum.size-1}]"
+ when String
+ nv = @enum.index(v) or
+ raise "Unknown constant '#{v}'. Should be one of { #{@enum.join(", ")} }"
+ v = nv
+ else
+ raise "Expected an Integer or a String"
+ end
+ end
+ @default = v
+ end
+
+ def enum=(*argv)
+ @type == :enum or raise "#{pref} Enum valid only for enum types."
+ @enum = argv.flatten
+ end
+
+ def conflict= a; @conflict += a.map { |x| x.gsub(/^-+/, "") }; end
+ def imply= a; @imply += a.map { |x| x.gsub(/^-+/, "") }; end
+
+ def check
+ pref = "Option #{long || ""}|#{short || ""}:"
+ raise "#{pref} No type specified" if type.nil?
+
+ if multiple
+ raise "#{pref} Multiple is meaningless with a flag" if type == :flag
+ raise "#{pref} An option marked multiple cannot have a default value" unless default.nil?
+ raise "#{pref} Multiple is incompatible with enum type" if type == :enum
+ end
+
+ if @type == :flag && noflag && !short.nil?
+ raise "#{pref} flag with 'no' option cannot have a short switch"
+ end
+
+ super
+
+ # case @type
+ # when nil
+ # raise "#{pref} No type specified"
+ # when :uint32, :uint64
+ # @default.nil? || @default =~ /^\d+$/ or
+ # raise "#{pref} Invalid unsigned integer #{@default}"
+ # when :int32, :int64, :int, :long
+ # @default.nil? || @default =~ /^[+-]?\d+$/ or
+ # raise "#{pref} Invalid integer #{@default}"
+ # when :double
+ # @default.nil? || @default =~ /^[+-]?[\d.]+([eE][+-]?\d+)?$/ or
+ # raise "#{pref} Invalid double #{@default}"
+ # when :flag
+ # raise "#{pref} A flag cannot be declared multiple" if @multiple
+ # raise "#{pref} Suffix is meaningless for a flag" if @suffix
+ # end
+ end
+
+ def static_decl
+ a = []
+ if @type == :enum
+ a << "struct #{@var} {"
+ a << " enum { #{@enum.map { |x| x.gsub(/[^a-zA-Z0-9_]/, "_") }.join(", ")} };"
+ a << " static const char* const strs[#{@enum.size + 1}];"
+ a << "};"
+ end
+ a
+ end
+
+ def var_decl
+ if @type == :flag
+ ["#{"bool".ljust($typejust)} #{@var}_flag;"]
+ else
+ a = []
+ if @multiple
+ c_type = "::std::vector<#{$type_to_C_type[@type]}>"
+ a << (c_type.ljust($typejust) + " #{@var}_arg;")
+ a << ("typedef #{c_type}::iterator #{@var}_arg_it;")
+ a << ("typedef #{c_type}::const_iterator #{@var}_arg_const_it;")
+ else
+ a << "#{$type_to_C_type[@type].ljust($typejust)} #{@var}_arg;"
+ end
+ a << "#{"bool".ljust($typejust)} #{@var}_given;"
+ end
+ end
+
+ def init
+ s = "#{@var}_#{@type == :flag ? "flag" : "arg"}("
+ s += default_val(@default, @type, @enum) unless @multiple
+ s += ")"
+ unless @type == :flag
+ s += ", #{@var}_given(false)"
+ end
+ s
+ end
+
+ def long_enum
+ return nil if !@short.nil?
+ res = [@var.upcase + "_OPT"]
+ if @type == :flag && noflag
+ res << "NO#{@var.upcase}_OPT"
+ end
+ res
+ end
+
+ def struct
+ res = ["{\"#{long}\", #{@type == :flag ? 0 : 1}, 0, #{@short ? "'" + @short + "'" : long_enum[0]}}"]
+ if @type == :flag && noflag
+ res << "{\"no#{long}\", 0, 0, #{long_enum()[1]}}"
+ end
+ res
+ end
+ def short_str
+ return nil if @short.nil?
+ @short + (@type == :flag ? "" : ":")
+ end
+ def switches
+ s = @short.nil? ? " " : "-#{@short}"
+ s += ", " unless @short.nil? || @long.nil?
+ unless @long.nil?
+ if @type == :flag && @noflag
+ s += "--[no]#{@long}"
+ else
+ s += "--#{@long}"
+ end
+ s += "=#{@typestr || dflt_typestr(@type, @enum)}" unless @type == :flag
+ end
+ s
+ end
+
+ def default_str
+ return @default unless @type == :enum
+ @enum[@default || 0]
+ end
+
+ def help
+ s = @required ? "*" : " "
+ @description ||= "Switch #{switches}"
+ s += @description.gsub(/"/, '\"') || ""
+ default = default_str
+ s += " (#{default})" unless default.nil?
+ s
+ end
+
+ def dump
+ case @type
+ when :flag
+ ["\"#{@var}_flag:\"", "#{@var}_flag"]
+ when :enum
+ ["\"#{@var}_given:\"", "#{@var}_given",
+ "\" #{@var}_arg:\"", "#{@var}_arg", '"|"', "#{@var}::strs[#{@var}_arg]"]
+ else
+ ["\"#{@var}_given:\"", "#{@var}_given",
+ "\" #{@var}_arg:\"", @multiple ? "vec_str(#{@var}_arg)" : "#{@var}_arg"]
+ end
+ end
+
+ def parse_arg(no = false)
+ a = @imply.map { |ios| "#{$opt_hash[ios].var}_flag = true;" }
+ a << "#{@var}_given = true;" unless @type == :flag
+ case @type
+ when :flag
+ if @noflag
+ a << ["#{@var}_flag = #{no ? "false" : "true"};"]
+ else
+ a << ["#{@var}_flag = #{@default == "true" ? "false" : "true"};"]
+ end
+ when :string
+ a << (@multiple ? "#{@var}_arg.push_back(#{str_conv("optarg", @type, false)});" : "#{@var}_arg.assign(optarg);")
+ when :c_string
+ a << (@multiple ? "#{@var}_arg.push_back(#{str_conv("optarg", @type, false)});" : "#{@var}_arg = optarg;")
+ when :uint32, :uint64, :int32, :int64, :int, :long, :double
+ a << (@multiple ? "#{@var}_arg.push_back(#{str_conv("optarg", @type, @suffix)});" : "#{@var}_arg = #{str_conv("optarg", @type, @suffix)};")
+ a << "CHECK_ERR(#{@type}_t, optarg, \"#{switches}\")"
+ when :enum
+ a << "#{@var}_arg = #{str_conv("optarg", @type, "#{@var}::strs")};"
+ a << "CHECK_ERR(#{@type}, optarg, \"#{switches}\")"
+ end
+ a
+ end
+end
+
+class Arg < BaseOptArg
+ attr_accessor :description, :type, :typestr, :multiple, :access_types
+ attr_reader :name, :at_least, :suffix, :var
+ def initialize(str)
+ @name = str
+ @var = @name.gsub(/[^a-zA-Z0-9_]/, "_")
+ @type = nil
+ @at_least = 0
+ @suffix = false
+ @access_types = []
+ end
+
+ def type=(t)
+ super
+ raise "An arg cannot be of type '#{t}'" if t == :flag
+ end
+
+ def on; raise "An arg cannot be a flag with default value on"; end
+ def off; raise "An arg cannot be a flag with default value off"; end
+
+ def default=(*args)
+ raise "An arg cannot have a default value (#{args[0]})"
+ end
+
+ def hidden=(*args)
+ raise "An arg cannot be marked hidden"
+ end
+
+ def secret=(*args)
+ raise "An arg cannot be marked secret"
+ end
+
+ def required=(*args)
+ raise "An arg cannot be marked required"
+ end
+
+ def check
+ super
+
+ pref = "Arg #{name}:"
+ raise "#{pref} No type specified" if type.nil?
+ end
+
+ def var_decl
+ if @multiple
+ c_type = "::std::vector<#{$type_to_C_type[@type]}>"
+ [c_type.ljust($typejust) + " #{@var}_arg;",
+ "typedef #{c_type}::iterator #{@var}_arg_it;",
+ "typedef #{c_type}::const_iterator #{@var}_arg_const_it;"]
+ else
+ ["#{$type_to_C_type[@type]}".ljust($typejust) + " #{@var}_arg;"]
+ end
+ end
+
+ def init
+ s = "#{@var}_arg("
+ s += default_val(@default, @type) unless @multiple
+ s += ")"
+ s
+ end
+
+ def dump
+ ["\"#{@var}_arg:\"",
+ @multiple ? "vec_str(#{@var}_arg)" : "#{@var}_arg"]
+ end
+
+ def parse_arg
+ a = []
+ off = ""
+ if @multiple
+ a << "for( ; optind < argc; ++optind) {"
+ a << " #{@var}_arg.push_back(#{str_conv("argv[optind]", @type, @suffix)});"
+ off = " "
+ else
+ a << "#{@var}_arg = #{str_conv("argv[optind]", @type, @suffix)};"
+ end
+ unless @type == :string || @type == :c_string
+ a << (off + "CHECK_ERR(#{@type}_t, argv[optind], \"#{@var}\")")
+ end
+ a << (@multiple ? "}" : "++optind;")
+ a
+ end
+end
+
+def option(name1, name2 = nil, &b)
+ long = short = nil
+ if name1 =~ /^--/ || name1.length >= 2
+ long, short = name1, name2
+ elsif !name2.nil? && (name2 =~ /^--/ || name2.length >= 2)
+ long, short = name2, name1
+ else
+ long, short = nil, name1
+ end
+
+ long.gsub!(/^--/, "") unless long.nil?
+ short.gsub!(/^-/, "") unless short.nil?
+ o = Option.new(long, short)
+ $options.each { |lo|
+ if (!long.nil? && lo.long == long) || (!short.nil? && lo.short == short)
+ raise "#{b.source_location.join(":")}: Option #{long}|#{short} conflicts with existing option #{lo.long}|#{lo.short}"
+ end
+ }
+ $options << o
+ $target = o
+ name = "Option #{long || ""}|#{short || ""}"
+ run_block(name, b)
+ $target = NoTarget.new
+ begin
+ o.check
+ rescue => e
+ raise "#{b.source_location.join(":")}: #{e.message}"
+ end
+end
+
+def arg(name, &b)
+ a = Arg.new(name)
+ $args.any? { |la| la.name == name } and
+ raise "#{b.source_location.join(":")}: Arg '#{name}' already exists"
+ $args << a
+ $target = a
+ name = "Arg #{name}"
+ run_block(name, b)
+ $target = NoTarget.new
+ begin
+ a.check
+ rescue => e
+ raise "#{b.source_location.join(":")}: #{e.message}"
+ end
+end
diff --git a/lib/yaggo/general.rb b/lib/yaggo/general.rb
new file mode 100644
index 0000000..b477e01
--- /dev/null
+++ b/lib/yaggo/general.rb
@@ -0,0 +1,127 @@
+# This file is part of Yaggo.
+
+# Yaggo 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.
+
+# Yaggo 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 Yaggo. If not, see <http://www.gnu.org/licenses/>.
+
+
+$typejust = 30
+$switchesjust = 40
+
+$type_to_C_type = {
+ :uint32 => "uint32_t",
+ :uint64 => "uint64_t",
+ :int32 => "int32_t",
+ :int64 => "int64_t",
+ :int => "int",
+ :long => "long",
+ :double => "double",
+ :string => "string",
+ :c_string => "const char *",
+ :enum => "int",
+}
+$type_default = {
+ :uint32 => "0",
+ :uint64 => "0",
+ :int32 => "0",
+ :int64 => "0",
+ :int => "0",
+ :long => "0",
+ :double => "0.0",
+ :string => "",
+ :c_string => "",
+ :enum => "0",
+}
+
+def dflt_typestr(type, *argv)
+ case type
+ when :c_string
+ "string"
+ when :enum
+ argv[0].join("|")
+ else
+ type.to_s
+ end
+end
+
+def suffix_arg(suffix)
+ case suffix
+ when true
+ "true"
+ when false
+ "false"
+ when String
+ suffix
+ else
+ raise "Invalid suffix specifier"
+ end
+end
+
+def str_conv(arg, type, *argv)
+ case type
+ when :string
+ "string(#{arg})"
+ when :c_string
+ arg
+ when :uint32, :uint64
+ "conv_uint<#{$type_to_C_type[type]}>((const char*)#{arg}, err, #{suffix_arg(argv[0])})"
+ when :int32, :int64, :long, :int
+ "conv_int<#{$type_to_C_type[type]}>((const char*)#{arg}, err, #{suffix_arg(argv[0])})"
+ when :double
+ "conv_double((const char*)#{arg}, err, #{suffix_arg(argv[0])})"
+ when :enum
+ # Convert a string to its equivalent enum value
+ "conv_enum((const char*)#{arg}, err, #{argv[0]})"
+ end
+end
+
+def find_error_header bt
+ bt.each { |l| l =~ /^\(eval\):\d+:/ and return $& }
+ return ""
+end
+
+def run_block(name, b)
+ eval("#{$option_variables.join(" = ")} = nil", $main_binding)
+ b.call
+ $option_variables.each { |n| eval("#{n} #{n} unless #{n}.nil?", $main_binding) }
+rescue NoMethodError => e
+ header = find_error_header(e.backtrace)
+ raise "#{header} In #{name}: invalid keyword '#{e.name}' in statement '#{e.name} #{e.args.map { |s| "\"#{s}\"" }.join(" ")}'"
+rescue NameError => e
+ header = find_error_header(e.backtrace)
+ raise "#{header} In #{name}: invalid keyword '#{e.name}'"
+rescue RuntimeError, ArgumentError => e
+ header = find_error_header(e.backtrace)
+ raise "#{header} In #{name}: #{e.message}"
+end
+
+
+def check_conflict_exclude
+ $options.each { |o|
+ $opt_hash[o.long] = o unless o.long.nil?
+ $opt_hash[o.short] = o unless o.short.nil?
+ }
+ $options.each { |o|
+ o.conflict.each { |co|
+ $opt_hash[co] or
+ raise "Unknown conflict option '#{co}' for switch #{o.long}|#{o.short}"
+ }
+ }
+ $options.each { |o|
+ o.imply.each { |ios|
+ io = $opt_hash[ios] or
+ raise "Unknown implied option '#{io}' for switch #{o.long}|#{o.short}"
+ io.type == :flag or
+ raise "Implied option '#{io}' for switch #{o.long}|#{o.short} is not a flag"
+ }
+ }
+end
diff --git a/lib/yaggo/library.rb b/lib/yaggo/library.rb
new file mode 100644
index 0000000..4dd7bde
--- /dev/null
+++ b/lib/yaggo/library.rb
@@ -0,0 +1,197 @@
+# This file is part of Yaggo.
+
+# Yaggo 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.
+
+# Yaggo 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 Yaggo. If not, see <http://www.gnu.org/licenses/>.
+
+
+def output_conversion_code file
+ file.puts(<<EOS)
+ static bool adjust_double_si_suffix(double &res, const char *suffix) {
+ if(*suffix == '\\0')
+ return true;
+ if(*(suffix + 1) != '\\0')
+ return false;
+
+ switch(*suffix) {
+ case 'a': res *= 1e-18; break;
+ case 'f': res *= 1e-15; break;
+ case 'p': res *= 1e-12; break;
+ case 'n': res *= 1e-9; break;
+ case 'u': res *= 1e-6; break;
+ case 'm': res *= 1e-3; break;
+ case 'k': res *= 1e3; break;
+ case 'M': res *= 1e6; break;
+ case 'G': res *= 1e9; break;
+ case 'T': res *= 1e12; break;
+ case 'P': res *= 1e15; break;
+ case 'E': res *= 1e18; break;
+ default: return false;
+ }
+ return true;
+ }
+
+ static double conv_double(const char *str, ::std::string &err, bool si_suffix) {
+ char *endptr = 0;
+ errno = 0;
+ double res = strtod(str, &endptr);
+ if(errno) {
+ err.assign(strerror(errno));
+ return (double)0.0;
+ }
+ bool invalid =
+ si_suffix ? !adjust_double_si_suffix(res, endptr) : *endptr != '\\0';
+ if(invalid) {
+ err.assign("Invalid character");
+ return (double)0.0;
+ }
+ return res;
+ }
+
+ static int conv_enum(const char* str, ::std::string& err, const char* const strs[]) {
+ int res = 0;
+ for(const char* const* cstr = strs; *cstr; ++cstr, ++res)
+ if(!strcmp(*cstr, str))
+ return res;
+ err += "Invalid constant '";
+ err += str;
+ err += "'. Expected one of { ";
+ for(const char* const* cstr = strs; *cstr; ++cstr) {
+ if(cstr != strs)
+ err += ", ";
+ err += *cstr;
+ }
+ err += " }";
+ return -1;
+ }
+
+ template<typename T>
+ static bool adjust_int_si_suffix(T &res, const char *suffix) {
+ if(*suffix == '\\0')
+ return true;
+ if(*(suffix + 1) != '\\0')
+ return false;
+
+ switch(*suffix) {
+ case 'k': res *= (T)1000; break;
+ case 'M': res *= (T)1000000; break;
+ case 'G': res *= (T)1000000000; break;
+ case 'T': res *= (T)1000000000000; break;
+ case 'P': res *= (T)1000000000000000; break;
+ case 'E': res *= (T)1000000000000000000; break;
+ default: return false;
+ }
+ return true;
+ }
+
+ template<typename T>
+ static T conv_int(const char *str, ::std::string &err, bool si_suffix) {
+ char *endptr = 0;
+ errno = 0;
+ long long int res = strtoll(str, &endptr, 0);
+ if(errno) {
+ err.assign(strerror(errno));
+ return (T)0;
+ }
+ bool invalid =
+ si_suffix ? !adjust_int_si_suffix(res, endptr) : *endptr != '\\0';
+ if(invalid) {
+ err.assign("Invalid character");
+ return (T)0;
+ }
+ if(res > ::std::numeric_limits<T>::max() ||
+ res < ::std::numeric_limits<T>::min()) {
+ err.assign("Value out of range");
+ return (T)0;
+ }
+ return (T)res;
+ }
+
+ template<typename T>
+ static T conv_uint(const char *str, ::std::string &err, bool si_suffix) {
+ char *endptr = 0;
+ errno = 0;
+ while(isspace(*str)) { ++str; }
+ if(*str == '-') {
+ err.assign("Negative value");
+ return (T)0;
+ }
+ unsigned long long int res = strtoull(str, &endptr, 0);
+ if(errno) {
+ err.assign(strerror(errno));
+ return (T)0;
+ }
+ bool invalid =
+ si_suffix ? !adjust_int_si_suffix(res, endptr) : *endptr != '\\0';
+ if(invalid) {
+ err.assign("Invalid character");
+ return (T)0;
+ }
+ if(res > ::std::numeric_limits<T>::max()) {
+ err.assign("Value out of range");
+ return (T)0;
+ }
+ return (T)res;
+ }
+
+ template<typename T>
+ static ::std::string vec_str(const std::vector<T> &vec) {
+ ::std::ostringstream os;
+ for(typename ::std::vector<T>::const_iterator it = vec.begin();
+ it != vec.end(); ++it) {
+ if(it != vec.begin())
+ os << ",";
+ os << *it;
+ }
+ return os.str();
+ }
+
+ class string : public ::std::string {
+ public:
+ string() : ::std::string() {}
+ explicit string(const ::std::string &s) : std::string(s) {}
+ explicit string(const char *s) : ::std::string(s) {}
+ int as_enum(const char* const strs[]) {
+ ::std::string err;
+ int res = #{str_conv("this->c_str()", :enum, "strs")};
+ if(!err.empty())
+ throw ::std::runtime_error(err);
+ return res;
+ }
+
+
+EOS
+ [:uint32, :uint64, :int32, :int64, :int, :long, :double].each do |type|
+ file.puts(<<EOS)
+ #{$type_to_C_type[type]} as_#{type}_suffix() const { return as_#{type}(true); }
+ #{$type_to_C_type[type]} as_#{type}(bool si_suffix = false) const {
+ ::std::string err;
+ #{$type_to_C_type[type]} res = #{str_conv("this->c_str()", type, "si_suffix")};
+ if(!err.empty()) {
+ ::std::string msg("Invalid conversion of '");
+ msg += *this;
+ msg += "' to #{type}_t: ";
+ msg += err;
+ throw ::std::runtime_error(msg);
+ }
+ return res;
+ }
+EOS
+ end
+
+ file.puts(<<EOS)
+ };
+
+EOS
+# }
+ end
+
diff --git a/lib/yaggo/main.rb b/lib/yaggo/main.rb
new file mode 100644
index 0000000..f665488
--- /dev/null
+++ b/lib/yaggo/main.rb
@@ -0,0 +1,152 @@
+# This file is part of Yaggo.
+
+# Yaggo 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.
+
+# Yaggo 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 Yaggo. If not, see <http://www.gnu.org/licenses/>.
+
+require 'optparse'
+
+require 'yaggo/version'
+require 'yaggo/man_page'
+require 'yaggo/stub'
+require 'yaggo/general'
+require 'yaggo/library'
+require 'yaggo/dsl'
+require 'yaggo/parser'
+require 'yaggo/zsh_completion'
+
+def main
+ $yaggo_options = {
+ :output => nil,
+ :license => nil,
+ :stub => false,
+ :zc => nil,
+ :extended => false,
+ :debug => false,
+ }
+
+ parser = OptionParser.new do |o|
+ o.version = $yaggo_version
+ o.banner = "Usage: #{$0} [options] [file.yaggo]"
+ o.separator ""
+ o.separator "Specific options:"
+
+ o.on("-o", "--output FILE", "Output file") { |v|
+ $yaggo_options[:output] = v
+ }
+ o.on("-l", "--license PATH", "License file to copy in header") { |v|
+ $yaggo_options[:license] = v
+ }
+ o.on("-m", "--man [FILE]", "Display or write manpage") { |v|
+ display_man_page v
+ exit 0;
+ }
+ o.on("-s", "--stub", "Output a stub yaggo file") {
+ $yaggo_options[:stub] = true
+ }
+ o.on("--zc PATH", "Write zsh completion file") { |v|
+ $yaggo_options[:zc] = v
+ }
+ o.on("-e", "--extended-syntax", "Use extended syntax") {
+ $yaggo_options[:extended] = true
+ }
+ o.on("--debug", "Debug yaggo") {
+ $yaggo_options[:debug] = true
+ }
+
+ o.on_tail("-h", "--help", "Show this message") {
+ puts o
+ exit 0
+ }
+ end
+ parser.parse! ARGV
+
+ if $yaggo_options[:stub]
+ begin
+ display_stub_yaggo_file $yaggo_options[:output]
+ rescue => e
+ STDERR.puts("Failed to write stub: #{e.message}")
+ exit 1
+ end
+
+ exit
+ end
+
+ if !$yaggo_options[:stub] && !$yaggo_options[:manual] && ARGV.empty?
+ STDERR.puts "Error: some yaggo files and/or --lib switch is required", parser
+ exit 1
+ end
+ if !$yaggo_options[:output].nil?
+ if $yaggo_options[:stub]
+ if ARGV.size > 0
+ STDERR.puts "Error: no input file needed with the --stub switch", parser
+ exit 1
+ end
+ elsif ARGV.size != 1
+ STDERR.puts "Error: output switch meaningfull only with 1 input file", parser
+ exit 1
+ end
+ end
+
+ ARGV.each do |input_file|
+ pid = fork do
+ begin
+ yaggo_script = File.read(input_file)
+ if $yaggo_options[:extended]
+ yaggo_script.gsub!(/\)\s*\n\s*\{/, ") {")
+ end
+ eval(File.read(input_file))
+ parsed = true
+ check_conflict_exclude
+ rescue RuntimeError, SyntaxError, Errno::ENOENT, Errno::EACCES => e
+ raise e if $yaggo_options[:debug]
+ STDERR.puts(e.message.gsub(/^\(eval\)/, input_file))
+ exit 1
+ rescue NoMethodError => e
+ raise e if $yaggo_options[:debug]
+ STDERR.puts("Invalid keyword '#{e.name}'")
+ exit 1
+ end
+
+ fsplit = File.basename(input_file).split(/\./)
+ $klass ||= fsplit.size > 1 ? fsplit[0..-2].join(".") : fsplit[0]
+ $output = $yaggo_options[:output] if $yaggo_options[:output]
+ $output ||= input_file.gsub(/\.yaggo$/, "") + ".hpp"
+
+ begin
+ out_fd = open($output, "w")
+ output_cpp_parser(out_fd, $klass)
+ rescue RuntimeError => e
+ raise e if $yaggo_options[:debug]
+ STDERR.puts("#{input_file}: #{e.message}")
+ exit 1
+ ensure
+ out_fd.close if out_fd
+ end
+
+ if $yaggo_options[:zc]
+ begin
+ out_fd = open($yaggo_options[:zc], "w")
+ output_zsh_completion(out_fd, $yaggo_options[:zc])
+ rescue RuntimeError => e
+ raise e if $yaggo_options[:debug]
+ STDERR.puts("#{input_file}: #{e.message}")
+ exit 1
+ ensure
+ out_fd.close if out_fd
+ end
+ end
+ end
+ Process.waitpid pid
+ exit 1 if !$?.exited? || ($?.exited? && $?.exitstatus != 0)
+ end
+end
diff --git a/lib/yaggo/man_page.rb b/lib/yaggo/man_page.rb
new file mode 100644
index 0000000..39287eb
--- /dev/null
+++ b/lib/yaggo/man_page.rb
@@ -0,0 +1,449 @@
+# This file is part of Yaggo.
+
+# Yaggo 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.
+
+# Yaggo 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 Yaggo. If not, see <http://www.gnu.org/licenses/>.
+
+require 'pathname'
+
+def display_man_page out
+ manual = <<EOS
+.TH yaggo 1 "2015-06-24" "version #{$yaggo_version}" "USER COMMANDS"
+
+.SH NAME
+yaggo \- command line switch parser generator
+
+.SH SYNOPSIS
+.B yaggo
+[-o|--output FILE] [-l|--license PATH] [-s|--stub] [--zc PATH] [-e|--extended-syntax] [--man] [-h|--help]
+
+.SH DESCRIPTION
+Yaggo stands for Yet Another GenGetOpt. It is inspired by gengetopt
+software from the FSF.
+
+Yaggo generates a C++ class to parse command line switches (usually
+argc and argv passed to main) using getopt_long. The switches and
+arguments to the program are specified in a description file. To each
+description file, yaggo generates one C++ header file containing the
+parsing code.
+.PP
+See the EXAMPLES section for a complete and simple example.
+
+.SH OPTIONS
+.TP
+\-l|\-\-license
+Display the file at the top of the generated headers. It usually
+contains the license governing the distribution of the headers.
+.TP
+-m|\-\-man
+Display this man page
+.TP
+\-s|\-\-stub
+Generate a stub: a simple yaggo file that can be modified for one's use.
+.TP
+\-e|--extended-syntax
+Use the extended syntax: blocks can be defined on the next line of a command.
+.TP
+\-h|--help
+Display a short help text
+.PP
+
+.SH EXAMPLE
+
+Consider the description files 'example_args.yaggo' which defines a
+switch "-i" (or "--int") that takes an unsigned integer and defaults
+to 42; a switch "-s" (or "--string") that takes a string and can be
+given multiple times; a switch "--flag" which does not take any
+argument; a switch "--severity" which can take only 3 values: "low",
+"middle" and "high".
+
+It takes the following arguments: a string followed by zero or more floating point numbers.
+
+.nf
+purpose "Example of yaggo usage"
+package "example"
+description "This is just an example.
+And a multi-line description."
+
+option("int", "i") {
+ description "Integer switch"
+ uint32; default "42" }
+option("string", "s") {
+ description "Many strings"
+ string; multiple }
+option("flag") {
+ description "A flag switch"
+ flag; off }
+option("severity") {
+ description "An enum switch"
+ enum "low", "middle", "high" }
+arg("first") {
+ description "First arg"
+ c_string }
+arg("rest") {
+ description "Rest of'em"
+ double; multiple }
+.fi
+
+The associated simple C++ program 'examples.cpp' which display information about the switches and arguments passed:
+
+.nf
+#include <iostream>
+#include "example_args.hpp"
+
+int main(int argc, char *argv[]) {
+ example_args args(argc, argv);
+
+ std::cout << "Integer switch: " << args.int_arg << "\\\\n";
+ if(args.string_given)
+ std::cout << "Number of string(s): " << args.string_arg.size() << "\\\\n";
+ else
+ std::cout << "No string switch\\\\n";
+ std::cout << "Flag is " << (args.flag_flag ? "on" : "off") << "\\\\n";
+ std::cout << "First arg: " << args.first_arg << "\\\\n";
+ std::cout << "Severity arg: " << args.severity_arg << " " << example_args::severity::strs[args.severity_arg] << "\\\\n";
+ if(args.severity_arg == example_args::severity::high)
+ std::cout << "Warning: severity is high\\\\n";
+ std::cout << "Rest:";
+ for(example_args::rest_arg_it it = args.rest_arg.begin(); it != args.rest_arg.end(); ++it)
+ std::cout << " " << *it;
+ std::cout << std::endl;
+
+ return 0;
+}
+.fi
+
+This can be compiled with the following commands:
+
+.nf
+% yaggo example_args.yaggo
+% g++ -o example example.cpp
+.fi
+
+The yaggo command above will create by default the file
+'example_args.hpp' (changed '.yaggo' extension to '.hpp'). The output
+file name can be changed with the 'output' keyword explained below.
+
+.SH DESCRIPTION FORMAT
+
+A description file is a sequence of statements. A statement is a
+keyword followed by some arguments. Strings must be surrounded by
+quotes ("" or '') and can span multiple lines. The order of the
+statements is irrelevant. Statements are separated by new lines or
+semi-colons ';'.
+
+.IP *
+Technically speaking, yaggo is implemented as a DSL (Domain Specific
+Language) using ruby. The description file is a valid ruby script and
+the keywords are ruby functions.
+.PP
+
+The following statements are global, not attached to a particular option or argument.
+
+.TP
+purpose
+A one line description of the program.
+.TP
+package
+The name of the package for the usage string. Defaults to the name of the class.
+.TP
+usage
+The usage string. If none given a standard one is generated by yaggo.
+.TP
+description
+A longer description of the program displayed before the list of switch. Displayed by the help.
+.TP
+text
+Some text to be displayed after the list of switches. Displayed by the help.
+.TP
+version
+The version string of the software.
+.TP
+license
+The license and copyright string of the software.
+.TP
+name
+The name of the class generated. Defaults to the name of the
+description file minus the .yaggo extension.
+.TP
+posix
+Posix correct behavior (instead of GNU behavior): switch processing
+stops at the first non-option argument
+.TP
+output
+The name of the output file. Defaults to the name of the
+description file with the .yaggo extension changed to .hpp.
+.PP
+
+The 'option' statement takes one or two arguments, which must be in
+parentheses, and a block of statements surrounded by curly braces
+({...}). The arguments are the long and short version of the
+option. Either one of the long or short version can be omitted. The
+block of statements describe the option in more details, as described
+below.
+
+A switch is named after the long version, or the short version if no
+long version. An 'option' statement for an option named 'switch'
+defines one or two public members in the class. For a flag, it
+creates 'switch_flag' as a boolean. Otherwise, it
+creates 'switch_arg', with a type as specified, and 'switch_given', a
+boolean indicating whether or not the switch was given on the command
+line.
+
+For example, the statement:
+
+.nf
+option("integer", "i") {
+ int; default 5
+}
+.fi
+
+will add the following members to the C++ class:
+
+.nf
+int integer_arg;
+bool integer_given;
+.fi
+
+where "integer_arg" is initialized to 5 and "integer_given" is
+initialized to "false". If the switch "--integer 10" or "-i 10" is
+passed on the command line "integer_arg" is set to 10 and
+integer_given is set to "true".
+
+The statement:
+
+.nf
+option("verbose") {
+ off
+}
+.fi
+
+will add the following member to the C++ class:
+
+.nf
+bool verbose_flag;
+.fi
+
+where "verbose_flag" is initialized to "false". Passing the switch
+"--verbose" on the command line sets "verbose_flag" to true".
+
+
+In addition to the switch created by 'option', the following switches
+are defined by default (unless some option statement overrides them):
+
+.TP
+\-h, \-\-help
+Display the help message.
+.TP
+\-\-full\-help
+Display hidden options as well.
+.TP
+\-\-version
+Display version string.
+.PP
+
+The following statement are recognized in an option block:
+
+.TP
+description "str"
+A short description for this switch.
+
+.TP
+int32, int64, uint32, uint64, double, int, long
+This switch is parsed as a number with the corresponding type int32_t,
+int64_t, uint32_t, uint64_t, double, int and long.
+
+.TP
+suffix
+Valid for numerical type switches as above. It can be appended
+with a SI suffix (e.g. 1M mean 1000000). The suffixes k, M, G, T, P,
+and E are supported for all the numerical types. The suffixes m, u, n,
+p, f, and a are supported for the double type.
+
+.TP
+c_string, string
+This switch is taken as a C string (const char *) or a C++ string
+(inherits from std::string). The C++ string type has the extra
+methods '<type> as_<type>(bool suffix)', where <type> is any numerical
+type as above, to convert the string into that type. If the 'suffix'
+boolean is true, parsing is done using SI suffixes.
+
+.TP
+enum
+This statement must be followed by a comma separated list of strings
+(as in 'enum "choice0", "choice1", "choice2"'). This switch takes value
+a string in the list and is converted to int. C enum type named
+"switchname::enum" is defined with the same choices in the given order.
+
+.TP
+required
+This switch is required. An error is generated if not given on the
+command line.
+.TP
+conflict
+Specify a comma separated list of switches that conflicts with this
+one.
+.TP
+imply
+Specify a comma separated list of switches (of type flag) which are
+implied by this one.
+.TP
+hidden
+This switch is not shown with --help. Use --full-help to see the
+hidden switches, if any.
+.TP
+secret
+This switch is not shown in any help message. Neither --help nor
+--full-help.
+.TP
+multiple
+This switch can be passed multiple times. The values are stored in a
+std::vector. A type for the iterator is also defined in the class with
+the name 'switch_arg_it', where 'switch' is the name of the option.
+.TP
+flag
+This switch is a flag and does not take an argument.
+.TP
+on, off
+The default state for a flag switch. Implies flag. Unless the 'no'
+option is used (see below), with 'off', the default value of the flag
+is "false" and passing --flag sets it to true. With 'on', the default
+value of the flag is "true" and passing --flag sets it to false.
+.TP
+no
+A flag with two switches. If the switch is named "flag", two switches
+are generated: --flag and --noflag, respectively setting it to "true"
+and "false". The 'on' and 'off' options define the default value.
+.TP
+default "val"
+The default value for this switch. It can be a string or a valid
+number. SI suffixes are supported as well (for example "1M" means 1
+m`illion).
+.TP
+typestr "str"
+In the help message, by default, the type of the option is
+displayed. It can be replaced by the string given to 'typestr'.
+.TP
+at_least n
+The given switch must be given at least n times. Implies multiple.
+.TP
+access "type"
+Make sure that the string passed is a path to which we have
+access. "type" is a comma separated list of "read", "write" or
+"exec". It is checked with access(2). The same warning applies:
+
+"Warning: Using access() to check if a user is authorized to, for
+example, open a file before actually doing so using open(2) creates a
+security hole, because the user might exploit the short time interval
+between checking and opening the file to manipulate it. For this
+reason, the use of this system call should be avoided. (In the
+example just described, a safer alternative would be to temporarily
+switch the process's effective user ID to the real ID and then call
+open(2).)"
+
+.PP
+
+A 'arg' statement defines an arg passed to the command line. The
+statement takes a single argument, the name of the arg, and a block of
+statements. The block of statements are similar to the option block,
+except that "hidden", "flag", "on", "off" and "no" are not allowed. At
+most one arg can have the 'multiple' statement, and it must be the
+last one.
+
+.SH EXAMPLE USAGE
+
+The argument object parses the switches on construction or later on
+using the parse method. For example, the two pieces code show these
+two different usage.
+
+Using parse method:
+.nf
+ example_args args; // Global variable with switches
+
+ int main(int argc, char* argv[]) {
+ args.parse(argc, argv);
+ }
+.fi
+
+Parse on construction:
+.nf
+ int main(int argc, char* argv[]) {
+ example_args args(argc, argv);
+ }
+.fi
+
+The subclass error can be used to output error messsage (and terminate
+program). It output an error message, the usage string, etc. The error
+class behave like an output stream, it can be used to create
+complicated error message. For example:
+
+.nf
+ if(false_condition)
+ example_args::error() << "Failed to open file '" << args.file_arg << "'";
+.fi
+
+An error object prints an error message and terminate the program with
+exit upon destruction. An exit code can be passed to error. By default
+the exit code (passed to exit) is the constant EXIT_FAILURE (normally
+1). For example:
+
+.nf
+ example_args::error(77) << "Failed with return code 77";
+.fi
+
+.SH LICENSE
+
+There are 2 parts to the software: the yaggo ruby script itself, and
+the header files generated by yaggo from the description files. The
+licenses are as follow:
+
+.TP
+yaggo the ruby script
+This software is licensed under the GNU General
+Public License version 3 or any later version. Copyright (c) 2011
+Guillaume Marcais.
+
+.TP The generated header files. These files have the license and
+copyright that you, the user of yaggo, assign with the 'license'
+keyword. .PP In short: only yaggo the software is GPL. The generated
+header files are considered derivative of your work (e.g. the
+description), and you define the copyright and license of those as you
+see fit.
+
+.SH BUGS
+.IP *
+The error message returned by ruby can be a little confusing.
+
+.SH AUTHOR
+Guillaume Marcais (gmarcais at umd.edu)
+.SH SEE ALSO
+getopt_long(3), gengetopt(1), exit(2)
+EOS
+
+ if !out && STDOUT.isatty
+ require 'tempfile'
+ Tempfile.open("yaggo_man") do |fd|
+ begin
+ fd.write(manual)
+ fd.flush
+ system("man", fd.path)
+ ensure
+ fd.unlink
+ end
+ end
+ elsif !out
+ STDOUT.puts(manual)
+ else
+ path = Pathname.new(out)
+ path.write manual
+ end
+end
diff --git a/lib/yaggo/parser.rb b/lib/yaggo/parser.rb
new file mode 100644
index 0000000..9bbdd8b
--- /dev/null
+++ b/lib/yaggo/parser.rb
@@ -0,0 +1,404 @@
+# This file is part of Yaggo.
+
+# Yaggo 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.
+
+# Yaggo 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 Yaggo. If not, see <http://www.gnu.org/licenses/>.
+
+def quote_newline_dquotes str, spaces = ""
+ str.gsub(/"/, '\\"').split(/\n/).join("\\n\" \\\n#{spaces}\"")
+end
+
+def output_options_descriptions out, opts, hidden
+ opts.each { |o|
+ # need to be improved. break lines if too long
+ next if o.secret || (o.hidden ^ hidden)
+ s = " " + o.switches
+ if s.size >= $switchesjust
+ s += "\\n" + "".ljust($switchesjust)
+ else
+ s = s.ljust($switchesjust)
+ end
+ out.puts(" \"#{s} #{o.help}\\n\"")
+ }
+end
+
+def output_cpp_parser(h, class_name)
+ $options.each { |o| o.check }
+ $args.each { |a| a.check }
+ if $args.size > 1
+ mul_args = $args[0..-2].select { |a| a.multiple }
+ if mul_args.size > 0
+ gram = mul_args.size > 1 ? "s are" : " is"
+ raise "The following#{gram} not the last arg but marked multiple: #{mul_args.map { |a| a.name }.join(", ")}"
+ end
+ end
+
+ # Headers
+
+ h.puts(<<EOS)
+/***** This code was generated by Yaggo. Do not edit ******/
+
+EOS
+
+ if $license
+ lines = $license.split(/\n/)
+ h.puts("/* #{lines[0]}", *(lines[1..-1].map { |l| " * " + l }))
+ h.puts(" */", "")
+ elsif $yaggo_options[:license]
+ open($yaggo_options[:license]) { |fd|
+ h.puts(fd.read)
+ }
+ h.puts("")
+ end
+
+h.puts(<<EOS)
+#ifndef __#{class_name.upcase()}_HPP__
+#define __#{class_name.upcase()}_HPP__
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <errno.h>
+#include <string.h>
+#include <stdexcept>
+#include <string>
+#include <limits>
+#include <vector>
+#include <iostream>
+#include <sstream>
+#include <memory>
+
+class #{class_name} {
+ // Boiler plate stuff. Conversion from string to other formats
+EOS
+
+ output_conversion_code h
+
+ h.puts(<<EOS)
+public:
+EOS
+
+ static_decl = $options.map { |o| o.static_decl }.flatten
+ h.puts(" " + static_decl.join("\n "), "") unless static_decl.empty?
+
+ ($options + $args).each { |o| h.puts(" " + o.var_decl.join("\n ")) }
+ h.puts("")
+
+ # Create enum if option with no short version
+ only_long = $options.map { |o| o.long_enum }.flatten.compact
+ need_full = $options.any? { |o| o.hidden }
+
+ help_no_h = $options.any? { |o| o.short == "h" }
+ version_no_V = $options.any? { |o| o.short == "V" }
+ usage_no_U = $options.any? { |o| o.short == "U" }
+ h.print(" enum {\n START_OPT = 1000")
+ h.print(",\n FULL_HELP_OPT") if need_full
+ h.print(",\n HELP_OPT") if help_no_h
+ h.print(",\n VERSION_OPT") if version_no_V
+ h.print(",\n USAGE_OPT") if usage_no_U
+ if only_long.empty?
+ h.puts("\n };")
+ else
+ h.puts(",", " " + only_long.join(",\n "), " };")
+ end
+
+ # Constructors and initialization
+ h.puts("", " #{class_name}() :")
+ h.puts(" " + ($options + $args).map { |o| o.init }.join(",\n "), " { }")
+ h.puts("", " #{class_name}(int argc, char* argv[]) :")
+ h.puts(" " + ($options + $args).map { |o| o.init }.join(",\n "))
+ h.puts(" { parse(argc, argv); }", "");
+
+ # Main arsing function
+ h.puts(" void parse(int argc, char* argv[]) {",
+ " static struct option long_options[] = {")
+ $options.empty? or
+ h.puts(" " + $options.map { |o| o.struct }.flatten.join(",\n ") + ",")
+ h.puts(" {\"help\", 0, 0, #{help_no_h ? "HELP_OPT" : "'h'"}},")
+ h.puts(" {\"full-help\", 0, 0, FULL_HELP_OPT},") if need_full
+ h.puts(" {\"usage\", 0, 0, #{usage_no_U ? "USAGE_OPT" : "'U'"}},",
+ " {\"version\", 0, 0, #{version_no_V ? "VERSION_OPT" : "'V'"}},",
+ " {0, 0, 0, 0}", " };")
+ short_str = $posix ? "+" : ""
+ short_str += "h" unless help_no_h
+ short_str += "V" unless version_no_V
+ short_str += "U" unless usage_no_U
+ short_str += $options.map { |o| o.short_str }.compact.join("")
+
+ h.puts(" static const char *short_options = \"#{short_str}\";", "")
+
+ need_err = $options.any? { |o| o.type != :flag && o.type != :string && o.type != :c_string}
+ need_err ||= $args.any? { |a| a.type != :string && a.type != :c_string }
+ need_err ||= ($options + $args).any? { |o| !o.access_types.empty? }
+ h.puts(" ::std::string err;") if need_err
+
+ # Actual parsing
+ h.puts(<<EOS)
+#define CHECK_ERR(type,val,which) if(!err.empty()) { ::std::cerr << "Invalid " #type " '" << val << "' for [" which "]: " << err << "\\n"; exit(1); }
+ while(true) {
+ int index = -1;
+ int c = getopt_long(argc, argv, short_options, long_options, &index);
+ if(c == -1) break;
+ switch(c) {
+ case ':':
+ ::std::cerr << \"Missing required argument for \"
+ << (index == -1 ? ::std::string(1, (char)optopt) : std::string(long_options[index].name))
+ << ::std::endl;
+ exit(1);
+ case #{help_no_h ? "HELP_OPT" : "'h'"}:
+ ::std::cout << usage() << \"\\n\\n\" << help() << std::endl;
+ exit(0);
+ case #{usage_no_U ? "USAGE_OPT" : "'U'"}:
+ ::std::cout << usage() << \"\\nUse --help for more information.\" << std::endl;
+ exit(0);
+ case 'V':
+ print_version();
+ exit(0);
+ case '?':
+ ::std::cerr << \"Use --usage or --help for some help\\n\";
+ exit(1);
+EOS
+ if need_full
+ h.puts(<<EOS)
+ case FULL_HELP_OPT:
+ ::std::cout << usage() << \"\\n\\n\" << help() << \"\\n\\n\" << hidden() << std::flush;
+ exit(0);
+EOS
+ end
+
+ $options.each { |o|
+ if o.type == :flag && o.noflag
+ h.puts(" case #{o.long_enum[0]}:",
+ " " + o.parse_arg.join("\n "),
+ " break;",
+ " case #{o.long_enum[1]}:",
+ " " + o.parse_arg(true).join("\n "),
+ " break;")
+ else
+ h.puts(" case #{o.long_enum ? o.long_enum[0] : "'" + o.short + "'"}:",
+ " " + o.parse_arg.join("\n "),
+ " break;")
+ end
+ }
+ h.puts(" }", # close case
+ " }") # close while(true)
+
+ # Check required
+ $options.any? { |o| o.required} and
+ h.puts("", " // Check that required switches are present")
+ $options.each { |o|
+ next unless o.required
+ h.puts(<<EOS)
+ if(!#{o.var}_given)
+ error("[#{o.switches}] required switch");
+EOS
+ }
+ # Check conflict
+ $options.any? { |o| !o.conflict.empty? } and
+ h.puts("", " // Check mutually exlusive switches")
+ $options.each { |o|
+ o_check = o.var + (o.type == :flag ? "_flag" : "_given")
+ o.conflict.each { |cos|
+ co = $opt_hash[cos]
+ co_check = co.var + (co.type == :flag ? "_flag" : "_given")
+ h.puts(<<EOS)
+ if(#{o_check} && #{co_check})
+ error("Switches [#{o.switches}] and [#{co.switches}] are mutually exclusive");
+EOS
+ }
+ }
+ # Check at_least
+ $options.any? { |o| o.at_least } and
+ h.puts("", " // Check at_least requirements")
+ $options.each { |o|
+ next unless o.multiple && !o.at_least.nil?
+ h.puts(<<EOS)
+ if(#{o.var}_arg.size() < #{o.at_least})
+ error("[#{o.switches}] must be given at least #{o.at_least} times");
+EOS
+ }
+
+ # Parse arguments
+ h.puts("", " // Parse arguments")
+ if $args.size == 0 || !$args[-1].multiple
+ h.puts(<<EOS)
+ if(argc - optind != #{$args.size})
+ error("Requires exactly #{$args.size} argument#{$args.size > 1 ? "s" : ""}.");
+EOS
+ else
+ min_args = $args.size - 1 + $args[-1].at_least
+ h.puts(<<EOS)
+ if(argc - optind < #{min_args})
+ error("Requires at least #{min_args} argument#{min_args > 1 ? "s" : ""}.");
+EOS
+ end
+ $args.each { |a| h.puts(" " + a.parse_arg.join("\n ")) }
+
+ # Check access rights
+ if ($options + $args).any? { |o| !o.access_types.empty? }
+ r_to_f = { "read" => "R_OK", "write" => "W_OK", "exec" => "X_OK" }
+ h.puts("", " // Check access rights")
+ ($args + $options).each { |o|
+ next if o.access_types.empty?
+ mode = o.access_types.map { |t| r_to_f[t] }.join("|")
+ msg = Arg === o ? "Argument " + o.name : "Switch " + o.switches
+ msg += ", access right (#{o.access_types.join("|")}) failed for file '"
+ h.puts(" if(access(#{o.var}_arg, #{mode})) {",
+ " err = \"#{msg}\";",
+ " ((err += #{o.var}_arg) += \"': \") += strerror(errno);",
+ " error(err.c_str());",
+ " }")
+ }
+ end
+
+ h.puts(" }") # close parser
+
+ # Usage
+ if !$usage.nil?
+ ausage = quote_newline_dquotes($usage, " ")
+ else
+ ausage = "Usage: #{$package || class_name} [options]"
+ $args.each { |a|
+ ausage += " #{a.name}:#{a.typestr || dflt_typestr(a.type)}#{a.multiple ? "+" : ""}"
+ }
+ end
+
+ h.puts(<<EOS)
+ static const char * usage() { return "#{ausage}"; }
+ class error {
+ int code_;
+ std::ostringstream msg_;
+
+ // Select the correct version (GNU or XSI) version of
+ // strerror_r. strerror_ behaves like the GNU version of strerror_r,
+ // regardless of which version is provided by the system.
+ static const char* strerror__(char* buf, int res) {
+ return res != -1 ? buf : "Invalid error";
+ }
+ static const char* strerror__(char* buf, char* res) {
+ return res;
+ }
+ static const char* strerror_(int err, char* buf, size_t buflen) {
+ return strerror__(buf, strerror_r(err, buf, buflen));
+ }
+ struct no_t { };
+
+ public:
+ static no_t no;
+ error(int code = EXIT_FAILURE) : code_(code) { }
+ explicit error(const char* msg, int code = EXIT_FAILURE) : code_(code)
+ { msg_ << msg; }
+ error(const std::string& msg, int code = EXIT_FAILURE) : code_(code)
+ { msg_ << msg; }
+ error& operator<<(no_t) {
+ char buf[1024];
+ msg_ << ": " << strerror_(errno, buf, sizeof(buf));
+ return *this;
+ }
+ template<typename T>
+ error& operator<<(const T& x) { msg_ << x; return (*this); }
+ ~error() {
+ ::std::cerr << "Error: " << msg_.str() << "\\n"
+ << usage() << "\\n"
+ << "Use --help for more information"
+ << ::std::endl;
+ exit(code_);
+ }
+ };
+EOS
+
+ # Help
+ desc = ""
+ unless $purpose.nil?
+ desc += $purpose + "\\n\\n"
+ end
+ unless $description.nil?
+ desc += $description.split(/\n/).join("\\n\" \\\n \"") + "\\n\\n"
+ end
+
+ h.puts(<<EOS)
+ static const char * help() { return
+ "#{desc}"
+ "Options (default value in (), *required):\\n"
+EOS
+ output_options_descriptions(h, $options, false)
+ usage_switch = " -U, "
+ usage_switch = " " * usage_switch.size if usage_no_U
+ usage_switch += "--usage"
+ h.puts(" \"#{usage_switch.ljust($switchesjust)} Usage\\n\"")
+ help_switch = " -h, "
+ help_switch = " " * help_switch.size if help_no_h
+ help_switch += "--help"
+ h.puts(" \"#{help_switch.ljust($switchesjust)} This message\\n\"")
+ h.puts(" \"#{" --full-help".ljust($switchesjust)} Detailed help\\n\"") if need_full
+ version_switch = " -V, "
+ version_switch = " " * version_switch.size if version_no_V
+ version_switch += "--version"
+ h.print(" \"#{version_switch.ljust($switchesjust)} Version")
+ if $after_text.nil?
+ h.puts("\";")
+ else
+ h.puts("\\n\" \\", " \"\\n\"")
+ atext = quote_newline_dquotes($after_text, " ")
+ h.puts(" \"#{atext}\";")
+ end
+ h.puts(" }")
+
+ # Hidden help
+ has_hidden = $options.any? { |o| o.hidden }
+ if has_hidden
+ h.puts(<<EOS)
+ static const char* hidden() { return
+ "Hidden options:\\n"
+EOS
+ output_options_descriptions(h, $options, true)
+ h.puts(<<EOS)
+ "";
+ }
+EOS
+ else
+ h.puts(<<EOS)
+ static const char* hidden() { return ""; }
+EOS
+ end
+
+
+ # Version
+ h.puts(" void print_version(::std::ostream &os = std::cout) const {",
+ "#ifndef PACKAGE_VERSION",
+ "#define PACKAGE_VERSION \"0.0.0\"",
+ "#endif",
+ " os << #{$version ? "\"" + $version + "\"" : "PACKAGE_VERSION"} << \"\\n\";",
+ " }")
+
+ # Dump
+ h.puts(" void dump(::std::ostream &os = std::cout) {")
+ ($options + $args).each { |o| h.puts(" os << #{o.dump.join(" << ")} << \"\\n\";") }
+ h.puts(" }")
+
+ # Private methods
+ h.puts(<<EOS)
+};
+EOS
+
+ # Initialize static members
+ # TODO: Should we have an option to put this in a .cc file?
+ $options.each { |o|
+ next unless o.type == :enum
+ h.puts("const char* const #{class_name}::#{o.var}::strs[#{o.enum.size + 1}] = { #{o.enum.map { |x| "\"#{x}\"" }.join(", ") }, (const char*)0 };")
+ }
+
+h.puts(<<EOS)
+#endif // __#{class_name.upcase}_HPP__"
+EOS
+end
diff --git a/lib/yaggo/stub.rb b/lib/yaggo/stub.rb
new file mode 100644
index 0000000..b40a84b
--- /dev/null
+++ b/lib/yaggo/stub.rb
@@ -0,0 +1,42 @@
+# This file is part of Yaggo.
+
+# Yaggo 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.
+
+# Yaggo 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 Yaggo. If not, see <http://www.gnu.org/licenses/>.
+
+
+def display_stub_yaggo_file file
+ stub = <<EOS
+# Stub file generated by yaggo. Modify to your liking
+purpose = "Foo software to do bar and baz, one line description"
+description = "A longer multiline description of how Foo does bar and baz
+
+Really, it works great, you should try all the options below
+"
+
+option("b", "bar") {
+ description "Insist on bar"
+ flag }
+option("z", "baz") {
+ description "Baz parameter"
+ int64; default "5" }
+option("l", "long") {
+ description "Long switch can be used multiple time"
+ int32; multiple }
+arg("OneArg") {
+ description "first arg"
+ string }
+EOS
+
+ out = file ? open(file, "W") : STDOUT
+ out.write(stub)
+end
diff --git a/lib/yaggo/version.rb b/lib/yaggo/version.rb
new file mode 100644
index 0000000..02d2e89
--- /dev/null
+++ b/lib/yaggo/version.rb
@@ -0,0 +1 @@
+$yaggo_version = "1.5.9"
diff --git a/lib/yaggo/zsh_completion.rb b/lib/yaggo/zsh_completion.rb
new file mode 100644
index 0000000..5ab3e24
--- /dev/null
+++ b/lib/yaggo/zsh_completion.rb
@@ -0,0 +1,94 @@
+# This file is part of Yaggo.
+
+# Yaggo 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.
+
+# Yaggo 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 Yaggo. If not, see <http://www.gnu.org/licenses/>.
+
+
+def zsh_conflict_option o
+ conflict_options = o.conflict + $options.map { |co|
+ (co.conflict.include?(o.short) || co.conflict.include?(o.long)) ? (co.short || co.long) : nil
+ }.compact.uniq
+ return "" if conflict_options.empty?
+ "'(" + conflict_options.map { |co_name|
+ co = $opt_hash[co_name]
+ [co.short && "-#{co.short}", co.long && "--#{co.long}"]
+ }.flatten.compact.uniq.join(" ") + ")'"
+end
+
+def zsh_switches_option o
+ switches = if o.type == :flag
+ [o.short && "-#{o.short}", o.long && "--#{o.long}"]
+ else
+ [o.short && "-#{o.short}+", o.long && "--#{o.long}="]
+ end
+ switches.compact!
+ swstr = switches.size > 1 ? "{#{switches.join(",")}}" : switches[0]
+ swstr = "\\*#{swstr}" if o.multiple
+ swstr
+end
+
+def zsh_type_completion o, with_type = true
+ typedescr = o.typestr || o.type.id2name
+ typename = with_type ? ":" + typedescr : ""
+ guard_help = "#{typedescr} #{o.description || ""}"
+ case o.type
+ when :flag
+ return ""
+ when :enum
+ return "#{typename}:(#{o.enum.join(" ")})"
+ when :string, :c_string
+ case o.typestr || ""
+ when /file|path/i
+ return "#{typename}:_files"
+ when /dir/i
+ return "#{typename}:_files -/"
+ else
+ return typename
+ end
+ when :int32, :int64, :int, :long
+ suffixes = o.suffix ? "[kMGTPE]" : ""
+ return "#{typename}:_guard \"[0-9+-]##{suffixes}\" \"#{guard_help}\""
+ when :uint32, :uint64
+ suffixes = o.suffix ? "[kMGTPE]" : ""
+ return "#{typename}:_guard \"[0-9+]##{suffixes}\" \"#{guard_help}\""
+ when :double
+ suffixes = "[munpfakMGTPE]" if o.suffix
+ return "#{typename}:_guard \"[0-9.eE+-]##{suffixes}\" \"#{guard_help}\""
+ else
+ return default
+ end
+end
+
+def output_zsh_completion(fd, filename)
+ cmdname = File.basename(filename).gsub(/^_/, "")
+
+ fd.puts("#compdef #{cmdname}", "",
+ "local context state state_descr line",
+ "typeset -A opt_args", "")
+ return if $options.empty? && $args.empty?
+ fd.puts("_arguments -s -S \\")
+ $options.each { |o|
+ conflicts = zsh_conflict_option o
+ switches = zsh_switches_option o
+ descr = o.description ? "[#{o.description}]" : ""
+ action = zsh_type_completion o, true
+ fd.puts("#{conflicts}#{switches}'#{descr}#{action}' \\")
+ }
+ $args.each { |a|
+ descr = a.description || " "
+ action = zsh_type_completion a, false
+ many = a.multiple ? "*" : ""
+ fd.puts("'#{many}:#{descr}#{action}' \\")
+ }
+ fd.puts(" && return 0")
+end
diff --git a/license-header.txt b/license-header.txt
new file mode 100644
index 0000000..4c87ed0
--- /dev/null
+++ b/license-header.txt
@@ -0,0 +1,14 @@
+# This file is part of Yaggo.
+
+# Yaggo 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.
+
+# Yaggo 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 Yaggo. If not, see <http://www.gnu.org/licenses/>.
diff --git a/setup.rb b/setup.rb
new file mode 100644
index 0000000..424a5f3
--- /dev/null
+++ b/setup.rb
@@ -0,0 +1,1585 @@
+#
+# setup.rb
+#
+# Copyright (c) 2000-2005 Minero Aoki
+#
+# This program is free software.
+# You can distribute/modify this program under the terms of
+# the GNU LGPL, Lesser General Public License version 2.1.
+#
+
+unless Enumerable.method_defined?(:map) # Ruby 1.4.6
+ module Enumerable
+ alias map collect
+ end
+end
+
+unless File.respond_to?(:read) # Ruby 1.6
+ def File.read(fname)
+ open(fname) {|f|
+ return f.read
+ }
+ end
+end
+
+unless Errno.const_defined?(:ENOTEMPTY) # Windows?
+ module Errno
+ class ENOTEMPTY
+ # We do not raise this exception, implementation is not needed.
+ end
+ end
+end
+
+def File.binread(fname)
+ open(fname, 'rb') {|f|
+ return f.read
+ }
+end
+
+# for corrupted Windows' stat(2)
+def File.dir?(path)
+ File.directory?((path[-1,1] == '/') ? path : path + '/')
+end
+
+
+class ConfigTable
+
+ include Enumerable
+
+ def initialize(rbconfig)
+ @rbconfig = rbconfig
+ @items = []
+ @table = {}
+ # options
+ @install_prefix = nil
+ @config_opt = nil
+ @verbose = true
+ @no_harm = false
+ end
+
+ attr_accessor :install_prefix
+ attr_accessor :config_opt
+
+ attr_writer :verbose
+
+ def verbose?
+ @verbose
+ end
+
+ attr_writer :no_harm
+
+ def no_harm?
+ @no_harm
+ end
+
+ def [](key)
+ lookup(key).resolve(self)
+ end
+
+ def []=(key, val)
+ lookup(key).set val
+ end
+
+ def names
+ @items.map {|i| i.name }
+ end
+
+ def each(&block)
+ @items.each(&block)
+ end
+
+ def key?(name)
+ @table.key?(name)
+ end
+
+ def lookup(name)
+ @table[name] or setup_rb_error "no such config item: #{name}"
+ end
+
+ def add(item)
+ @items.push item
+ @table[item.name] = item
+ end
+
+ def remove(name)
+ item = lookup(name)
+ @items.delete_if {|i| i.name == name }
+ @table.delete_if {|name, i| i.name == name }
+ item
+ end
+
+ def load_script(path, inst = nil)
+ if File.file?(path)
+ MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path
+ end
+ end
+
+ def savefile
+ '.config'
+ end
+
+ def load_savefile
+ begin
+ File.foreach(savefile()) do |line|
+ k, v = *line.split(/=/, 2)
+ self[k] = v.strip
+ end
+ rescue Errno::ENOENT
+ setup_rb_error $!.message + "\n#{File.basename($0)} config first"
+ end
+ end
+
+ def save
+ @items.each {|i| i.value }
+ File.open(savefile(), 'w') {|f|
+ @items.each do |i|
+ f.printf "%s=%s\n", i.name, i.value if i.value? and i.value
+ end
+ }
+ end
+
+ def load_standard_entries
+ standard_entries(@rbconfig).each do |ent|
+ add ent
+ end
+ end
+
+ def standard_entries(rbconfig)
+ c = rbconfig
+
+ rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT'])
+
+ major = c['MAJOR'].to_i
+ minor = c['MINOR'].to_i
+ teeny = c['TEENY'].to_i
+ version = "#{major}.#{minor}"
+
+ # ruby ver. >= 1.4.4?
+ newpath_p = ((major >= 2) or
+ ((major == 1) and
+ ((minor >= 5) or
+ ((minor == 4) and (teeny >= 4)))))
+
+ if c['rubylibdir']
+ # V > 1.6.3
+ libruby = "#{c['prefix']}/lib/ruby"
+ librubyver = c['rubylibdir']
+ librubyverarch = c['archdir']
+ siteruby = c['sitedir']
+ siterubyver = c['sitelibdir']
+ siterubyverarch = c['sitearchdir']
+ elsif newpath_p
+ # 1.4.4 <= V <= 1.6.3
+ libruby = "#{c['prefix']}/lib/ruby"
+ librubyver = "#{c['prefix']}/lib/ruby/#{version}"
+ librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
+ siteruby = c['sitedir']
+ siterubyver = "$siteruby/#{version}"
+ siterubyverarch = "$siterubyver/#{c['arch']}"
+ else
+ # V < 1.4.4
+ libruby = "#{c['prefix']}/lib/ruby"
+ librubyver = "#{c['prefix']}/lib/ruby/#{version}"
+ librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
+ siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby"
+ siterubyver = siteruby
+ siterubyverarch = "$siterubyver/#{c['arch']}"
+ end
+ parameterize = lambda {|path|
+ path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')
+ }
+
+ if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
+ makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
+ else
+ makeprog = 'make'
+ end
+
+ [
+ ExecItem.new('installdirs', 'std/site/home',
+ 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\
+ {|val, table|
+ case val
+ when 'std'
+ table['rbdir'] = '$librubyver'
+ table['sodir'] = '$librubyverarch'
+ when 'site'
+ table['rbdir'] = '$siterubyver'
+ table['sodir'] = '$siterubyverarch'
+ when 'home'
+ setup_rb_error '$HOME was not set' unless ENV['HOME']
+ table['prefix'] = ENV['HOME']
+ table['rbdir'] = '$libdir/ruby'
+ table['sodir'] = '$libdir/ruby'
+ end
+ },
+ PathItem.new('prefix', 'path', c['prefix'],
+ 'path prefix of target environment'),
+ PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
+ 'the directory for commands'),
+ PathItem.new('libdir', 'path', parameterize.call(c['libdir']),
+ 'the directory for libraries'),
+ PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
+ 'the directory for shared data'),
+ PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
+ 'the directory for man pages'),
+ PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
+ 'the directory for system configuration files'),
+ PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']),
+ 'the directory for local state data'),
+ PathItem.new('libruby', 'path', libruby,
+ 'the directory for ruby libraries'),
+ PathItem.new('librubyver', 'path', librubyver,
+ 'the directory for standard ruby libraries'),
+ PathItem.new('librubyverarch', 'path', librubyverarch,
+ 'the directory for standard ruby extensions'),
+ PathItem.new('siteruby', 'path', siteruby,
+ 'the directory for version-independent aux ruby libraries'),
+ PathItem.new('siterubyver', 'path', siterubyver,
+ 'the directory for aux ruby libraries'),
+ PathItem.new('siterubyverarch', 'path', siterubyverarch,
+ 'the directory for aux ruby binaries'),
+ PathItem.new('rbdir', 'path', '$siterubyver',
+ 'the directory for ruby scripts'),
+ PathItem.new('sodir', 'path', '$siterubyverarch',
+ 'the directory for ruby extentions'),
+ PathItem.new('rubypath', 'path', rubypath,
+ 'the path to set to #! line'),
+ ProgramItem.new('rubyprog', 'name', rubypath,
+ 'the ruby program using for installation'),
+ ProgramItem.new('makeprog', 'name', makeprog,
+ 'the make program to compile ruby extentions'),
+ SelectItem.new('shebang', 'all/ruby/never', 'ruby',
+ 'shebang line (#!) editing mode'),
+ BoolItem.new('without-ext', 'yes/no', 'no',
+ 'does not compile/install ruby extentions')
+ ]
+ end
+ private :standard_entries
+
+ def load_multipackage_entries
+ multipackage_entries().each do |ent|
+ add ent
+ end
+ end
+
+ def multipackage_entries
+ [
+ PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
+ 'package names that you want to install'),
+ PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
+ 'package names that you do not want to install')
+ ]
+ end
+ private :multipackage_entries
+
+ ALIASES = {
+ 'std-ruby' => 'librubyver',
+ 'stdruby' => 'librubyver',
+ 'rubylibdir' => 'librubyver',
+ 'archdir' => 'librubyverarch',
+ 'site-ruby-common' => 'siteruby', # For backward compatibility
+ 'site-ruby' => 'siterubyver', # For backward compatibility
+ 'bin-dir' => 'bindir',
+ 'bin-dir' => 'bindir',
+ 'rb-dir' => 'rbdir',
+ 'so-dir' => 'sodir',
+ 'data-dir' => 'datadir',
+ 'ruby-path' => 'rubypath',
+ 'ruby-prog' => 'rubyprog',
+ 'ruby' => 'rubyprog',
+ 'make-prog' => 'makeprog',
+ 'make' => 'makeprog'
+ }
+
+ def fixup
+ ALIASES.each do |ali, name|
+ @table[ali] = @table[name]
+ end
+ @items.freeze
+ @table.freeze
+ @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/
+ end
+
+ def parse_opt(opt)
+ m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}"
+ m.to_a[1,2]
+ end
+
+ def dllext
+ @rbconfig['DLEXT']
+ end
+
+ def value_config?(name)
+ lookup(name).value?
+ end
+
+ class Item
+ def initialize(name, template, default, desc)
+ @name = name.freeze
+ @template = template
+ @value = default
+ @default = default
+ @description = desc
+ end
+
+ attr_reader :name
+ attr_reader :description
+
+ attr_accessor :default
+ alias help_default default
+
+ def help_opt
+ "--#{@name}=#{@template}"
+ end
+
+ def value?
+ true
+ end
+
+ def value
+ @value
+ end
+
+ def resolve(table)
+ @value.gsub(%r<\$([^/]+)>) { table[$1] }
+ end
+
+ def set(val)
+ @value = check(val)
+ end
+
+ private
+
+ def check(val)
+ setup_rb_error "config: --#{name} requires argument" unless val
+ val
+ end
+ end
+
+ class BoolItem < Item
+ def config_type
+ 'bool'
+ end
+
+ def help_opt
+ "--#{@name}"
+ end
+
+ private
+
+ def check(val)
+ return 'yes' unless val
+ case val
+ when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes'
+ when /\An(o)?\z/i, /\Af(alse)\z/i then 'no'
+ else
+ setup_rb_error "config: --#{@name} accepts only yes/no for argument"
+ end
+ end
+ end
+
+ class PathItem < Item
+ def config_type
+ 'path'
+ end
+
+ private
+
+ def check(path)
+ setup_rb_error "config: --#{@name} requires argument" unless path
+ path[0,1] == '$' ? path : File.expand_path(path)
+ end
+ end
+
+ class ProgramItem < Item
+ def config_type
+ 'program'
+ end
+ end
+
+ class SelectItem < Item
+ def initialize(name, selection, default, desc)
+ super
+ @ok = selection.split('/')
+ end
+
+ def config_type
+ 'select'
+ end
+
+ private
+
+ def check(val)
+ unless @ok.include?(val.strip)
+ setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
+ end
+ val.strip
+ end
+ end
+
+ class ExecItem < Item
+ def initialize(name, selection, desc, &block)
+ super name, selection, nil, desc
+ @ok = selection.split('/')
+ @action = block
+ end
+
+ def config_type
+ 'exec'
+ end
+
+ def value?
+ false
+ end
+
+ def resolve(table)
+ setup_rb_error "$#{name()} wrongly used as option value"
+ end
+
+ undef set
+
+ def evaluate(val, table)
+ v = val.strip.downcase
+ unless @ok.include?(v)
+ setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})"
+ end
+ @action.call v, table
+ end
+ end
+
+ class PackageSelectionItem < Item
+ def initialize(name, template, default, help_default, desc)
+ super name, template, default, desc
+ @help_default = help_default
+ end
+
+ attr_reader :help_default
+
+ def config_type
+ 'package'
+ end
+
+ private
+
+ def check(val)
+ unless File.dir?("packages/#{val}")
+ setup_rb_error "config: no such package: #{val}"
+ end
+ val
+ end
+ end
+
+ class MetaConfigEnvironment
+ def initialize(config, installer)
+ @config = config
+ @installer = installer
+ end
+
+ def config_names
+ @config.names
+ end
+
+ def config?(name)
+ @config.key?(name)
+ end
+
+ def bool_config?(name)
+ @config.lookup(name).config_type == 'bool'
+ end
+
+ def path_config?(name)
+ @config.lookup(name).config_type == 'path'
+ end
+
+ def value_config?(name)
+ @config.lookup(name).config_type != 'exec'
+ end
+
+ def add_config(item)
+ @config.add item
+ end
+
+ def add_bool_config(name, default, desc)
+ @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
+ end
+
+ def add_path_config(name, default, desc)
+ @config.add PathItem.new(name, 'path', default, desc)
+ end
+
+ def set_config_default(name, default)
+ @config.lookup(name).default = default
+ end
+
+ def remove_config(name)
+ @config.remove(name)
+ end
+
+ # For only multipackage
+ def packages
+ raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer
+ @installer.packages
+ end
+
+ # For only multipackage
+ def declare_packages(list)
+ raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer
+ @installer.packages = list
+ end
+ end
+
+end # class ConfigTable
+
+
+# This module requires: #verbose?, #no_harm?
+module FileOperations
+
+ def mkdir_p(dirname, prefix = nil)
+ dirname = prefix + File.expand_path(dirname) if prefix
+ $stderr.puts "mkdir -p #{dirname}" if verbose?
+ return if no_harm?
+
+ # Does not check '/', it's too abnormal.
+ dirs = File.expand_path(dirname).split(%r<(?=/)>)
+ if /\A[a-z]:\z/i =~ dirs[0]
+ disk = dirs.shift
+ dirs[0] = disk + dirs[0]
+ end
+ dirs.each_index do |idx|
+ path = dirs[0..idx].join('')
+ Dir.mkdir path unless File.dir?(path)
+ end
+ end
+
+ def rm_f(path)
+ $stderr.puts "rm -f #{path}" if verbose?
+ return if no_harm?
+ force_remove_file path
+ end
+
+ def rm_rf(path)
+ $stderr.puts "rm -rf #{path}" if verbose?
+ return if no_harm?
+ remove_tree path
+ end
+
+ def remove_tree(path)
+ if File.symlink?(path)
+ remove_file path
+ elsif File.dir?(path)
+ remove_tree0 path
+ else
+ force_remove_file path
+ end
+ end
+
+ def remove_tree0(path)
+ Dir.foreach(path) do |ent|
+ next if ent == '.'
+ next if ent == '..'
+ entpath = "#{path}/#{ent}"
+ if File.symlink?(entpath)
+ remove_file entpath
+ elsif File.dir?(entpath)
+ remove_tree0 entpath
+ else
+ force_remove_file entpath
+ end
+ end
+ begin
+ Dir.rmdir path
+ rescue Errno::ENOTEMPTY
+ # directory may not be empty
+ end
+ end
+
+ def move_file(src, dest)
+ force_remove_file dest
+ begin
+ File.rename src, dest
+ rescue
+ File.open(dest, 'wb') {|f|
+ f.write File.binread(src)
+ }
+ File.chmod File.stat(src).mode, dest
+ File.unlink src
+ end
+ end
+
+ def force_remove_file(path)
+ begin
+ remove_file path
+ rescue
+ end
+ end
+
+ def remove_file(path)
+ File.chmod 0777, path
+ File.unlink path
+ end
+
+ def install(from, dest, mode, prefix = nil)
+ $stderr.puts "install #{from} #{dest}" if verbose?
+ return if no_harm?
+
+ realdest = prefix ? prefix + File.expand_path(dest) : dest
+ realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
+ str = File.binread(from)
+ if diff?(str, realdest)
+ verbose_off {
+ rm_f realdest if File.exist?(realdest)
+ }
+ File.open(realdest, 'wb') {|f|
+ f.write str
+ }
+ File.chmod mode, realdest
+
+ File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
+ if prefix
+ f.puts realdest.sub(prefix, '')
+ else
+ f.puts realdest
+ end
+ }
+ end
+ end
+
+ def diff?(new_content, path)
+ return true unless File.exist?(path)
+ new_content != File.binread(path)
+ end
+
+ def command(*args)
+ $stderr.puts args.join(' ') if verbose?
+ system(*args) or raise RuntimeError,
+ "system(#{args.map{|a| a.inspect }.join(' ')}) failed"
+ end
+
+ def ruby(*args)
+ command config('rubyprog'), *args
+ end
+
+ def make(task = nil)
+ command(*[config('makeprog'), task].compact)
+ end
+
+ def extdir?(dir)
+ File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb")
+ end
+
+ def files_of(dir)
+ Dir.open(dir) {|d|
+ return d.select {|ent| File.file?("#{dir}/#{ent}") }
+ }
+ end
+
+ DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn )
+
+ def directories_of(dir)
+ Dir.open(dir) {|d|
+ return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT
+ }
+ end
+
+end
+
+
+# This module requires: #srcdir_root, #objdir_root, #relpath
+module HookScriptAPI
+
+ def get_config(key)
+ @config[key]
+ end
+
+ alias config get_config
+
+ # obsolete: use metaconfig to change configuration
+ def set_config(key, val)
+ @config[key] = val
+ end
+
+ #
+ # srcdir/objdir (works only in the package directory)
+ #
+
+ def curr_srcdir
+ "#{srcdir_root()}/#{relpath()}"
+ end
+
+ def curr_objdir
+ "#{objdir_root()}/#{relpath()}"
+ end
+
+ def srcfile(path)
+ "#{curr_srcdir()}/#{path}"
+ end
+
+ def srcexist?(path)
+ File.exist?(srcfile(path))
+ end
+
+ def srcdirectory?(path)
+ File.dir?(srcfile(path))
+ end
+
+ def srcfile?(path)
+ File.file?(srcfile(path))
+ end
+
+ def srcentries(path = '.')
+ Dir.open("#{curr_srcdir()}/#{path}") {|d|
+ return d.to_a - %w(. ..)
+ }
+ end
+
+ def srcfiles(path = '.')
+ srcentries(path).select {|fname|
+ File.file?(File.join(curr_srcdir(), path, fname))
+ }
+ end
+
+ def srcdirectories(path = '.')
+ srcentries(path).select {|fname|
+ File.dir?(File.join(curr_srcdir(), path, fname))
+ }
+ end
+
+end
+
+
+class ToplevelInstaller
+
+ Version = '3.4.1'
+ Copyright = 'Copyright (c) 2000-2005 Minero Aoki'
+
+ TASKS = [
+ [ 'all', 'do config, setup, then install' ],
+ [ 'config', 'saves your configurations' ],
+ [ 'show', 'shows current configuration' ],
+ [ 'setup', 'compiles ruby extentions and others' ],
+ [ 'install', 'installs files' ],
+ [ 'test', 'run all tests in test/' ],
+ [ 'clean', "does `make clean' for each extention" ],
+ [ 'distclean',"does `make distclean' for each extention" ]
+ ]
+
+ def ToplevelInstaller.invoke
+ config = ConfigTable.new(load_rbconfig())
+ config.load_standard_entries
+ config.load_multipackage_entries if multipackage?
+ config.fixup
+ klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller)
+ klass.new(File.dirname($0), config).invoke
+ end
+
+ def ToplevelInstaller.multipackage?
+ File.dir?(File.dirname($0) + '/packages')
+ end
+
+ def ToplevelInstaller.load_rbconfig
+ if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
+ ARGV.delete(arg)
+ load File.expand_path(arg.split(/=/, 2)[1])
+ $".push 'rbconfig.rb'
+ else
+ require 'rbconfig'
+ end
+ ::Config::CONFIG
+ end
+
+ def initialize(ardir_root, config)
+ @ardir = File.expand_path(ardir_root)
+ @config = config
+ # cache
+ @valid_task_re = nil
+ end
+
+ def config(key)
+ @config[key]
+ end
+
+ def inspect
+ "#<#{self.class} #{__id__()}>"
+ end
+
+ def invoke
+ run_metaconfigs
+ case task = parsearg_global()
+ when nil, 'all'
+ parsearg_config
+ init_installers
+ exec_config
+ exec_setup
+ exec_install
+ else
+ case task
+ when 'config', 'test'
+ ;
+ when 'clean', 'distclean'
+ @config.load_savefile if File.exist?(@config.savefile)
+ else
+ @config.load_savefile
+ end
+ __send__ "parsearg_#{task}"
+ init_installers
+ __send__ "exec_#{task}"
+ end
+ end
+
+ def run_metaconfigs
+ @config.load_script "#{@ardir}/metaconfig"
+ end
+
+ def init_installers
+ @installer = Installer.new(@config, @ardir, File.expand_path('.'))
+ end
+
+ #
+ # Hook Script API bases
+ #
+
+ def srcdir_root
+ @ardir
+ end
+
+ def objdir_root
+ '.'
+ end
+
+ def relpath
+ '.'
+ end
+
+ #
+ # Option Parsing
+ #
+
+ def parsearg_global
+ while arg = ARGV.shift
+ case arg
+ when /\A\w+\z/
+ setup_rb_error "invalid task: #{arg}" unless valid_task?(arg)
+ return arg
+ when '-q', '--quiet'
+ @config.verbose = false
+ when '--verbose'
+ @config.verbose = true
+ when '--help'
+ print_usage $stdout
+ exit 0
+ when '--version'
+ puts "#{File.basename($0)} version #{Version}"
+ exit 0
+ when '--copyright'
+ puts Copyright
+ exit 0
+ else
+ setup_rb_error "unknown global option '#{arg}'"
+ end
+ end
+ nil
+ end
+
+ def valid_task?(t)
+ valid_task_re() =~ t
+ end
+
+ def valid_task_re
+ @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/
+ end
+
+ def parsearg_no_options
+ unless ARGV.empty?
+ task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1)
+ setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}"
+ end
+ end
+
+ alias parsearg_show parsearg_no_options
+ alias parsearg_setup parsearg_no_options
+ alias parsearg_test parsearg_no_options
+ alias parsearg_clean parsearg_no_options
+ alias parsearg_distclean parsearg_no_options
+
+ def parsearg_config
+ evalopt = []
+ set = []
+ @config.config_opt = []
+ while i = ARGV.shift
+ if /\A--?\z/ =~ i
+ @config.config_opt = ARGV.dup
+ break
+ end
+ name, value = *@config.parse_opt(i)
+ if @config.value_config?(name)
+ @config[name] = value
+ else
+ evalopt.push [name, value]
+ end
+ set.push name
+ end
+ evalopt.each do |name, value|
+ @config.lookup(name).evaluate value, @config
+ end
+ # Check if configuration is valid
+ set.each do |n|
+ @config[n] if @config.value_config?(n)
+ end
+ end
+
+ def parsearg_install
+ @config.no_harm = false
+ @config.install_prefix = ''
+ while a = ARGV.shift
+ case a
+ when '--no-harm'
+ @config.no_harm = true
+ when /\A--prefix=/
+ path = a.split(/=/, 2)[1]
+ path = File.expand_path(path) unless path[0,1] == '/'
+ @config.install_prefix = path
+ else
+ setup_rb_error "install: unknown option #{a}"
+ end
+ end
+ end
+
+ def print_usage(out)
+ out.puts 'Typical Installation Procedure:'
+ out.puts " $ ruby #{File.basename $0} config"
+ out.puts " $ ruby #{File.basename $0} setup"
+ out.puts " # ruby #{File.basename $0} install (may require root privilege)"
+ out.puts
+ out.puts 'Detailed Usage:'
+ out.puts " ruby #{File.basename $0} <global option>"
+ out.puts " ruby #{File.basename $0} [<global options>] <task> [<task options>]"
+
+ fmt = " %-24s %s\n"
+ out.puts
+ out.puts 'Global options:'
+ out.printf fmt, '-q,--quiet', 'suppress message outputs'
+ out.printf fmt, ' --verbose', 'output messages verbosely'
+ out.printf fmt, ' --help', 'print this message'
+ out.printf fmt, ' --version', 'print version and quit'
+ out.printf fmt, ' --copyright', 'print copyright and quit'
+ out.puts
+ out.puts 'Tasks:'
+ TASKS.each do |name, desc|
+ out.printf fmt, name, desc
+ end
+
+ fmt = " %-24s %s [%s]\n"
+ out.puts
+ out.puts 'Options for CONFIG or ALL:'
+ @config.each do |item|
+ out.printf fmt, item.help_opt, item.description, item.help_default
+ end
+ out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
+ out.puts
+ out.puts 'Options for INSTALL:'
+ out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
+ out.printf fmt, '--prefix=path', 'install path prefix', ''
+ out.puts
+ end
+
+ #
+ # Task Handlers
+ #
+
+ def exec_config
+ @installer.exec_config
+ @config.save # must be final
+ end
+
+ def exec_setup
+ @installer.exec_setup
+ end
+
+ def exec_install
+ @installer.exec_install
+ end
+
+ def exec_test
+ @installer.exec_test
+ end
+
+ def exec_show
+ @config.each do |i|
+ printf "%-20s %s\n", i.name, i.value if i.value?
+ end
+ end
+
+ def exec_clean
+ @installer.exec_clean
+ end
+
+ def exec_distclean
+ @installer.exec_distclean
+ end
+
+end # class ToplevelInstaller
+
+
+class ToplevelInstallerMulti < ToplevelInstaller
+
+ include FileOperations
+
+ def initialize(ardir_root, config)
+ super
+ @packages = directories_of("#{@ardir}/packages")
+ raise 'no package exists' if @packages.empty?
+ @root_installer = Installer.new(@config, @ardir, File.expand_path('.'))
+ end
+
+ def run_metaconfigs
+ @config.load_script "#{@ardir}/metaconfig", self
+ @packages.each do |name|
+ @config.load_script "#{@ardir}/packages/#{name}/metaconfig"
+ end
+ end
+
+ attr_reader :packages
+
+ def packages=(list)
+ raise 'package list is empty' if list.empty?
+ list.each do |name|
+ raise "directory packages/#{name} does not exist"\
+ unless File.dir?("#{@ardir}/packages/#{name}")
+ end
+ @packages = list
+ end
+
+ def init_installers
+ @installers = {}
+ @packages.each do |pack|
+ @installers[pack] = Installer.new(@config,
+ "#{@ardir}/packages/#{pack}",
+ "packages/#{pack}")
+ end
+ with = extract_selection(config('with'))
+ without = extract_selection(config('without'))
+ @selected = @installers.keys.select {|name|
+ (with.empty? or with.include?(name)) \
+ and not without.include?(name)
+ }
+ end
+
+ def extract_selection(list)
+ a = list.split(/,/)
+ a.each do |name|
+ setup_rb_error "no such package: #{name}" unless @installers.key?(name)
+ end
+ a
+ end
+
+ def print_usage(f)
+ super
+ f.puts 'Inluded packages:'
+ f.puts ' ' + @packages.sort.join(' ')
+ f.puts
+ end
+
+ #
+ # Task Handlers
+ #
+
+ def exec_config
+ run_hook 'pre-config'
+ each_selected_installers {|inst| inst.exec_config }
+ run_hook 'post-config'
+ @config.save # must be final
+ end
+
+ def exec_setup
+ run_hook 'pre-setup'
+ each_selected_installers {|inst| inst.exec_setup }
+ run_hook 'post-setup'
+ end
+
+ def exec_install
+ run_hook 'pre-install'
+ each_selected_installers {|inst| inst.exec_install }
+ run_hook 'post-install'
+ end
+
+ def exec_test
+ run_hook 'pre-test'
+ each_selected_installers {|inst| inst.exec_test }
+ run_hook 'post-test'
+ end
+
+ def exec_clean
+ rm_f @config.savefile
+ run_hook 'pre-clean'
+ each_selected_installers {|inst| inst.exec_clean }
+ run_hook 'post-clean'
+ end
+
+ def exec_distclean
+ rm_f @config.savefile
+ run_hook 'pre-distclean'
+ each_selected_installers {|inst| inst.exec_distclean }
+ run_hook 'post-distclean'
+ end
+
+ #
+ # lib
+ #
+
+ def each_selected_installers
+ Dir.mkdir 'packages' unless File.dir?('packages')
+ @selected.each do |pack|
+ $stderr.puts "Processing the package `#{pack}' ..." if verbose?
+ Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
+ Dir.chdir "packages/#{pack}"
+ yield @installers[pack]
+ Dir.chdir '../..'
+ end
+ end
+
+ def run_hook(id)
+ @root_installer.run_hook id
+ end
+
+ # module FileOperations requires this
+ def verbose?
+ @config.verbose?
+ end
+
+ # module FileOperations requires this
+ def no_harm?
+ @config.no_harm?
+ end
+
+end # class ToplevelInstallerMulti
+
+
+class Installer
+
+ FILETYPES = %w( bin lib ext data conf man )
+
+ include FileOperations
+ include HookScriptAPI
+
+ def initialize(config, srcroot, objroot)
+ @config = config
+ @srcdir = File.expand_path(srcroot)
+ @objdir = File.expand_path(objroot)
+ @currdir = '.'
+ end
+
+ def inspect
+ "#<#{self.class} #{File.basename(@srcdir)}>"
+ end
+
+ def noop(rel)
+ end
+
+ #
+ # Hook Script API base methods
+ #
+
+ def srcdir_root
+ @srcdir
+ end
+
+ def objdir_root
+ @objdir
+ end
+
+ def relpath
+ @currdir
+ end
+
+ #
+ # Config Access
+ #
+
+ # module FileOperations requires this
+ def verbose?
+ @config.verbose?
+ end
+
+ # module FileOperations requires this
+ def no_harm?
+ @config.no_harm?
+ end
+
+ def verbose_off
+ begin
+ save, @config.verbose = @config.verbose?, false
+ yield
+ ensure
+ @config.verbose = save
+ end
+ end
+
+ #
+ # TASK config
+ #
+
+ def exec_config
+ exec_task_traverse 'config'
+ end
+
+ alias config_dir_bin noop
+ alias config_dir_lib noop
+
+ def config_dir_ext(rel)
+ extconf if extdir?(curr_srcdir())
+ end
+
+ alias config_dir_data noop
+ alias config_dir_conf noop
+ alias config_dir_man noop
+
+ def extconf
+ ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt
+ end
+
+ #
+ # TASK setup
+ #
+
+ def exec_setup
+ exec_task_traverse 'setup'
+ end
+
+ def setup_dir_bin(rel)
+ files_of(curr_srcdir()).each do |fname|
+ update_shebang_line "#{curr_srcdir()}/#{fname}"
+ end
+ end
+
+ alias setup_dir_lib noop
+
+ def setup_dir_ext(rel)
+ make if extdir?(curr_srcdir())
+ end
+
+ alias setup_dir_data noop
+ alias setup_dir_conf noop
+ alias setup_dir_man noop
+
+ def update_shebang_line(path)
+ return if no_harm?
+ return if config('shebang') == 'never'
+ old = Shebang.load(path)
+ if old
+ $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1
+ new = new_shebang(old)
+ return if new.to_s == old.to_s
+ else
+ return unless config('shebang') == 'all'
+ new = Shebang.new(config('rubypath'))
+ end
+ $stderr.puts "updating shebang: #{File.basename(path)}" if verbose?
+ open_atomic_writer(path) {|output|
+ File.open(path, 'rb') {|f|
+ f.gets if old # discard
+ output.puts new.to_s
+ output.print f.read
+ }
+ }
+ end
+
+ def new_shebang(old)
+ if /\Aruby/ =~ File.basename(old.cmd)
+ Shebang.new(config('rubypath'), old.args)
+ elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby'
+ Shebang.new(config('rubypath'), old.args[1..-1])
+ else
+ return old unless config('shebang') == 'all'
+ Shebang.new(config('rubypath'))
+ end
+ end
+
+ def open_atomic_writer(path, &block)
+ tmpfile = File.basename(path) + '.tmp'
+ begin
+ File.open(tmpfile, 'wb', &block)
+ File.rename tmpfile, File.basename(path)
+ ensure
+ File.unlink tmpfile if File.exist?(tmpfile)
+ end
+ end
+
+ class Shebang
+ def Shebang.load(path)
+ line = nil
+ File.open(path) {|f|
+ line = f.gets
+ }
+ return nil unless /\A#!/ =~ line
+ parse(line)
+ end
+
+ def Shebang.parse(line)
+ cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ')
+ new(cmd, args)
+ end
+
+ def initialize(cmd, args = [])
+ @cmd = cmd
+ @args = args
+ end
+
+ attr_reader :cmd
+ attr_reader :args
+
+ def to_s
+ "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}")
+ end
+ end
+
+ #
+ # TASK install
+ #
+
+ def exec_install
+ rm_f 'InstalledFiles'
+ exec_task_traverse 'install'
+ end
+
+ def install_dir_bin(rel)
+ install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755
+ end
+
+ def install_dir_lib(rel)
+ install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644
+ end
+
+ def install_dir_ext(rel)
+ return unless extdir?(curr_srcdir())
+ install_files rubyextentions('.'),
+ "#{config('sodir')}/#{File.dirname(rel)}",
+ 0555
+ end
+
+ def install_dir_data(rel)
+ install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644
+ end
+
+ def install_dir_conf(rel)
+ # FIXME: should not remove current config files
+ # (rename previous file to .old/.org)
+ install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644
+ end
+
+ def install_dir_man(rel)
+ install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644
+ end
+
+ def install_files(list, dest, mode)
+ mkdir_p dest, @config.install_prefix
+ list.each do |fname|
+ install fname, dest, mode, @config.install_prefix
+ end
+ end
+
+ def libfiles
+ glob_reject(%w(*.y *.output), targetfiles())
+ end
+
+ def rubyextentions(dir)
+ ents = glob_select("*.#{@config.dllext}", targetfiles())
+ if ents.empty?
+ setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
+ end
+ ents
+ end
+
+ def targetfiles
+ mapdir(existfiles() - hookfiles())
+ end
+
+ def mapdir(ents)
+ ents.map {|ent|
+ if File.exist?(ent)
+ then ent # objdir
+ else "#{curr_srcdir()}/#{ent}" # srcdir
+ end
+ }
+ end
+
+ # picked up many entries from cvs-1.11.1/src/ignore.c
+ JUNK_FILES = %w(
+ core RCSLOG tags TAGS .make.state
+ .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
+ *~ *.old *.bak *.BAK *.orig *.rej _$* *$
+
+ *.org *.in .*
+ )
+
+ def existfiles
+ glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.')))
+ end
+
+ def hookfiles
+ %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
+ %w( config setup install clean ).map {|t| sprintf(fmt, t) }
+ }.flatten
+ end
+
+ def glob_select(pat, ents)
+ re = globs2re([pat])
+ ents.select {|ent| re =~ ent }
+ end
+
+ def glob_reject(pats, ents)
+ re = globs2re(pats)
+ ents.reject {|ent| re =~ ent }
+ end
+
+ GLOB2REGEX = {
+ '.' => '\.',
+ '$' => '\$',
+ '#' => '\#',
+ '*' => '.*'
+ }
+
+ def globs2re(pats)
+ /\A(?:#{
+ pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|')
+ })\z/
+ end
+
+ #
+ # TASK test
+ #
+
+ TESTDIR = 'test'
+
+ def exec_test
+ unless File.directory?('test')
+ $stderr.puts 'no test in this package' if verbose?
+ return
+ end
+ $stderr.puts 'Running tests...' if verbose?
+ begin
+ require 'test/unit'
+ rescue LoadError
+ setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.'
+ end
+ runner = Test::Unit::AutoRunner.new(true)
+ runner.to_run << TESTDIR
+ runner.run
+ end
+
+ #
+ # TASK clean
+ #
+
+ def exec_clean
+ exec_task_traverse 'clean'
+ rm_f @config.savefile
+ rm_f 'InstalledFiles'
+ end
+
+ alias clean_dir_bin noop
+ alias clean_dir_lib noop
+ alias clean_dir_data noop
+ alias clean_dir_conf noop
+ alias clean_dir_man noop
+
+ def clean_dir_ext(rel)
+ return unless extdir?(curr_srcdir())
+ make 'clean' if File.file?('Makefile')
+ end
+
+ #
+ # TASK distclean
+ #
+
+ def exec_distclean
+ exec_task_traverse 'distclean'
+ rm_f @config.savefile
+ rm_f 'InstalledFiles'
+ end
+
+ alias distclean_dir_bin noop
+ alias distclean_dir_lib noop
+
+ def distclean_dir_ext(rel)
+ return unless extdir?(curr_srcdir())
+ make 'distclean' if File.file?('Makefile')
+ end
+
+ alias distclean_dir_data noop
+ alias distclean_dir_conf noop
+ alias distclean_dir_man noop
+
+ #
+ # Traversing
+ #
+
+ def exec_task_traverse(task)
+ run_hook "pre-#{task}"
+ FILETYPES.each do |type|
+ if type == 'ext' and config('without-ext') == 'yes'
+ $stderr.puts 'skipping ext/* by user option' if verbose?
+ next
+ end
+ traverse task, type, "#{task}_dir_#{type}"
+ end
+ run_hook "post-#{task}"
+ end
+
+ def traverse(task, rel, mid)
+ dive_into(rel) {
+ run_hook "pre-#{task}"
+ __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
+ directories_of(curr_srcdir()).each do |d|
+ traverse task, "#{rel}/#{d}", mid
+ end
+ run_hook "post-#{task}"
+ }
+ end
+
+ def dive_into(rel)
+ return unless File.dir?("#{@srcdir}/#{rel}")
+
+ dir = File.basename(rel)
+ Dir.mkdir dir unless File.dir?(dir)
+ prevdir = Dir.pwd
+ Dir.chdir dir
+ $stderr.puts '---> ' + rel if verbose?
+ @currdir = rel
+ yield
+ Dir.chdir prevdir
+ $stderr.puts '<--- ' + rel if verbose?
+ @currdir = File.dirname(rel)
+ end
+
+ def run_hook(id)
+ path = [ "#{curr_srcdir()}/#{id}",
+ "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) }
+ return unless path
+ begin
+ instance_eval File.read(path), path, 1
+ rescue
+ raise if $DEBUG
+ setup_rb_error "hook #{path} failed:\n" + $!.message
+ end
+ end
+
+end # class Installer
+
+
+class SetupError < StandardError; end
+
+def setup_rb_error(msg)
+ raise SetupError, msg
+end
+
+if $0 == __FILE__
+ begin
+ ToplevelInstaller.invoke
+ rescue SetupError
+ raise if $DEBUG
+ $stderr.puts $!.message
+ $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
+ exit 1
+ end
+end
diff --git a/test/Makefile b/test/Makefile
new file mode 100644
index 0000000..ebb3ade
--- /dev/null
+++ b/test/Makefile
@@ -0,0 +1,20 @@
+CC = g++
+CPPFLAGS = -I. -Wall -Werror
+CXXFLAGS = -O2
+YAGGO = ../bin/yaggo
+RY = ruby -I../lib $(YAGGO)
+
+all: count
+
+count: count.o
+count_cmdline.hpp _count: count.yaggo $(YAGGO)
+ $(RY) --debug --zc _count $<
+count.o: count.cpp count_cmdline.hpp
+
+test_errno.hpp: test_errno.yaggo $(YAGGO)
+ $(RY) --debug $<
+test_errno: test_errno.o
+test_errno.o: test_errno.cc test_errno.hpp
+
+clean:
+ rm -f *.o count
diff --git a/test/count.cpp b/test/count.cpp
new file mode 100644
index 0000000..12aabce
--- /dev/null
+++ b/test/count.cpp
@@ -0,0 +1,33 @@
+#include <iostream>
+#include "count_cmdline.hpp"
+
+#define CONV(type) \
+ try { \
+ std::cout << "as_" << #type << ": " \
+ << args.verra_arg.as_ ## type () << std::endl; \
+ } catch(std::exception &e) { \
+ std::cerr << "Conv to " << #type << " failed: " \
+ << e.what() << std::endl; \
+ }
+
+
+int main(int argc, char *argv[])
+{
+ count_cmdline args(argc, argv);
+ args.dump(std::cout);
+ CONV(uint32);
+ CONV(uint64);
+ CONV(int32);
+ CONV(int64);
+ CONV(double);
+ if(args.severity_arg == count_cmdline::severity::low)
+ std::cout << "Pfiou!\n";
+ try {
+ std::cout << args.verra_arg.as_enum(count_cmdline::severity::strs) << "\n";
+ } catch(std::exception& e) {
+ std::cerr << "Conv to enum failed: " << e.what() << std::endl;
+ }
+ if(args.secret_flag)
+ std::cerr << "How did you know about the --secret option?" << std::endl;
+ return 0;
+}
diff --git a/test/count_cmdline.yaggo b/test/count_cmdline.yaggo
new file mode 100755
index 0000000..720425d
--- /dev/null
+++ b/test/count_cmdline.yaggo
@@ -0,0 +1,84 @@
+purpose = "Count k-mers or qmers in fasta or fastq files"
+package "jellyfish count"
+description <<EOS
+Count k-mers in fasta or fastq files.
+
+You see it works pretty well
+EOS
+version "0.0.1"
+
+license "My great license
+Enjoy!"
+
+output "count_cmdline.hpp"
+name "count_cmdline"
+
+option("mer-len", "m") {
+ required; description "Length of mer"
+ uint32; default "314159"
+}
+option("size", "s") {
+ uint64; required; suffix; description "Hash size"
+}
+option("threads", "t") {
+ uint32; default 1; description "Number of threads"
+}
+option("output", "o") {
+ string; default "mer_counts"; description "Output prefix"
+ conflict "c", "high"; typestr "dir"
+}
+option("counter-len", "c") {
+ uint32; default "7"; typestr "Length in bits"
+ description "Length of counting field"
+}
+option("high", "h") {
+ on; description "Am I high?"
+}
+option("severity") {
+ description "Severity description"
+ enum "low", "middle", "high"
+# default 2
+}
+option("out-counter-len") {
+ hidden; uint32; default "4"; typestr "Length in bytes"
+ description "Length of counter fiel in output"
+}
+option("both-strands", "C") {
+ flag; off; description "Count both strand, canonical representation"
+}
+option("lib") {
+ string; multiple
+ description "Boggus lib"
+}
+option("str") {
+ description "C string"
+ c_string }
+option("vstr") {
+ description "vector of string"
+ c_string; multiple }
+option("numbers") {
+ int64; multiple
+ description "Many ints"
+}
+option("double") {
+ double; suffix; multiple
+ description "Many doubles"
+}
+option("verra") {
+ string; typestr "who knows but it is way too long anyhow."
+ description "On verra"
+}
+option("file") {
+ description "file"
+ c_string; access("write"); typestr "path" }
+option("secret") {
+ description "Very secret option"
+ off; secret }
+arg("first") {
+ c_string; typestr "path"
+ description "First"; access "read", "exec"
+}
+arg("second") {
+ uint32; multiple
+ description "Plenty of ints"
+}
diff --git a/test/test_errno.cc b/test/test_errno.cc
new file mode 100644
index 0000000..5a3368c
--- /dev/null
+++ b/test/test_errno.cc
@@ -0,0 +1,12 @@
+#include <iostream>
+#include "test_errno.hpp"
+
+int main(int argc, char *argv[])
+{
+ args_t args(argc, argv);
+
+ if(argc > 1)
+ args_t::error() << "Error" << args_t::error::no;
+
+ return 0;
+}
diff --git a/test/test_errno.yaggo b/test/test_errno.yaggo
new file mode 100644
index 0000000..198e460
--- /dev/null
+++ b/test/test_errno.yaggo
@@ -0,0 +1,7 @@
+description "Toto"
+
+name "args_t"
+
+arg("test") {
+ description "Test"
+ c_string }
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/yaggo.git
More information about the debian-med-commit
mailing list