From: Simon Marchi Date: Fri, 15 Mar 2024 15:36:49 +0000 (-0400) Subject: tap: import some changes X-Git-Url: http://git.efficios.com/?p=argpar.git;a=commitdiff_plain;h=HEAD;hp=143cec42e14e050571be640cb2dfa5c5e0198d59 tap: import some changes Import some changes from: http://git.efficios.com/?p=babeltrace.git;a=tree;f=tests/utils/tap;h=fa181570e8d9b79801d8a8358aca00633856994e;hb=a165a5742a2b173e59900e515caa3b37a0afb42f Most importantly, the `extern "C"` allowing the use of this file in C++. Change-Id: I796dd0e07fe89ba84db97b8735705bae7e39d43c Signed-off-by: Simon Marchi --- diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..83941d0 --- /dev/null +++ b/.clang-format @@ -0,0 +1,119 @@ +# SPDX-License-Identifier: GPL-2.0-only +# SPDX-FileCopyrightText: 2019-2024 Philippe Proulx + +# For clang-format 15 +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: None +AlignConsecutiveDeclarations: None +AlignConsecutiveMacros: Consecutive +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: None +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: ["ARGPAR_HIDDEN"] +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: Never + AfterEnum: true + AfterFunction: true + AfterNamespace: false + AfterStruct: true + AfterUnion: true + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: false +BreakConstructorInitializers: AfterColon +BreakInheritanceList: AfterColon +BreakStringLiterals: false +ColumnLimit: 100 +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: false +DerivePointerAlignment: false +DisableFormat: false +EmptyLineBeforeAccessModifier: Always +FixNamespaceComments: true +IncludeBlocks: Regroup +IncludeCategories: + # System C headers + - Regex: '^<.+\.h>$' + Priority: 2 + # System C++ headers + - Regex: "^<.+>$" + Priority: 1 + # The rest (local headers) + - Regex: ".+" + Priority: 3 +IncludeIsMainRegex: "please_dont_do_that" +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentExternBlock: NoIndent +IndentGotoLabels: false +IndentPPDirectives: AfterHash +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +Language: Cpp +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +PPIndentWidth: 4 +PointerAlignment: Right +ReferenceAlignment: Left +ReflowComments: false +SortIncludes: CaseInsensitive +SortUsingDeclarations: false +SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Both +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +UseCRLF: false +UseTab: Never diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..459afc9 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,101 @@ +# SPDX-License-Identifier: GPL-2.0-only +# SPDX-FileCopyrightText: EfficiOS Inc. + +Checks: '-*, + bugprone-argument-comment, + bugprone-assert-side-effect, + bugprone-assignment-in-if-condition, + bugprone-bad-signal-to-kill-thread, + bugprone-bool-pointer-implicit-conversion, + bugprone-copy-constructor-init, + bugprone-dangling-handle, + bugprone-exception-escape, + bugprone-fold-init-type, + bugprone-forward-declaration-namespace, + bugprone-forwarding-reference-overload, + bugprone-inaccurate-erase, + bugprone-incorrect-roundings, + bugprone-infinite-loop, + bugprone-integer-division, + bugprone-macro-parentheses, + bugprone-macro-repeated-side-effects, + bugprone-misplaced-operator-in-strlen-in-alloc, + bugprone-misplaced-pointer-arithmetic-in-alloc, + bugprone-move-forwarding-reference, + bugprone-multiple-statement-macro, + bugprone-not-null-terminated-result, + bugprone-parent-virtual-call, + bugprone-posix-return, + bugprone-shared-ptr-array-mismatch, + bugprone-signal-handler, + bugprone-signed-char-misuse, + bugprone-sizeof-container, + bugprone-sizeof-expression, + bugprone-standalone-empty, + bugprone-string-constructor, + bugprone-string-integer-assignment, + bugprone-string-literal-with-embedded-nul, + bugprone-suspicious-enum-usage, + bugprone-suspicious-include, + bugprone-suspicious-memory-comparison + bugprone-suspicious-memset-usage, + bugprone-suspicious-missing-comma, + bugprone-suspicious-realloc-usage, + bugprone-suspicious-semicolon, + bugprone-suspicious-string-compare, + bugprone-swapped-arguments, + bugprone-terminating-continue, + bugprone-throw-keyword-missing, + bugprone-too-small-loop-variable, + bugprone-unchecked-optional-access + bugprone-undefined-memory-manipulation, + bugprone-undelegated-constructor, + bugprone-unhandled-exception-at-new, + bugprone-unhandled-self-assignment, + bugprone-unused-raii, + bugprone-unused-return-value, + bugprone-use-after-move, + bugprone-virtual-near-miss, + bugprone-unused-raii, + bugprone-use-after-move, + cppcoreguidelines-pro-type-const-cast, + cppcoreguidelines-slicing, + cppcoreguidelines-special-member-functions, + cppcoreguidelines-virtual-class-destructor, + google-build-explicit-make-pair, + google-explicit-constructor, + misc-const-correctness, + misc-misleading-identifier, + misc-non-copyable-objects, + misc-throw-by-value-catch-by-reference, + misc-unused-parameters, + misc-unused-using-decls, + modernize-avoid-bind, + modernize-concat-nested-namespaces, + modernize-loop-convert, + modernize-make-shared, + modernize-make-unique, + modernize-pass-by-value, + modernize-redundant-void-arg, + modernize-replace-auto-ptr, + modernize-replace-random-shuffle, + modernize-replace-auto-ptr, + modernize-shrink-to-fit, + modernize-use-bool-literals, + modernize-use-default-member-init, + modernize-use-emplace, + modernize-use-equals-default, + modernize-use-equals-delete, + modernize-use-noexcept, + modernize-use-nullptr, + modernize-use-override, + modernize-use-transparent-functors, + modernize-use-using, + performance-*, + -performance-no-int-to-ptr, + readability-redundant-member-init, + readability-simplify-boolean-expr' +FormatStyle: 'file' +CheckOptions: + - key: bugprone-assert-side-effect.AssertMacros + value: ARGPAR_ASSERT diff --git a/.gitignore b/.gitignore index ee42aaa..a86f2db 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +# SPDX-FileCopyrightText: EfficiOS Inc. + .libs .deps *.trs @@ -28,5 +31,7 @@ missing test-driver Makefile Makefile.in -tests/test_argpar +tests/test-argpar Doxyfile +api-doc/ +README.html diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000..7627e8c --- /dev/null +++ b/.gitreview @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +# SPDX-FileCopyrightText: EfficiOS Inc. + +[gerrit] +host=review.lttng.org +port=29418 +project=argpar.git +defaultbranch=master +defaultremote=review +defaultrebase=0 diff --git a/Doxyfile.in b/Doxyfile.in index 20133bd..d0b9f69 100644 --- a/Doxyfile.in +++ b/Doxyfile.in @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +# SPDX-FileCopyrightText: EfficiOS Inc. + PROJECT_NAME = argpar PROJECT_NUMBER = @PACKAGE_VERSION@ PROJECT_BRIEF = "Position-aware command-line argument parsing library" diff --git a/LICENSES/BSD-2-Clause.txt b/LICENSES/BSD-2-Clause.txt new file mode 100644 index 0000000..5f662b3 --- /dev/null +++ b/LICENSES/BSD-2-Clause.txt @@ -0,0 +1,9 @@ +Copyright (c) + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSES/CC-BY-SA-4.0.txt b/LICENSES/CC-BY-SA-4.0.txt new file mode 100644 index 0000000..835a683 --- /dev/null +++ b/LICENSES/CC-BY-SA-4.0.txt @@ -0,0 +1,170 @@ +Creative Commons Attribution-ShareAlike 4.0 International + + Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. + +Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors. + +Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. + +Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public. + +Creative Commons Attribution-ShareAlike 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. + +Section 1 – Definitions. + + a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses, approved by Creative Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. + + e. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. + + i. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights under this Public License. + + k. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. + +Section 2 – Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: + + A. reproduce and Share the Licensed Material, in whole or in part; and + + B. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. + + 3. Term. The term of this Public License is specified in Section 6(a). + + 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. + + 5. Downstream recipients. + + A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. + + B. Additional offer from the Licensor – Adapted Material. Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter’s License You apply. + + C. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. + + 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this Public License. + + 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. + +Section 3 – License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified form), You must: + + A. retain the following if it is supplied by the Licensor with the Licensed Material: + + i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of warranties; + + v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; + + B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and + + C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. + + 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. + + b. ShareAlike.In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply. + + 1. The Adapter’s License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply. + +Section 4 – Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; + + b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and + + c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. +For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. + +Section 5 – Disclaimer of Warranties and Limitation of Liability. + + a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. + + b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. + + c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. + +Section 6 – Term and Termination. + + a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or + + 2. upon express reinstatement by the Licensor. + + c. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. + + d. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. + + e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. + +Section 7 – Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. + +Section 8 – Interpretation. + + a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. + + c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. + + d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. + +Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/LICENSES/GPL-2.0-only.txt b/LICENSES/GPL-2.0-only.txt new file mode 100644 index 0000000..17cb286 --- /dev/null +++ b/LICENSES/GPL-2.0-only.txt @@ -0,0 +1,117 @@ +GNU GENERAL PUBLIC LICENSE +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) 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 this service 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 make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. + + c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +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 convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + + one line to give the program's name and an idea of what it does. Copyright (C) yyyy 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 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. + +signature of Ty Coon, 1 April 1989 Ty Coon, President of Vice diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 0000000..2071b23 --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile.am b/Makefile.am index 6b36a14..fd6f18a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +# SPDX-FileCopyrightText: EfficiOS Inc. + SUBDIRS = \ argpar \ tests ACLOCAL_AMFLAGS = -I m4 + +EXTRA_DIST = LICENSES diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..942cde7 --- /dev/null +++ b/README.adoc @@ -0,0 +1,202 @@ +// SPDX-FileCopyrightText: 2023 Philippe Proulx +// SPDX-License-Identifier: CC-BY-SA-4.0 + +// Render with Asciidoctor + += argpar +15 March 2024 +:bt2: Babeltrace{nbsp}2 +ifdef::env-github[] +:toc: macro +endif::[] +ifndef::env-github[] +:toc: left +endif::[] +:idprefix: +:idseparator: - + +**argpar**, an https://efficios.com/[EfficiOS] project, is yet another +open-source command-line argument parser for C/{cpp} programs. + +ifdef::env-github[] +toc::[] +endif::[] + +== Features + +The most important features of argpar are: + +* A single MIT-licensed, independent C99 source file and its header that + you can copy as is into your own project. + +* Declarative description of expected options: ++ +[source,c] +---- +enum my_opt_id { + MY_OPT_ID_DATA, + MY_OPT_ID_SQUEEZE, + MY_OPT_ID_MEOW, +}; + +static const struct argpar_opt_descr descrs[] = { + { MY_OPT_ID_DATA, 'd', NULL, false }, + { MY_OPT_ID_SQUEEZE, '\0', "squeeze", true }, + { MY_OPT_ID_MEOW, 'm', "meow", true }, + ARGPAR_OPT_DESCR_SENTINEL, +}; +---- + +* Supports short (`-k`) and long (`--kernel`) options. ++ +Multiple short options may be concatenated, and the argument of the +last one may be "`attached`" (`-xvfmyfile`). ++ +The argument of a long option may follow a space (`--meow{nbsp}mix`) or +an `=` sign (`--meow=mix`). + +* Supports repeated options: ++ +---- +--meow=mix salut -f4 /path/to/file --meow blend -cqc +---- + +* Fully documented, `const`-correct C99 API based on an argument + iterator. ++ +The `argpar_iter_next()` function produces items in the same order that +it parses original arguments, including non-option items. This means, +for example, that for: ++ +---- +--hello --count=23 /path/to/file -ab --type file -- magie +---- ++ +`argpar_iter_next()` produces the following items, in this order: + +** Option item: `--hello`. +** Option item: `--count` with argument `23`. +** Non-option item: `/path/to/file`. +** Option item: `-a`. +** Option item: `-b`. +** Option item: `--type` with argument `file`. +** Non-option item: `--`. +** Non-option item: `magie`. + +* On parsing error, provides a detailed error object including the index + of the argument (in `argv`) that caused the error as well as the name, + if available, of the unknown option. + +== Known limitations + +Compared to other similar open-source command-line argument parsers, +argpar has the following known limitations: + +* Doesn't support abbreviated long options. ++ +For example, if your option descriptor describes `--fraction`, then +`argpar_iter_next()` won't parse `--frac=23`: it will return an unknown +option error instead. + +* Doesn't explicitly support "`end of option`" (`--`). ++ +This is valid: ++ +---- +--hello=world --meow -- mix --hut=23 +---- ++ +`argpar_iter_next()` provides the `--` argument as a non-option item. + +* Doesn't support a non-option argument having the form of an option, + for example if you need to pass the exact relative path `--calorie`. ++ +In that case, you would need to pass `./--calorie`. There's no generic +way to escape `-` as of this version. This is in part because argpar +doesn't support "`end of option`" (`--`). ++ +As a workaround, because argpar offers an iterator API, you may: ++ +. Stop using `argpar_iter_next()` from the first `--` non-option item + __**ITEM**__. +. Use `argpar_item_non_opt_orig_index()` with __**ITEM**__ to get the + original index __**I**__ of the first `--` within `argv` (as passed + to `argpar_iter_create()`). +. Read the remaining non-option arguments from `argv`, starting at + __**I**__{nbsp}+{nbsp}1. + +* Doesn't handle the `-h`/`--help` option in a special way (doesn't show + some automatic usage message). + +* Doesn't provide direct access to the value of an option. ++ +This is because argpar offers an iterator API to support positional and +repeated options. + +== Build argpar + +To use argpar in your own project, simply copy the `argpar/argpar.c` and +`argpar/argpar.h` files and you're ready to go. + +To build this project, make sure you have +https://docs.gtk.org/glib/[GLib]{nbsp}2 (required by the tests), and +then: + +. **If you build from a Git clone**, run: ++ +[role="term"] +---- +$ ./bootstrap +---- ++ +This generates the `configure` script and other important files. + +. Configure the project: ++ +[role="term"] +---- +$ ./configure +---- ++ +See `./configure --help` for more options. + +. Build the project: ++ +[role="term"] +---- +$ make +---- + +== Build the API documentation + +To build the API documentation, make sure you have +https://www.doxygen.nl/[Doxygen], and then: + +* From the root of the project, run: ++ +---- +$ doxygen +---- + +Open `api-doc/html/index.html` with Netscape Navigator. + +== Run the tests + +To run the argpar tests: + +. <>. + +. Run the tests: ++ +[role="term"] +---- +$ make check +---- + +== Community + +argpar uses https://review.lttng.org/admin/repos/argpar,general[Gerrit] +for code review. + +To report a bug, https://github.com/efficios/argpar/issues/new[create a +GitHub issue]. diff --git a/argpar/Makefile.am b/argpar/Makefile.am index 175526a..ed7db0e 100644 --- a/argpar/Makefile.am +++ b/argpar/Makefile.am @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +# SPDX-FileCopyrightText: EfficiOS Inc. + noinst_LTLIBRARIES = libargpar.la libargpar_la_SOURCES = argpar.c argpar.h diff --git a/argpar/argpar.c b/argpar/argpar.c index 82b561c..5c3d8fa 100644 --- a/argpar/argpar.c +++ b/argpar/argpar.c @@ -1,11 +1,9 @@ /* * SPDX-License-Identifier: MIT - * - * Copyright (c) 2019-2021 Philippe Proulx - * Copyright (c) 2020-2021 Simon Marchi + * SPDX-FileCopyrightText: 2019-2024 Philippe Proulx + * SPDX-FileCopyrightText: 2020-2024 Simon Marchi */ -#include #include #include #include @@ -13,23 +11,34 @@ #include "argpar.h" -#define ARGPAR_REALLOC(_ptr, _type, _nmemb) \ - ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type))) +/* + * If argpar is used in some shared library, we don't want said library + * to export its symbols, so mark them as "hidden". + * + * On Windows, symbols are local unless explicitly exported; see + * . + */ +#if defined(_WIN32) || defined(__CYGWIN__) +# define ARGPAR_HIDDEN +#else +# define ARGPAR_HIDDEN __attribute__((visibility("hidden"))) +#endif + +#define ARGPAR_REALLOC(_ptr, _type, _nmemb) ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type))) -#define ARGPAR_CALLOC(_type, _nmemb) \ - ((_type *) calloc((_nmemb), sizeof(_type))) +#define ARGPAR_CALLOC(_type, _nmemb) ((_type *) calloc((_nmemb), sizeof(_type))) #define ARGPAR_ZALLOC(_type) ARGPAR_CALLOC(_type, 1) #ifdef NDEBUG /* - * Force usage of the assertion condition to prevent unused variable warnings - * when `assert()` are disabled by the `NDEBUG` definition. + * Force usage of the assertion condition to prevent unused variable + * warnings when `assert()` are disabled by the `NDEBUG` definition. */ -# define ARGPAR_ASSERT(_cond) ((void) sizeof((void) (_cond), 0)) +# define ARGPAR_ASSERT(_cond) ((void) sizeof((void) (_cond), 0)) #else -# include -# define ARGPAR_ASSERT(_cond) assert(_cond) +# include +# define ARGPAR_ASSERT(_cond) assert(_cond) #endif /* @@ -38,163 +47,159 @@ * Such a structure contains the state of an iterator between calls to * argpar_iter_next(). */ -struct argpar_iter { - /* - * Data provided by the user to argpar_iter_create(); immutable - * afterwards. - */ - struct { - unsigned int argc; - const char * const *argv; - const struct argpar_opt_descr *descrs; - } user; - - /* - * Index of the argument to process in the next - * argpar_iter_next() call. - */ - unsigned int i; - - /* Counter of non-option arguments */ - int non_opt_index; - - /* - * Current character within the current short option group: if - * it's not `NULL`, the parser is within a short option group, - * therefore it must resume there in the next argpar_iter_next() - * call. - */ - const char *short_opt_group_ch; - - /* Temporary character buffer which only grows */ - struct { - size_t size; - char *data; - } tmp_buf; +struct argpar_iter +{ + /* + * Data provided by the user to argpar_iter_create(); immutable + * afterwards. + */ + struct + { + unsigned int argc; + const char * const *argv; + const argpar_opt_descr_t *descrs; + } user; + + /* + * Index of the argument to process in the next + * argpar_iter_next() call. + */ + unsigned int i; + + /* Counter of non-option arguments */ + int non_opt_index; + + /* + * Current character within the current short option group: if + * it's not `NULL`, the parser is within a short option group, + * therefore it must resume there in the next argpar_iter_next() + * call. + */ + const char *short_opt_group_ch; + + /* Temporary character buffer which only grows */ + struct + { + size_t size; + char *data; + } tmp_buf; }; /* Base parsing item */ -struct argpar_item { - enum argpar_item_type type; +struct argpar_item +{ + argpar_item_type_t type; }; /* Option parsing item */ -struct argpar_item_opt { - struct argpar_item base; +typedef struct argpar_item_opt +{ + argpar_item_t base; - /* Corresponding descriptor */ - const struct argpar_opt_descr *descr; + /* Corresponding descriptor */ + const argpar_opt_descr_t *descr; - /* Argument, or `NULL` if none; owned by this */ - char *arg; -}; + /* Argument, or `NULL` if none; owned by this */ + char *arg; +} argpar_item_opt_t; /* Non-option parsing item */ -struct argpar_item_non_opt { - struct argpar_item base; - - /* - * Complete argument, pointing to one of the entries of the - * original arguments (`argv`). - */ - const char *arg; - - /* - * Index of this argument amongst all original arguments - * (`argv`). - */ - unsigned int orig_index; - - /* Index of this argument amongst other non-option arguments */ - unsigned int non_opt_index; -}; +typedef struct argpar_item_non_opt +{ + argpar_item_t base; + + /* + * Complete argument, pointing to one of the entries of the + * original arguments (`argv`). + */ + const char *arg; + + /* + * Index of this argument amongst all original arguments + * (`argv`). + */ + unsigned int orig_index; + + /* Index of this argument amongst other non-option arguments */ + unsigned int non_opt_index; +} argpar_item_non_opt_t; /* Parsing error */ -struct argpar_error { - /* Error type */ - enum argpar_error_type type; +struct argpar_error +{ + /* Error type */ + argpar_error_type_t type; - /* Original argument index */ - unsigned int orig_index; + /* Original argument index */ + unsigned int orig_index; - /* Name of unknown option; owned by this */ - char *unknown_opt_name; + /* Name of unknown option; owned by this */ + char *unknown_opt_name; - /* Option descriptor */ - const struct argpar_opt_descr *opt_descr; + /* Option descriptor */ + const argpar_opt_descr_t *opt_descr; - /* `true` if a short option caused the error */ - bool is_short; + /* `true` if a short option caused the error */ + bool is_short; }; -ARGPAR_HIDDEN -enum argpar_item_type argpar_item_type(const struct argpar_item * const item) +ARGPAR_HIDDEN argpar_item_type_t argpar_item_type(const argpar_item_t * const item) { - ARGPAR_ASSERT(item); - return item->type; + ARGPAR_ASSERT(item); + return item->type; } -ARGPAR_HIDDEN -const struct argpar_opt_descr *argpar_item_opt_descr( - const struct argpar_item * const item) +ARGPAR_HIDDEN const argpar_opt_descr_t *argpar_item_opt_descr(const argpar_item_t * const item) { - ARGPAR_ASSERT(item); - ARGPAR_ASSERT(item->type == ARGPAR_ITEM_TYPE_OPT); - return ((const struct argpar_item_opt *) item)->descr; + ARGPAR_ASSERT(item); + ARGPAR_ASSERT(item->type == ARGPAR_ITEM_TYPE_OPT); + return ((const argpar_item_opt_t *) item)->descr; } -ARGPAR_HIDDEN -const char *argpar_item_opt_arg(const struct argpar_item * const item) +ARGPAR_HIDDEN const char *argpar_item_opt_arg(const argpar_item_t * const item) { - ARGPAR_ASSERT(item); - ARGPAR_ASSERT(item->type == ARGPAR_ITEM_TYPE_OPT); - return ((const struct argpar_item_opt *) item)->arg; + ARGPAR_ASSERT(item); + ARGPAR_ASSERT(item->type == ARGPAR_ITEM_TYPE_OPT); + return ((const argpar_item_opt_t *) item)->arg; } -ARGPAR_HIDDEN -const char *argpar_item_non_opt_arg(const struct argpar_item * const item) +ARGPAR_HIDDEN const char *argpar_item_non_opt_arg(const argpar_item_t * const item) { - ARGPAR_ASSERT(item); - ARGPAR_ASSERT(item->type == ARGPAR_ITEM_TYPE_NON_OPT); - return ((const struct argpar_item_non_opt *) item)->arg; + ARGPAR_ASSERT(item); + ARGPAR_ASSERT(item->type == ARGPAR_ITEM_TYPE_NON_OPT); + return ((const argpar_item_non_opt_t *) item)->arg; } -ARGPAR_HIDDEN -unsigned int argpar_item_non_opt_orig_index( - const struct argpar_item * const item) +ARGPAR_HIDDEN unsigned int argpar_item_non_opt_orig_index(const argpar_item_t * const item) { - ARGPAR_ASSERT(item); - ARGPAR_ASSERT(item->type == ARGPAR_ITEM_TYPE_NON_OPT); - return ((const struct argpar_item_non_opt *) item)->orig_index; + ARGPAR_ASSERT(item); + ARGPAR_ASSERT(item->type == ARGPAR_ITEM_TYPE_NON_OPT); + return ((const argpar_item_non_opt_t *) item)->orig_index; } -ARGPAR_HIDDEN -unsigned int argpar_item_non_opt_non_opt_index( - const struct argpar_item * const item) +ARGPAR_HIDDEN unsigned int argpar_item_non_opt_non_opt_index(const argpar_item_t * const item) { - ARGPAR_ASSERT(item); - ARGPAR_ASSERT(item->type == ARGPAR_ITEM_TYPE_NON_OPT); - return ((const struct argpar_item_non_opt *) item)->non_opt_index; + ARGPAR_ASSERT(item); + ARGPAR_ASSERT(item->type == ARGPAR_ITEM_TYPE_NON_OPT); + return ((const argpar_item_non_opt_t *) item)->non_opt_index; } -ARGPAR_HIDDEN -void argpar_item_destroy(const struct argpar_item * const item) +ARGPAR_HIDDEN void argpar_item_destroy(const argpar_item_t * const item) { - if (!item) { - goto end; - } + if (!item) { + goto end; + } - if (item->type == ARGPAR_ITEM_TYPE_OPT) { - struct argpar_item_opt * const opt_item = - (struct argpar_item_opt *) item; + if (item->type == ARGPAR_ITEM_TYPE_OPT) { + argpar_item_opt_t * const opt_item = (argpar_item_opt_t *) item; - free(opt_item->arg); - } + free(opt_item->arg); + } - free((void *) item); + free((void *) item); end: - return; + return; } /* @@ -203,36 +208,33 @@ end: * * Returns `NULL` on memory error. */ -static -struct argpar_item_opt *create_opt_item( - const struct argpar_opt_descr * const descr, - const char * const arg) +static argpar_item_opt_t *create_opt_item(const argpar_opt_descr_t * const descr, + const char * const arg) { - struct argpar_item_opt *opt_item = - ARGPAR_ZALLOC(struct argpar_item_opt); + argpar_item_opt_t *opt_item = ARGPAR_ZALLOC(argpar_item_opt_t); - if (!opt_item) { - goto end; - } + if (!opt_item) { + goto end; + } - opt_item->base.type = ARGPAR_ITEM_TYPE_OPT; - opt_item->descr = descr; + opt_item->base.type = ARGPAR_ITEM_TYPE_OPT; + opt_item->descr = descr; - if (arg) { - opt_item->arg = strdup(arg); - if (!opt_item->arg) { - goto error; - } - } + if (arg) { + opt_item->arg = strdup(arg); + if (!opt_item->arg) { + goto error; + } + } - goto end; + goto end; error: - argpar_item_destroy(&opt_item->base); - opt_item = NULL; + argpar_item_destroy(&opt_item->base); + opt_item = NULL; end: - return opt_item; + return opt_item; } /* @@ -242,25 +244,23 @@ end: * * Returns `NULL` on memory error. */ -static -struct argpar_item_non_opt *create_non_opt_item(const char * const arg, - const unsigned int orig_index, - const unsigned int non_opt_index) +static argpar_item_non_opt_t *create_non_opt_item(const char * const arg, + const unsigned int orig_index, + const unsigned int non_opt_index) { - struct argpar_item_non_opt * const non_opt_item = - ARGPAR_ZALLOC(struct argpar_item_non_opt); + argpar_item_non_opt_t * const non_opt_item = ARGPAR_ZALLOC(argpar_item_non_opt_t); - if (!non_opt_item) { - goto end; - } + if (!non_opt_item) { + goto end; + } - non_opt_item->base.type = ARGPAR_ITEM_TYPE_NON_OPT; - non_opt_item->arg = arg; - non_opt_item->orig_index = orig_index; - non_opt_item->non_opt_index = non_opt_index; + non_opt_item->base.type = ARGPAR_ITEM_TYPE_NON_OPT; + non_opt_item->arg = arg; + non_opt_item->orig_index = orig_index; + non_opt_item->non_opt_index = non_opt_index; end: - return non_opt_item; + return non_opt_item; } /* @@ -274,102 +274,92 @@ end: * Returns 0 on success (including if `error` is `NULL`) or -1 on memory * error. */ -static -int set_error(struct argpar_error ** const error, - enum argpar_error_type type, - const char * const unknown_opt_name, - const struct argpar_opt_descr * const opt_descr, - const bool is_short) +static int set_error(argpar_error_t ** const error, argpar_error_type_t type, + const char * const unknown_opt_name, + const argpar_opt_descr_t * const opt_descr, const bool is_short) { - int ret = 0; + int ret = 0; - if (!error) { - goto end; - } + if (!error) { + goto end; + } - *error = ARGPAR_ZALLOC(struct argpar_error); - if (!*error) { - goto error; - } + *error = ARGPAR_ZALLOC(argpar_error_t); + if (!*error) { + goto error; + } - (*error)->type = type; + (*error)->type = type; - if (unknown_opt_name) { - (*error)->unknown_opt_name = ARGPAR_CALLOC(char, - strlen(unknown_opt_name) + 1 + (is_short ? 1 : 2)); - if (!(*error)->unknown_opt_name) { - goto error; - } + if (unknown_opt_name) { + (*error)->unknown_opt_name = + ARGPAR_CALLOC(char, strlen(unknown_opt_name) + 1 + (is_short ? 1 : 2)); + if (!(*error)->unknown_opt_name) { + goto error; + } - if (is_short) { - strcpy((*error)->unknown_opt_name, "-"); - } else { - strcpy((*error)->unknown_opt_name, "--"); - } + if (is_short) { + strcpy((*error)->unknown_opt_name, "-"); + } else { + strcpy((*error)->unknown_opt_name, "--"); + } - strcat((*error)->unknown_opt_name, unknown_opt_name); - } + strcat((*error)->unknown_opt_name, unknown_opt_name); + } - (*error)->opt_descr = opt_descr; - (*error)->is_short = is_short; - goto end; + (*error)->opt_descr = opt_descr; + (*error)->is_short = is_short; + goto end; error: - argpar_error_destroy(*error); - ret = -1; + argpar_error_destroy(*error); + ret = -1; end: - return ret; + return ret; } -ARGPAR_HIDDEN -enum argpar_error_type argpar_error_type( - const struct argpar_error * const error) +ARGPAR_HIDDEN argpar_error_type_t argpar_error_type(const argpar_error_t * const error) { - ARGPAR_ASSERT(error); - return error->type; + ARGPAR_ASSERT(error); + return error->type; } -ARGPAR_HIDDEN -unsigned int argpar_error_orig_index(const struct argpar_error * const error) +ARGPAR_HIDDEN unsigned int argpar_error_orig_index(const argpar_error_t * const error) { - ARGPAR_ASSERT(error); - return error->orig_index; + ARGPAR_ASSERT(error); + return error->orig_index; } -ARGPAR_HIDDEN -const char *argpar_error_unknown_opt_name( - const struct argpar_error * const error) +ARGPAR_HIDDEN const char *argpar_error_unknown_opt_name(const argpar_error_t * const error) { - ARGPAR_ASSERT(error); - ARGPAR_ASSERT(error->type == ARGPAR_ERROR_TYPE_UNKNOWN_OPT); - ARGPAR_ASSERT(error->unknown_opt_name); - return error->unknown_opt_name; + ARGPAR_ASSERT(error); + ARGPAR_ASSERT(error->type == ARGPAR_ERROR_TYPE_UNKNOWN_OPT); + ARGPAR_ASSERT(error->unknown_opt_name); + return error->unknown_opt_name; } -ARGPAR_HIDDEN -const struct argpar_opt_descr *argpar_error_opt_descr( - const struct argpar_error * const error, bool * const is_short) +ARGPAR_HIDDEN const argpar_opt_descr_t *argpar_error_opt_descr(const argpar_error_t * const error, + bool * const is_short) { - ARGPAR_ASSERT(error); - ARGPAR_ASSERT(error->type == ARGPAR_ERROR_TYPE_MISSING_OPT_ARG || - error->type == ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG); - ARGPAR_ASSERT(error->opt_descr); + ARGPAR_ASSERT(error); + ARGPAR_ASSERT(error->type == ARGPAR_ERROR_TYPE_MISSING_OPT_ARG || + error->type == ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG); + ARGPAR_ASSERT(error->opt_descr); - if (is_short) { - *is_short = error->is_short; - } + if (is_short) { + *is_short = error->is_short; + } - return error->opt_descr; + return error->opt_descr; } -ARGPAR_HIDDEN -void argpar_error_destroy(const struct argpar_error * const error) +ARGPAR_HIDDEN void argpar_error_destroy(const argpar_error_t * const error) { - if (error) { - free(error->unknown_opt_name); - free((void *) error); - } + if (error) { + free(error->unknown_opt_name); + free((void *) error); + } } /* @@ -383,35 +373,32 @@ void argpar_error_destroy(const struct argpar_error * const error) * * Returns `NULL` if no descriptor is found. */ -static -const struct argpar_opt_descr *find_descr( - const struct argpar_opt_descr * const descrs, - const char short_name, const char * const long_name) +static const argpar_opt_descr_t *find_descr(const argpar_opt_descr_t * const descrs, + const char short_name, const char * const long_name) { - const struct argpar_opt_descr *descr; + const argpar_opt_descr_t *descr; - for (descr = descrs; descr->short_name || descr->long_name; descr++) { - if (short_name && descr->short_name && - short_name == descr->short_name) { - goto end; - } + for (descr = descrs; descr->short_name || descr->long_name; descr++) { + if (short_name && descr->short_name && short_name == descr->short_name) { + goto end; + } - if (long_name && descr->long_name && - strcmp(long_name, descr->long_name) == 0) { - goto end; - } - } + if (long_name && descr->long_name && strcmp(long_name, descr->long_name) == 0) { + goto end; + } + } end: - return !descr->short_name && !descr->long_name ? NULL : descr; + return !descr->short_name && !descr->long_name ? NULL : descr; } /* Return type of parse_short_opt_group() and parse_long_opt() */ -enum parse_orig_arg_opt_ret { - PARSE_ORIG_ARG_OPT_RET_OK, - PARSE_ORIG_ARG_OPT_RET_ERROR = -1, - PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY = -2, -}; +typedef enum parse_orig_arg_opt_ret +{ + PARSE_ORIG_ARG_OPT_RET_OK, + PARSE_ORIG_ARG_OPT_RET_ERROR = -1, + PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY = -2, +} parse_orig_arg_opt_ret_t; /* * Parses the short option group argument `short_opt_group`, starting @@ -422,98 +409,90 @@ enum parse_orig_arg_opt_ret { * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets * `*error`. */ -static -enum parse_orig_arg_opt_ret parse_short_opt_group( - const char * const short_opt_group, - const char * const next_orig_arg, - const struct argpar_opt_descr * const descrs, - struct argpar_iter * const iter, - struct argpar_error ** const error, - struct argpar_item ** const item) +static parse_orig_arg_opt_ret_t +parse_short_opt_group(const char * const short_opt_group, const char * const next_orig_arg, + const argpar_opt_descr_t * const descrs, argpar_iter_t * const iter, + argpar_error_t ** const error, argpar_item_t ** const item) { - enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; - bool used_next_orig_arg = false; - const char *opt_arg = NULL; - const struct argpar_opt_descr *descr; - struct argpar_item_opt *opt_item; - - ARGPAR_ASSERT(strlen(short_opt_group) != 0); - - if (!iter->short_opt_group_ch) { - iter->short_opt_group_ch = short_opt_group; - } - - /* Find corresponding option descriptor */ - descr = find_descr(descrs, *iter->short_opt_group_ch, NULL); - if (!descr) { - const char unknown_opt_name[] = - {*iter->short_opt_group_ch, '\0'}; - - ret = PARSE_ORIG_ARG_OPT_RET_ERROR; - - if (set_error(error, ARGPAR_ERROR_TYPE_UNKNOWN_OPT, - unknown_opt_name, NULL, true)) { - ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; - } - - goto error; - } - - if (descr->with_arg) { - if (iter->short_opt_group_ch[1]) { - /* `-oarg` form */ - opt_arg = &iter->short_opt_group_ch[1]; - } else { - /* `-o arg` form */ - opt_arg = next_orig_arg; - used_next_orig_arg = true; - } - - /* - * We accept `-o ''` (empty option argument), but not - * `-o` alone if an option argument is expected. - */ - if (!opt_arg || (iter->short_opt_group_ch[1] && - strlen(opt_arg) == 0)) { - ret = PARSE_ORIG_ARG_OPT_RET_ERROR; - - if (set_error(error, ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, - NULL, descr, true)) { - ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; - } - - goto error; - } - } - - /* Create and append option argument */ - opt_item = create_opt_item(descr, opt_arg); - if (!opt_item) { - ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; - goto error; - } - - *item = &opt_item->base; - iter->short_opt_group_ch++; - - if (descr->with_arg || !*iter->short_opt_group_ch) { - /* Option has an argument: no more options */ - iter->short_opt_group_ch = NULL; - - if (used_next_orig_arg) { - iter->i += 2; - } else { - iter->i++; - } - } - - goto end; + parse_orig_arg_opt_ret_t ret = PARSE_ORIG_ARG_OPT_RET_OK; + bool used_next_orig_arg = false; + const char *opt_arg = NULL; + const argpar_opt_descr_t *descr; + argpar_item_opt_t *opt_item; + + ARGPAR_ASSERT(strlen(short_opt_group) != 0); + + if (!iter->short_opt_group_ch) { + iter->short_opt_group_ch = short_opt_group; + } + + /* Find corresponding option descriptor */ + descr = find_descr(descrs, *iter->short_opt_group_ch, NULL); + if (!descr) { + const char unknown_opt_name[] = {*iter->short_opt_group_ch, '\0'}; + + ret = PARSE_ORIG_ARG_OPT_RET_ERROR; + + if (set_error(error, ARGPAR_ERROR_TYPE_UNKNOWN_OPT, unknown_opt_name, NULL, true)) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; + } + + goto error; + } + + if (descr->with_arg) { + if (iter->short_opt_group_ch[1]) { + /* `-oarg` form */ + opt_arg = &iter->short_opt_group_ch[1]; + } else { + /* `-o arg` form */ + opt_arg = next_orig_arg; + used_next_orig_arg = true; + } + + /* + * We accept `-o ''` (empty option argument), but not + * `-o` alone if an option argument is expected. + */ + if (!opt_arg || (iter->short_opt_group_ch[1] && strlen(opt_arg) == 0)) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR; + + if (set_error(error, ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, NULL, descr, true)) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; + } + + goto error; + } + } + + /* Create and append option argument */ + opt_item = create_opt_item(descr, opt_arg); + if (!opt_item) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; + goto error; + } + + *item = &opt_item->base; + iter->short_opt_group_ch++; + + if (descr->with_arg || !*iter->short_opt_group_ch) { + /* Option has an argument: no more options */ + iter->short_opt_group_ch = NULL; + + if (used_next_orig_arg) { + iter->i += 2; + } else { + iter->i++; + } + } + + goto end; error: - ARGPAR_ASSERT(ret != PARSE_ORIG_ARG_OPT_RET_OK); + ARGPAR_ASSERT(ret != PARSE_ORIG_ARG_OPT_RET_OK); end: - return ret; + return ret; } /* @@ -524,120 +503,117 @@ end: * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets * `*error`. */ -static -enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, - const char * const next_orig_arg, - const struct argpar_opt_descr * const descrs, - struct argpar_iter * const iter, - struct argpar_error ** const error, - struct argpar_item ** const item) +static parse_orig_arg_opt_ret_t +parse_long_opt(const char * const long_opt_arg, const char * const next_orig_arg, + const argpar_opt_descr_t * const descrs, argpar_iter_t * const iter, + argpar_error_t ** const error, argpar_item_t ** const item) { - enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; - const struct argpar_opt_descr *descr; - struct argpar_item_opt *opt_item; - bool used_next_orig_arg = false; - - /* Option's argument, if any */ - const char *opt_arg = NULL; - - /* Position of first `=`, if any */ - const char *eq_pos; - - /* Option name */ - const char *long_opt_name = long_opt_arg; - - ARGPAR_ASSERT(strlen(long_opt_arg) != 0); - - /* Find the first `=` in original argument */ - eq_pos = strchr(long_opt_arg, '='); - if (eq_pos) { - const size_t long_opt_name_size = eq_pos - long_opt_arg; - - /* Isolate the option name */ - while (long_opt_name_size > iter->tmp_buf.size - 1) { - iter->tmp_buf.size *= 2; - iter->tmp_buf.data = ARGPAR_REALLOC(iter->tmp_buf.data, - char, iter->tmp_buf.size); - if (!iter->tmp_buf.data) { - ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; - goto error; - } - } - - memcpy(iter->tmp_buf.data, long_opt_arg, long_opt_name_size); - iter->tmp_buf.data[long_opt_name_size] = '\0'; - long_opt_name = iter->tmp_buf.data; - } - - /* Find corresponding option descriptor */ - descr = find_descr(descrs, '\0', long_opt_name); - if (!descr) { - ret = PARSE_ORIG_ARG_OPT_RET_ERROR; - - if (set_error(error, ARGPAR_ERROR_TYPE_UNKNOWN_OPT, - long_opt_name, NULL, false)) { - ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; - } - - goto error; - } - - /* Find option's argument if any */ - if (descr->with_arg) { - if (eq_pos) { - /* `--long-opt=arg` style */ - opt_arg = eq_pos + 1; - } else { - /* `--long-opt arg` style */ - if (!next_orig_arg) { - ret = PARSE_ORIG_ARG_OPT_RET_ERROR; - - if (set_error(error, ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, - NULL, descr, false)) { - ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; - } - - goto error; - } - - opt_arg = next_orig_arg; - used_next_orig_arg = true; - } - } else if (eq_pos) { - /* - * Unexpected `--opt=arg` style for a long option which - * doesn't accept an argument. - */ - ret = PARSE_ORIG_ARG_OPT_RET_ERROR; - - if (set_error(error, ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG, - NULL, descr, false)) { - ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; - } - - goto error; - } - - /* Create and append option argument */ - opt_item = create_opt_item(descr, opt_arg); - if (!opt_item) { - goto error; - } - - if (used_next_orig_arg) { - iter->i += 2; - } else { - iter->i++; - } - - *item = &opt_item->base; - goto end; + parse_orig_arg_opt_ret_t ret = PARSE_ORIG_ARG_OPT_RET_OK; + const argpar_opt_descr_t *descr; + argpar_item_opt_t *opt_item; + bool used_next_orig_arg = false; + + /* Option's argument, if any */ + const char *opt_arg = NULL; + + /* Position of first `=`, if any */ + const char *eq_pos; + + /* Option name */ + const char *long_opt_name = long_opt_arg; + + ARGPAR_ASSERT(strlen(long_opt_arg) != 0); + + /* Find the first `=` in original argument */ + eq_pos = strchr(long_opt_arg, '='); + if (eq_pos) { + const size_t long_opt_name_size = eq_pos - long_opt_arg; + + /* Isolate the option name */ + while (long_opt_name_size > iter->tmp_buf.size - 1) { + const size_t new_size = iter->tmp_buf.size * 2; + char * const new_data = ARGPAR_REALLOC(iter->tmp_buf.data, char, new_size); + + if (!new_data) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; + goto error; + } + + iter->tmp_buf.size = new_size; + iter->tmp_buf.data = new_data; + } + + memcpy(iter->tmp_buf.data, long_opt_arg, long_opt_name_size); + iter->tmp_buf.data[long_opt_name_size] = '\0'; + long_opt_name = iter->tmp_buf.data; + } + + /* Find corresponding option descriptor */ + descr = find_descr(descrs, '\0', long_opt_name); + if (!descr) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR; + + if (set_error(error, ARGPAR_ERROR_TYPE_UNKNOWN_OPT, long_opt_name, NULL, false)) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; + } + + goto error; + } + + /* Find option's argument if any */ + if (descr->with_arg) { + if (eq_pos) { + /* `--long-opt=arg` style */ + opt_arg = eq_pos + 1; + } else { + /* `--long-opt arg` style */ + if (!next_orig_arg) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR; + + if (set_error(error, ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, NULL, descr, false)) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; + } + + goto error; + } + + opt_arg = next_orig_arg; + used_next_orig_arg = true; + } + } else if (eq_pos) { + /* + * Unexpected `--opt=arg` style for a long option which + * doesn't accept an argument. + */ + ret = PARSE_ORIG_ARG_OPT_RET_ERROR; + + if (set_error(error, ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG, NULL, descr, false)) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; + } + + goto error; + } + + /* Create and append option argument */ + opt_item = create_opt_item(descr, opt_arg); + if (!opt_item) { + goto error; + } + + if (used_next_orig_arg) { + iter->i += 2; + } else { + iter->i++; + } + + *item = &opt_item->base; + goto end; error: - ARGPAR_ASSERT(ret != PARSE_ORIG_ARG_OPT_RET_OK); + ARGPAR_ASSERT(ret != PARSE_ORIG_ARG_OPT_RET_OK); end: - return ret; + return ret; } /* @@ -648,142 +624,126 @@ end: * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets * `*error`. */ -static -enum parse_orig_arg_opt_ret parse_orig_arg_opt(const char * const orig_arg, - const char * const next_orig_arg, - const struct argpar_opt_descr * const descrs, - struct argpar_iter * const iter, - struct argpar_error ** const error, - struct argpar_item ** const item) +static parse_orig_arg_opt_ret_t +parse_orig_arg_opt(const char * const orig_arg, const char * const next_orig_arg, + const argpar_opt_descr_t * const descrs, argpar_iter_t * const iter, + argpar_error_t ** const error, argpar_item_t ** const item) { - enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; + parse_orig_arg_opt_ret_t ret = PARSE_ORIG_ARG_OPT_RET_OK; - ARGPAR_ASSERT(orig_arg[0] == '-'); + ARGPAR_ASSERT(orig_arg[0] == '-'); - if (orig_arg[1] == '-') { - /* Long option */ - ret = parse_long_opt(&orig_arg[2], - next_orig_arg, descrs, iter, error, item); - } else { - /* Short option */ - ret = parse_short_opt_group(&orig_arg[1], - next_orig_arg, descrs, iter, error, item); - } + if (orig_arg[1] == '-') { + /* Long option */ + ret = parse_long_opt(&orig_arg[2], next_orig_arg, descrs, iter, error, item); + } else { + /* Short option */ + ret = parse_short_opt_group(&orig_arg[1], next_orig_arg, descrs, iter, error, item); + } - return ret; + return ret; } -ARGPAR_HIDDEN -struct argpar_iter *argpar_iter_create(const unsigned int argc, - const char * const * const argv, - const struct argpar_opt_descr * const descrs) +ARGPAR_HIDDEN argpar_iter_t *argpar_iter_create(const unsigned int argc, + const char * const * const argv, + const argpar_opt_descr_t * const descrs) { - struct argpar_iter *iter = ARGPAR_ZALLOC(struct argpar_iter); - - if (!iter) { - goto end; - } - - iter->user.argc = argc; - iter->user.argv = argv; - iter->user.descrs = descrs; - iter->tmp_buf.size = 128; - iter->tmp_buf.data = ARGPAR_CALLOC(char, iter->tmp_buf.size); - if (!iter->tmp_buf.data) { - argpar_iter_destroy(iter); - iter = NULL; - goto end; - } + argpar_iter_t *iter = ARGPAR_ZALLOC(argpar_iter_t); + + if (!iter) { + goto end; + } + + iter->user.argc = argc; + iter->user.argv = argv; + iter->user.descrs = descrs; + iter->tmp_buf.size = 128; + iter->tmp_buf.data = ARGPAR_CALLOC(char, iter->tmp_buf.size); + if (!iter->tmp_buf.data) { + argpar_iter_destroy(iter); + iter = NULL; + goto end; + } end: - return iter; + return iter; } -ARGPAR_HIDDEN -void argpar_iter_destroy(struct argpar_iter * const iter) +ARGPAR_HIDDEN void argpar_iter_destroy(argpar_iter_t * const iter) { - if (iter) { - free(iter->tmp_buf.data); - free(iter); - } + if (iter) { + free(iter->tmp_buf.data); + free(iter); + } } -ARGPAR_HIDDEN -enum argpar_iter_next_status argpar_iter_next( - struct argpar_iter * const iter, - const struct argpar_item ** const item, - const struct argpar_error ** const error) +ARGPAR_HIDDEN argpar_iter_next_status_t argpar_iter_next(argpar_iter_t * const iter, + const argpar_item_t ** const item, + const argpar_error_t ** const error) { - enum argpar_iter_next_status status; - enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret; - const char *orig_arg; - const char *next_orig_arg; - struct argpar_error ** const nc_error = (struct argpar_error **) error; - - ARGPAR_ASSERT(iter->i <= iter->user.argc); - - if (error) { - *nc_error = NULL; - } - - if (iter->i == iter->user.argc) { - status = ARGPAR_ITER_NEXT_STATUS_END; - goto end; - } - - orig_arg = iter->user.argv[iter->i]; - next_orig_arg = - iter->i < (iter->user.argc - 1) ? - iter->user.argv[iter->i + 1] : NULL; - - if (strcmp(orig_arg, "-") == 0 || strcmp(orig_arg, "--") == 0 || - orig_arg[0] != '-') { - /* Non-option argument */ - const struct argpar_item_non_opt * const non_opt_item = - create_non_opt_item(orig_arg, iter->i, - iter->non_opt_index); - - if (!non_opt_item) { - status = ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY; - goto end; - } - - iter->non_opt_index++; - iter->i++; - *item = &non_opt_item->base; - status = ARGPAR_ITER_NEXT_STATUS_OK; - goto end; - } - - /* Option argument */ - parse_orig_arg_opt_ret = parse_orig_arg_opt(orig_arg, - next_orig_arg, iter->user.descrs, iter, nc_error, - (struct argpar_item **) item); - switch (parse_orig_arg_opt_ret) { - case PARSE_ORIG_ARG_OPT_RET_OK: - status = ARGPAR_ITER_NEXT_STATUS_OK; - break; - case PARSE_ORIG_ARG_OPT_RET_ERROR: - if (error) { - ARGPAR_ASSERT(*error); - (*nc_error)->orig_index = iter->i; - } - status = ARGPAR_ITER_NEXT_STATUS_ERROR; - break; - case PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY: - status = ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY; - break; - default: - abort(); - } + argpar_iter_next_status_t status; + parse_orig_arg_opt_ret_t parse_orig_arg_opt_ret; + const char *orig_arg; + const char *next_orig_arg; + argpar_error_t ** const nc_error = (argpar_error_t **) error; + + ARGPAR_ASSERT(iter->i <= iter->user.argc); + + if (error) { + *nc_error = NULL; + } + + if (iter->i == iter->user.argc) { + status = ARGPAR_ITER_NEXT_STATUS_END; + goto end; + } + + orig_arg = iter->user.argv[iter->i]; + next_orig_arg = iter->i < (iter->user.argc - 1) ? iter->user.argv[iter->i + 1] : NULL; + + if (strcmp(orig_arg, "-") == 0 || strcmp(orig_arg, "--") == 0 || orig_arg[0] != '-') { + /* Non-option argument */ + const argpar_item_non_opt_t * const non_opt_item = + create_non_opt_item(orig_arg, iter->i, iter->non_opt_index); + + if (!non_opt_item) { + status = ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY; + goto end; + } + + iter->non_opt_index++; + iter->i++; + *item = &non_opt_item->base; + status = ARGPAR_ITER_NEXT_STATUS_OK; + goto end; + } + + /* Option argument */ + parse_orig_arg_opt_ret = parse_orig_arg_opt(orig_arg, next_orig_arg, iter->user.descrs, iter, + nc_error, (argpar_item_t **) item); + switch (parse_orig_arg_opt_ret) { + case PARSE_ORIG_ARG_OPT_RET_OK: + status = ARGPAR_ITER_NEXT_STATUS_OK; + break; + case PARSE_ORIG_ARG_OPT_RET_ERROR: + if (error) { + ARGPAR_ASSERT(*error); + (*nc_error)->orig_index = iter->i; + } + status = ARGPAR_ITER_NEXT_STATUS_ERROR; + break; + case PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY: + status = ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY; + break; + default: + abort(); + } end: - return status; + return status; } -ARGPAR_HIDDEN -unsigned int argpar_iter_ingested_orig_args( - const struct argpar_iter * const iter) +ARGPAR_HIDDEN unsigned int argpar_iter_ingested_orig_args(const argpar_iter_t * const iter) { - return iter->i; + return iter->i; } diff --git a/argpar/argpar.h b/argpar/argpar.h index 27503c5..d11a764 100644 --- a/argpar/argpar.h +++ b/argpar/argpar.h @@ -1,8 +1,7 @@ /* * SPDX-License-Identifier: MIT - * - * Copyright (c) 2019-2021 Philippe Proulx - * Copyright (c) 2020-2021 Simon Marchi + * SPDX-FileCopyrightText: 2019-2024 Philippe Proulx + * SPDX-FileCopyrightText: 2020-2024 Simon Marchi */ #ifndef ARGPAR_ARGPAR_H @@ -10,6 +9,10 @@ #include +#if defined(__cplusplus) +extern "C" { +#endif + /*! @mainpage @@ -83,7 +86,7 @@ A parsing item (the result of argpar_iter_next()) has the type #argpar_item. Get the type (option or non-option) of an item with -\link argpar_item_type(const struct argpar_item *) argpar_item_type()\endlink. +\link argpar_item_type(const argpar_item_t *) argpar_item_type()\endlink. Each item type has its set of dedicated functions (\c argpar_item_opt_ and \c argpar_item_non_opt_ prefixes). @@ -97,30 +100,24 @@ example, that for: argpar_iter_next() produces the following items, in this order: --# Option item (\--hello). --# Option item (\--count with argument 23). --# Non-option item (/path/to/file). --# Option item (-a). --# Option item (-b). --# Option item (\--type with argument file). --# Non-option item (\--). --# Non-option item (magie). +-# Option item: \--hello. +-# Option item: \--count with argument 23. +-# Non-option item: /path/to/file. +-# Option item: -a. +-# Option item: -b. +-# Option item: \--type with argument file. +-# Non-option item: \--. +-# Non-option item: magie. */ -/* - * If argpar is used in some shared library, we don't want said library - * to export its symbols, so mark them as "hidden". - * - * On Windows, symbols are local unless explicitly exported; see - * . - */ -#if defined(_WIN32) || defined(__CYGWIN__) -# define ARGPAR_HIDDEN +/* Internal: `noexcept` specifier if C++ ≥ 11 */ +#if defined(__cplusplus) && (__cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900)) +# define ARGPAR_NOEXCEPT noexcept #else -# define ARGPAR_HIDDEN __attribute__((visibility("hidden"))) +# define ARGPAR_NOEXCEPT #endif -struct argpar_opt_descr; +typedef struct argpar_opt_descr argpar_opt_descr_t; /*! @name Item API @@ -130,15 +127,16 @@ struct argpar_opt_descr; /*! @brief Type of a parsing item, as returned by - \link argpar_item_type(const struct argpar_item *) argpar_item_type()\endlink. + \link argpar_item_type(const argpar_item *) argpar_item_type()\endlink. */ -enum argpar_item_type { - /// Option - ARGPAR_ITEM_TYPE_OPT, +typedef enum argpar_item_type +{ + /// Option + ARGPAR_ITEM_TYPE_OPT, - /// Non-option - ARGPAR_ITEM_TYPE_NON_OPT, -}; + /// Non-option + ARGPAR_ITEM_TYPE_NON_OPT, +} argpar_item_type_t; /*! @struct argpar_item @@ -148,7 +146,7 @@ enum argpar_item_type { argpar_iter_next() sets a pointer to such a type. */ -struct argpar_item; +typedef struct argpar_item argpar_item_t; /*! @brief @@ -163,10 +161,7 @@ struct argpar_item; @pre \p item is not \c NULL. */ -/// @cond hidden_macro -ARGPAR_HIDDEN -/// @endcond -enum argpar_item_type argpar_item_type(const struct argpar_item *item); +argpar_item_type_t argpar_item_type(const argpar_item_t *item) ARGPAR_NOEXCEPT; /*! @brief @@ -183,11 +178,7 @@ enum argpar_item_type argpar_item_type(const struct argpar_item *item); @pre \p item has the type #ARGPAR_ITEM_TYPE_OPT. */ -/// @cond hidden_macro -ARGPAR_HIDDEN -/// @endcond -const struct argpar_opt_descr *argpar_item_opt_descr( - const struct argpar_item *item); +const argpar_opt_descr_t *argpar_item_opt_descr(const argpar_item_t *item) ARGPAR_NOEXCEPT; /*! @brief @@ -205,10 +196,7 @@ const struct argpar_opt_descr *argpar_item_opt_descr( @pre \p item has the type #ARGPAR_ITEM_TYPE_OPT. */ -/// @cond hidden_macro -ARGPAR_HIDDEN -/// @endcond -const char *argpar_item_opt_arg(const struct argpar_item *item); +const char *argpar_item_opt_arg(const argpar_item_t *item) ARGPAR_NOEXCEPT; /*! @brief @@ -228,10 +216,7 @@ const char *argpar_item_opt_arg(const struct argpar_item *item); @pre \p item has the type #ARGPAR_ITEM_TYPE_NON_OPT. */ -/// @cond hidden_macro -ARGPAR_HIDDEN -/// @endcond -const char *argpar_item_non_opt_arg(const struct argpar_item *item); +const char *argpar_item_non_opt_arg(const argpar_item_t *item) ARGPAR_NOEXCEPT; /*! @brief @@ -264,10 +249,7 @@ argument index of \c mix is 4. argpar_item_non_opt_non_opt_index() -- Returns the non-option index of a non-option parsing item. */ -/// @cond hidden_macro -ARGPAR_HIDDEN -/// @endcond -unsigned int argpar_item_non_opt_orig_index(const struct argpar_item *item); +unsigned int argpar_item_non_opt_orig_index(const argpar_item_t *item) ARGPAR_NOEXCEPT; /*! @brief @@ -299,10 +281,7 @@ argument index of \c mix is 1. argpar_item_non_opt_orig_index() -- Returns the original argument index of a non-option parsing item. */ -/// @cond hidden_macro -ARGPAR_HIDDEN -/// @endcond -unsigned int argpar_item_non_opt_non_opt_index(const struct argpar_item *item); +unsigned int argpar_item_non_opt_non_opt_index(const argpar_item_t *item) ARGPAR_NOEXCEPT; /*! @brief @@ -311,10 +290,7 @@ unsigned int argpar_item_non_opt_non_opt_index(const struct argpar_item *item); @param[in] item Parsing item to destroy (may be \c NULL). */ -/// @cond hidden_macro -ARGPAR_HIDDEN -/// @endcond -void argpar_item_destroy(const struct argpar_item *item); +void argpar_item_destroy(const argpar_item_t *item) ARGPAR_NOEXCEPT; /*! @def ARGPAR_ITEM_DESTROY_AND_RESET(_item) @@ -325,13 +301,13 @@ void argpar_item_destroy(const struct argpar_item *item); @param[in] _item Item to destroy and variable to reset - (const struct argpar_item * type). + (const argpar_item_t * type). */ -#define ARGPAR_ITEM_DESTROY_AND_RESET(_item) \ - { \ - argpar_item_destroy(_item); \ - _item = NULL; \ - } +#define ARGPAR_ITEM_DESTROY_AND_RESET(_item) \ + { \ + argpar_item_destroy(_item); \ + (_item) = NULL; \ + } /// @} @@ -343,18 +319,19 @@ void argpar_item_destroy(const struct argpar_item *item); /*! @brief Parsing error type, as returned by - \link argpar_error_type(const struct argpar_error *) argpar_error_type()\endlink. + \link argpar_error_type(const argpar_error_t *) argpar_error_type()\endlink. */ -enum argpar_error_type { - /// Unknown option error - ARGPAR_ERROR_TYPE_UNKNOWN_OPT, +typedef enum argpar_error_type +{ + /// Unknown option error + ARGPAR_ERROR_TYPE_UNKNOWN_OPT, - /// Missing option argument error - ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, + /// Missing option argument error + ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, - /// Unexpected option argument error - ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG, -}; + /// Unexpected option argument error + ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG, +} argpar_error_type_t; /*! @struct argpar_error @@ -362,7 +339,7 @@ enum argpar_error_type { @brief Opaque parsing error type */ -struct argpar_error; +typedef struct argpar_error argpar_error_t; /*! @brief @@ -377,10 +354,7 @@ struct argpar_error; @pre \p error is not \c NULL. */ -/// @cond hidden_macro -ARGPAR_HIDDEN -/// @endcond -enum argpar_error_type argpar_error_type(const struct argpar_error *error); +argpar_error_type_t argpar_error_type(const argpar_error_t *error) ARGPAR_NOEXCEPT; /*! @brief @@ -397,10 +371,7 @@ enum argpar_error_type argpar_error_type(const struct argpar_error *error); @pre \p error is not \c NULL. */ -/// @cond hidden_macro -ARGPAR_HIDDEN -/// @endcond -unsigned int argpar_error_orig_index(const struct argpar_error *error); +unsigned int argpar_error_orig_index(const argpar_error_t *error) ARGPAR_NOEXCEPT; /*! @brief @@ -424,13 +395,10 @@ part (\--mireille in the last example). \p error is not \c NULL. @pre The type of \p error, as returned by - \link argpar_error_type(const struct argpar_error *) argpar_error_type()\endlink, + \link argpar_error_type(const argpar_error_t *) argpar_error_type()\endlink, is #ARGPAR_ERROR_TYPE_UNKNOWN_OPT. */ -/// @cond hidden_macro -ARGPAR_HIDDEN -/// @endcond -const char *argpar_error_unknown_opt_name(const struct argpar_error *error); +const char *argpar_error_unknown_opt_name(const argpar_error_t *error) ARGPAR_NOEXCEPT; /*! @brief @@ -457,15 +425,12 @@ const char *argpar_error_unknown_opt_name(const struct argpar_error *error); \p error is not \c NULL. @pre The type of \p error, as returned by - \link argpar_error_type(const struct argpar_error *) argpar_error_type()\endlink, + \link argpar_error_type(const argpar_error_t *) argpar_error_type()\endlink, is #ARGPAR_ERROR_TYPE_MISSING_OPT_ARG or #ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG. */ -/// @cond hidden_macro -ARGPAR_HIDDEN -/// @endcond -const struct argpar_opt_descr *argpar_error_opt_descr( - const struct argpar_error *error, bool *is_short); +const argpar_opt_descr_t *argpar_error_opt_descr(const argpar_error_t *error, + bool *is_short) ARGPAR_NOEXCEPT; /*! @brief @@ -474,10 +439,7 @@ const struct argpar_opt_descr *argpar_error_opt_descr( @param[in] error Parsing error to destroy (may be \c NULL). */ -/// @cond hidden_macro -ARGPAR_HIDDEN -/// @endcond -void argpar_error_destroy(const struct argpar_error *error); +void argpar_error_destroy(const argpar_error_t *error) ARGPAR_NOEXCEPT; /// @} @@ -496,7 +458,7 @@ terminated with #ARGPAR_OPT_DESCR_SENTINEL, as its \p descrs parameter. The typical usage is, for example: @code -const struct argpar_opt_descr descrs[] = { +const argpar_opt_descr_t descrs[] = { { 0, 'd', NULL, false }, { 1, '\0', "squeeze", true }, { 2, 'm', "meow", true }, @@ -504,19 +466,20 @@ const struct argpar_opt_descr descrs[] = { }; @endcode */ -struct argpar_opt_descr { - /// Numeric ID, to uniquely identify this descriptor - const int id; +typedef struct argpar_opt_descr +{ + /// Numeric ID, to uniquely identify this descriptor + const int id; - /// Short option character, or '\0' - const char short_name; + /// Short option character, or '\0' + const char short_name; - /// Long option name (without the \-- prefix), or \c NULL - const char * const long_name; + /// Long option name (without the \-- prefix), or \c NULL + const char * const long_name; - /// \c true if this option has an argument - const bool with_arg; -}; + /// \c true if this option has an argument + const bool with_arg; +} argpar_opt_descr_t; /*! @brief @@ -525,7 +488,7 @@ struct argpar_opt_descr { The typical usage is, for example: @code -const struct argpar_opt_descr descrs[] = { +const argpar_opt_descr_t descrs[] = { { 0, 'd', NULL, false }, { 1, '\0', "squeeze", true }, { 2, 'm', "meow", true }, @@ -533,7 +496,10 @@ const struct argpar_opt_descr descrs[] = { }; @endcode */ -#define ARGPAR_OPT_DESCR_SENTINEL { -1, '\0', NULL, false } +#define ARGPAR_OPT_DESCR_SENTINEL \ + { \ + -1, '\0', NULL, false \ + } /*! @struct argpar_iter @@ -543,7 +509,7 @@ const struct argpar_opt_descr descrs[] = { argpar_iter_create() returns a pointer to such a type. */ -struct argpar_iter; +typedef struct argpar_iter argpar_iter_t; /*! @brief @@ -599,12 +565,8 @@ argpar_iter_next(). @sa argpar_iter_destroy() -- Destroys an argument parsing iterator. */ -/// @cond hidden_macro -ARGPAR_HIDDEN -/// @endcond -struct argpar_iter *argpar_iter_create(unsigned int argc, - const char * const *argv, - const struct argpar_opt_descr *descrs); +argpar_iter_t *argpar_iter_create(unsigned int argc, const char * const *argv, + const argpar_opt_descr_t *descrs) ARGPAR_NOEXCEPT; /*! @brief @@ -616,10 +578,7 @@ struct argpar_iter *argpar_iter_create(unsigned int argc, @sa argpar_iter_create() -- Creates an argument parsing iterator. */ -/// @cond hidden_macro -ARGPAR_HIDDEN -/// @endcond -void argpar_iter_destroy(struct argpar_iter *iter); +void argpar_iter_destroy(argpar_iter_t *iter) ARGPAR_NOEXCEPT; /*! @brief @@ -627,19 +586,20 @@ void argpar_iter_destroy(struct argpar_iter *iter); Error status enumerators have a negative value. */ -enum argpar_iter_next_status { - /// Success - ARGPAR_ITER_NEXT_STATUS_OK, +typedef enum argpar_iter_next_status +{ + /// Success + ARGPAR_ITER_NEXT_STATUS_OK, - /// End of iteration (no more original arguments to parse) - ARGPAR_ITER_NEXT_STATUS_END, + /// End of iteration (no more original arguments to parse) + ARGPAR_ITER_NEXT_STATUS_END, - /// Parsing error - ARGPAR_ITER_NEXT_STATUS_ERROR = -1, + /// Parsing error + ARGPAR_ITER_NEXT_STATUS_ERROR = -1, - /// Memory error - ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY = -12, -}; + /// Memory error + ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY = -12, +} argpar_iter_next_status_t; /*! @brief @@ -674,12 +634,8 @@ If there are no more original arguments to parse, this function returns @pre \p item is not \c NULL. */ -/// @cond hidden_macro -ARGPAR_HIDDEN -/// @endcond -enum argpar_iter_next_status argpar_iter_next( - struct argpar_iter *iter, const struct argpar_item **item, - const struct argpar_error **error); +argpar_iter_next_status_t argpar_iter_next(argpar_iter_t *iter, const argpar_item_t **item, + const argpar_error_t **error) ARGPAR_NOEXCEPT; /* * Returns the number of ingested elements from `argv`, as passed to @@ -703,13 +659,14 @@ enum argpar_iter_next_status argpar_iter_next( @pre \p iter is not \c NULL. */ -/// @cond hidden_macro -ARGPAR_HIDDEN -/// @endcond -unsigned int argpar_iter_ingested_orig_args(const struct argpar_iter *iter); +unsigned int argpar_iter_ingested_orig_args(const argpar_iter_t *iter) ARGPAR_NOEXCEPT; /// @} /// @} +#if defined(__cplusplus) +} +#endif + #endif /* ARGPAR_ARGPAR_H */ diff --git a/bootstrap b/bootstrap index 87a491c..0c40bdd 100755 --- a/bootstrap +++ b/bootstrap @@ -1,4 +1,7 @@ #!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only +# SPDX-FileCopyrightText: EfficiOS Inc. + set -x autoreconf -vi diff --git a/configure.ac b/configure.ac index d8b29f0..8a94fee 100644 --- a/configure.ac +++ b/configure.ac @@ -1,11 +1,14 @@ +dnl SPDX-License-Identifier: GPL-2.0-only +dnl SPDX-FileCopyrightText: EfficiOS Inc. + AC_INIT([argpar], [0.1.0]) +AC_USE_SYSTEM_EXTENSIONS + AM_INIT_AUTOMAKE([foreign]) LT_INIT AC_CONFIG_MACRO_DIRS([m4]) -AC_USE_SYSTEM_EXTENSIONS - # Depend on glib just for the tests. PKG_CHECK_MODULES([GLIB], [glib-2.0]) diff --git a/tests/Makefile.am b/tests/Makefile.am index fa84fc1..b7bc46d 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +# SPDX-FileCopyrightText: EfficiOS Inc. + SUBDIRS = tap AM_CPPFLAGS = \ @@ -5,11 +8,11 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/tests/tap \ $(GLIB_CFLAGS) -noinst_PROGRAMS = test_argpar -test_argpar_SOURCES = test_argpar.c +noinst_PROGRAMS = test-argpar +test_argpar_SOURCES = test-argpar.c test_argpar_LDADD = \ $(top_builddir)/tests/tap/libtap.la \ $(top_builddir)/argpar/libargpar.la \ $(GLIB_LIBS) -TESTS = test_argpar +TESTS = test-argpar diff --git a/tests/tap/Makefile.am b/tests/tap/Makefile.am index 7b1bc21..864458e 100644 --- a/tests/tap/Makefile.am +++ b/tests/tap/Makefile.am @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +# SPDX-FileCopyrightText: EfficiOS Inc. + noinst_LTLIBRARIES = libtap.la libtap_la_SOURCES = tap.c tap.h diff --git a/tests/tap/tap.c b/tests/tap/tap.c index 9f41408..5fa9482 100644 --- a/tests/tap/tap.c +++ b/tests/tap/tap.c @@ -44,12 +44,12 @@ static void _cleanup(void); #ifdef __MINGW32__ static inline -void flockfile (FILE * filehandle) { +void flockfile (FILE * filehandle __attribute__((unused))) { return; } static inline -void funlockfile(FILE * filehandle) { +void funlockfile(FILE * filehandle __attribute__((unused))) { return; } #endif diff --git a/tests/tap/tap.h b/tests/tap/tap.h index b3e9445..f07c1db 100644 --- a/tests/tap/tap.h +++ b/tests/tap/tap.h @@ -5,8 +5,12 @@ * Copyright (C) 2017 Jérémie Galarneau */ +#ifdef __cplusplus +extern "C" { +#endif + /* '## __VA_ARGS__' is a gcc'ism. C99 doesn't allow the token pasting - and requires the caller to add the final comma if they've ommitted + and requires the caller to add the final comma if they've omitted the optional arguments */ #ifdef __GNUC__ # define ok(e, test, ...) ((e) ? \ @@ -80,3 +84,7 @@ void todo_start(const char *, ...); void todo_end(void); int exit_status(void); + +#ifdef __cplusplus +} +#endif diff --git a/tests/test-argpar.c b/tests/test-argpar.c new file mode 100644 index 0000000..3c4d6bb --- /dev/null +++ b/tests/test-argpar.c @@ -0,0 +1,541 @@ +/* + * SPDX-License-Identifier: GPL-2.0-only + * SPDX-FileCopyrightText: 2019-2024 Philippe Proulx + * SPDX-FileCopyrightText: 2020-2024 Simon Marchi + */ + +#include +#include +#include +#include +#include +#include + +#include "argpar/argpar.h" +#include "tap/tap.h" + +/* + * Formats `item` and appends the resulting string to `res_str` to + * incrementally build an expected command line string. + * + * This function: + * + * ‣ Prefers the `--long-opt=arg` style over the `-s arg` style. + * + * ‣ Uses the `arg` form for non-option arguments, where `A` is the + * original argument index and `B` is the non-option argument index. + */ +static void append_to_res_str(GString * const res_str, const argpar_item_t * const item) +{ + if (res_str->len > 0) { + g_string_append_c(res_str, ' '); + } + + switch (argpar_item_type(item)) { + case ARGPAR_ITEM_TYPE_OPT: + { + const argpar_opt_descr_t * const descr = argpar_item_opt_descr(item); + const char * const arg = argpar_item_opt_arg(item); + + if (descr->long_name) { + g_string_append_printf(res_str, "--%s", descr->long_name); + + if (arg) { + g_string_append_printf(res_str, "=%s", arg); + } + } else if (descr->short_name) { + g_string_append_printf(res_str, "-%c", descr->short_name); + + if (arg) { + g_string_append_printf(res_str, " %s", arg); + } + } + + break; + } + case ARGPAR_ITEM_TYPE_NON_OPT: + { + const char * const arg = argpar_item_non_opt_arg(item); + const unsigned int orig_index = argpar_item_non_opt_orig_index(item); + const unsigned int non_opt_index = argpar_item_non_opt_non_opt_index(item); + + g_string_append_printf(res_str, "%s<%u,%u>", arg, orig_index, non_opt_index); + break; + } + default: + abort(); + } +} + +/* + * Parses `cmdline` with the argpar API using the option descriptors + * `descrs`, and ensures that the resulting effective command line is + * `expected_cmd_line` and that the number of ingested original + * arguments is `expected_ingested_orig_args`. + * + * This function splits `cmdline` on spaces to create an original + * argument array. + * + * This function builds the resulting command line from parsing items + * by space-separating each formatted item (see append_to_res_str()). + */ +static void test_succeed(const char * const cmdline, const char * const expected_cmd_line, + const argpar_opt_descr_t * const descrs, + const unsigned int expected_ingested_orig_args) +{ + argpar_iter_t *iter = NULL; + const argpar_item_t *item = NULL; + const argpar_error_t *error = NULL; + GString * const res_str = g_string_new(NULL); + gchar ** const argv = g_strsplit(cmdline, " ", 0); + unsigned int i, actual_ingested_orig_args; + + assert(argv); + assert(res_str); + iter = argpar_iter_create(g_strv_length(argv), (const char * const *) argv, descrs); + assert(iter); + + for (i = 0;; i++) { + argpar_iter_next_status_t status; + + ARGPAR_ITEM_DESTROY_AND_RESET(item); + status = argpar_iter_next(iter, &item, &error); + + ok(status == ARGPAR_ITER_NEXT_STATUS_OK || status == ARGPAR_ITER_NEXT_STATUS_END, + "argpar_iter_next() returns the expected status (%d) for command line `%s` (call %u)", + status, cmdline, i + 1); + ok(!error, "argpar_iter_next() doesn't set an error for command line `%s` (call %u)", + cmdline, i + 1); + + if (status == ARGPAR_ITER_NEXT_STATUS_END) { + ok(!item, + "argpar_iter_next() doesn't set an item for status `ARGPAR_ITER_NEXT_STATUS_END` " + "and command line `%s` (call %u)", + cmdline, i + 1); + break; + } + + append_to_res_str(res_str, item); + } + + actual_ingested_orig_args = argpar_iter_ingested_orig_args(iter); + ok(actual_ingested_orig_args == expected_ingested_orig_args, + "argpar_iter_ingested_orig_args() returns the expected number of ingested original " + "arguments for command line `%s`", + cmdline); + + if (actual_ingested_orig_args != expected_ingested_orig_args) { + diag("Expected: %u Got: %u", expected_ingested_orig_args, actual_ingested_orig_args); + } + + ok(strcmp(expected_cmd_line, res_str->str) == 0, + "argpar_iter_next() returns the expected parsing items for command line `%s`", cmdline); + + if (strcmp(expected_cmd_line, res_str->str) != 0) { + diag("Expected: `%s`", expected_cmd_line); + diag("Got: `%s`", res_str->str); + } + + argpar_item_destroy(item); + argpar_iter_destroy(iter); + assert(!error); + g_string_free(res_str, TRUE); + g_strfreev(argv); +} + +static void succeed_tests(void) +{ + /* No arguments */ + { + const argpar_opt_descr_t descrs[] = {ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("", "", descrs, 0); + } + + /* Single long option */ + { + const argpar_opt_descr_t descrs[] = {{0, '\0', "salut", false}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("--salut", "--salut", descrs, 1); + } + + /* Single short option */ + { + const argpar_opt_descr_t descrs[] = {{0, 'f', NULL, false}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("-f", "-f", descrs, 1); + } + + /* Short and long option (aliases) */ + { + const argpar_opt_descr_t descrs[] = {{0, 'f', "flaw", false}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("-f --flaw", "--flaw --flaw", descrs, 2); + } + + /* Long option with argument (space form) */ + { + const argpar_opt_descr_t descrs[] = {{0, '\0', "tooth", true}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("--tooth 67", "--tooth=67", descrs, 2); + } + + /* Long option with argument (equal form) */ + { + const argpar_opt_descr_t descrs[] = {{0, '\0', "polish", true}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("--polish=brick", "--polish=brick", descrs, 1); + } + + /* Short option with argument (space form) */ + { + const argpar_opt_descr_t descrs[] = {{0, 'c', NULL, true}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("-c chilly", "-c chilly", descrs, 2); + } + + /* Short option with argument (glued form) */ + { + const argpar_opt_descr_t descrs[] = {{0, 'c', NULL, true}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("-cchilly", "-c chilly", descrs, 1); + } + + /* Short and long option (aliases) with argument (all forms) */ + { + const argpar_opt_descr_t descrs[] = {{0, 'd', "dry", true}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("--dry=rate -dthing --dry street --dry=shape", + "--dry=rate --dry=thing --dry=street --dry=shape", descrs, 5); + } + + /* Many short options, last one with argument (glued form) */ + { + const argpar_opt_descr_t descrs[] = {{0, 'd', NULL, false}, + {0, 'e', NULL, false}, + {0, 'f', NULL, true}, + ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("-defmeow", "-d -e -f meow", descrs, 1); + } + + /* Many options */ + { + const argpar_opt_descr_t descrs[] = {{0, 'd', NULL, false}, + {0, 'e', "east", true}, + {0, '\0', "mind", false}, + ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("-d --mind -destart --mind --east cough -d --east=itch", + "-d --mind -d --east=start --mind --east=cough -d --east=itch", descrs, 8); + } + + /* Single non-option argument */ + { + const argpar_opt_descr_t descrs[] = {ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("kilojoule", "kilojoule<0,0>", descrs, 1); + } + + /* Two non-option arguments */ + { + const argpar_opt_descr_t descrs[] = {ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("kilojoule mitaine", "kilojoule<0,0> mitaine<1,1>", descrs, 2); + } + + /* Single non-option argument mixed with options */ + { + const argpar_opt_descr_t descrs[] = {{0, 'd', NULL, false}, + {0, '\0', "squeeze", true}, + ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("-d sprout yes --squeeze little bag -d", + "-d sprout<1,0> yes<2,1> --squeeze=little bag<5,2> -d", descrs, 7); + } + + /* Valid `---opt` */ + { + const argpar_opt_descr_t descrs[] = {{0, '\0', "-fuel", true}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("---fuel=three", "---fuel=three", descrs, 1); + } + + /* Long option containing `=` in argument (equal form) */ + { + const argpar_opt_descr_t descrs[] = {{0, '\0', "zebra", true}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("--zebra=three=yes", "--zebra=three=yes", descrs, 1); + } + + /* Short option's argument starting with `-` (glued form) */ + { + const argpar_opt_descr_t descrs[] = {{0, 'z', NULL, true}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("-z-will", "-z -will", descrs, 1); + } + + /* Short option's argument starting with `-` (space form) */ + { + const argpar_opt_descr_t descrs[] = {{0, 'z', NULL, true}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("-z -will", "-z -will", descrs, 2); + } + + /* Long option's argument starting with `-` (space form) */ + { + const argpar_opt_descr_t descrs[] = {{0, '\0', "janine", true}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("--janine -sutto", "--janine=-sutto", descrs, 2); + } + + /* Long option's argument starting with `-` (equal form) */ + { + const argpar_opt_descr_t descrs[] = {{0, '\0', "janine", true}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("--janine=-sutto", "--janine=-sutto", descrs, 1); + } + + /* Long option's empty argument (equal form) */ + { + const argpar_opt_descr_t descrs[] = {{0, 'f', NULL, false}, + {0, '\0', "yeah", true}, + ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("-f --yeah= -f", "-f --yeah= -f", descrs, 3); + } + + /* `-` non-option argument */ + { + const argpar_opt_descr_t descrs[] = {{0, 'f', NULL, false}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("-f - -f", "-f -<1,0> -f", descrs, 3); + } + + /* `--` non-option argument */ + { + const argpar_opt_descr_t descrs[] = {{0, 'f', NULL, false}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_succeed("-f -- -f", "-f --<1,0> -f", descrs, 3); + } + + /* Very long name of long option */ + { + const char opt_name[] = "kale-chips-waistcoat-yr-bicycle-rights-gochujang-" + "woke-tumeric-flexitarian-biodiesel-chillwave-cliche-" + "ethical-cardigan-listicle-pok-pok-sustainable-live-" + "edge-jianbing-gochujang-butcher-disrupt-tattooed-" + "tumeric-prism-photo-booth-vape-kogi-jean-shorts-" + "blog-williamsburg-fingerstache-palo-santo-artisan-" + "affogato-occupy-skateboard-adaptogen-neutra-celiac-" + "put-a-bird-on-it-kombucha-everyday-carry-hot-chicken-" + "craft-beer-subway-tile-tote-bag-disrupt-selvage-" + "raclette-art-party-readymade-paleo-heirloom-trust-" + "fund-small-batch-kinfolk-woke-cardigan-prism-" + "chambray-la-croix-hashtag-unicorn-edison-bulb-tbh-" + "cornhole-cliche-tattooed-green-juice-adaptogen-" + "kitsch-lo-fi-vexillologist-migas-gentrify-" + "viral-raw-denim"; + const argpar_opt_descr_t descrs[] = {{0, '\0', opt_name, true}, ARGPAR_OPT_DESCR_SENTINEL}; + char cmdline[1024]; + + sprintf(cmdline, "--%s=23", opt_name); + test_succeed(cmdline, cmdline, descrs, 1); + } +} + +/* + * Parses `cmdline` with the argpar API using the option descriptors + * `descrs`, and ensures that argpar_iter_next() fails with status + * `expected_status` and that it sets an error having: + * + * ‣ The original argument index `expected_orig_index`. + * + * ‣ If applicable: + * + * • The unknown option name `expected_unknown_opt_name`. + * + * • The option descriptor at index `expected_opt_descr_index` of + * `descrs`. + * + * • The option type `expected_is_short`. + * + * This function splits `cmdline` on spaces to create an original + * argument array. + */ +static void test_fail(const char * const cmdline, const argpar_error_type_t expected_error_type, + const unsigned int expected_orig_index, + const char * const expected_unknown_opt_name, + const unsigned int expected_opt_descr_index, const bool expected_is_short, + const argpar_opt_descr_t * const descrs) +{ + argpar_iter_t *iter = NULL; + const argpar_item_t *item = NULL; + gchar ** const argv = g_strsplit(cmdline, " ", 0); + unsigned int i; + const argpar_error_t *error = NULL; + + iter = argpar_iter_create(g_strv_length(argv), (const char * const *) argv, descrs); + assert(iter); + + for (i = 0;; i++) { + argpar_iter_next_status_t status; + + ARGPAR_ITEM_DESTROY_AND_RESET(item); + status = argpar_iter_next(iter, &item, &error); + ok(status == ARGPAR_ITER_NEXT_STATUS_OK || + (status == ARGPAR_ITER_NEXT_STATUS_ERROR && + argpar_error_type(error) == expected_error_type), + "argpar_iter_next() returns the expected status and error type (%d) " + "for command line `%s` (call %u)", + expected_error_type, cmdline, i + 1); + + if (status != ARGPAR_ITER_NEXT_STATUS_OK) { + ok(!item, + "argpar_iter_next() doesn't set an item for other status than " + "`ARGPAR_ITER_NEXT_STATUS_OK` and command line `%s` (call %u)", + cmdline, i + 1); + ok(error, + "argpar_iter_next() sets an error for other status than " + "`ARGPAR_ITER_NEXT_STATUS_OK` and command line `%s` (call %u)", + cmdline, i + 1); + ok(argpar_error_orig_index(error) == expected_orig_index, + "argpar_iter_next() sets an error with the expected original argument index " + "for command line `%s` (call %u)", + cmdline, i + 1); + + if (argpar_error_type(error) == ARGPAR_ERROR_TYPE_UNKNOWN_OPT) { + ok(strcmp(argpar_error_unknown_opt_name(error), expected_unknown_opt_name) == 0, + "argpar_iter_next() sets an error with the expected unknown option name " + "for command line `%s` (call %u)", + cmdline, i + 1); + } else { + bool is_short; + + ok(argpar_error_opt_descr(error, &is_short) == &descrs[expected_opt_descr_index], + "argpar_iter_next() sets an error with the expected option descriptor " + "for command line `%s` (call %u)", + cmdline, i + 1); + ok(is_short == expected_is_short, + "argpar_iter_next() sets an error with the expected option type " + "for command line `%s` (call %u)", + cmdline, i + 1); + } + break; + } + + ok(item, + "argpar_iter_next() sets an item for status `ARGPAR_ITER_NEXT_STATUS_OK` " + "and command line `%s` (call %u)", + cmdline, i + 1); + ok(!error, + "argpar_iter_next() doesn't set an error for status `ARGPAR_ITER_NEXT_STATUS_OK` " + "and command line `%s` (call %u)", + cmdline, i + 1); + } + + /* + ok(strcmp(expected_error, error) == 0, + "argpar_iter_next() sets the expected error string " + "for command line `%s`", cmdline); + + if (strcmp(expected_error, error) != 0) { + diag("Expected: `%s`", expected_error); + diag("Got: `%s`", error); + } + */ + + argpar_item_destroy(item); + argpar_iter_destroy(iter); + argpar_error_destroy(error); + g_strfreev(argv); +} + +static void fail_tests(void) +{ + /* Unknown short option (space form) */ + { + const argpar_opt_descr_t descrs[] = {{0, 'd', NULL, true}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_fail("-d salut -e -d meow", ARGPAR_ERROR_TYPE_UNKNOWN_OPT, 2, "-e", 0, false, descrs); + } + + /* Unknown short option (glued form) */ + { + const argpar_opt_descr_t descrs[] = {{0, 'd', 0, true}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_fail("-dsalut -e -d meow", ARGPAR_ERROR_TYPE_UNKNOWN_OPT, 1, "-e", 0, false, descrs); + } + + /* Unknown long option (space form) */ + { + const argpar_opt_descr_t descrs[] = {{0, '\0', "sink", true}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_fail("--sink party --food --sink impulse", ARGPAR_ERROR_TYPE_UNKNOWN_OPT, 2, "--food", + 0, false, descrs); + } + + /* Unknown long option (equal form) */ + { + const argpar_opt_descr_t descrs[] = {{0, '\0', "sink", true}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_fail("--sink=party --food --sink=impulse", ARGPAR_ERROR_TYPE_UNKNOWN_OPT, 1, "--food", + 0, false, descrs); + } + + /* Unknown option before non-option argument */ + { + const argpar_opt_descr_t descrs[] = {{0, '\0', "thumb", true}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_fail("--thumb=party --food=18 bateau --thumb waves", ARGPAR_ERROR_TYPE_UNKNOWN_OPT, 1, + "--food", 0, false, descrs); + } + + /* Unknown option after non-option argument */ + { + const argpar_opt_descr_t descrs[] = {{0, '\0', "thumb", true}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_fail("--thumb=party wound --food --thumb waves", ARGPAR_ERROR_TYPE_UNKNOWN_OPT, 2, + "--food", 0, false, descrs); + } + + /* Missing long option argument */ + { + const argpar_opt_descr_t descrs[] = {{0, '\0', "thumb", true}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_fail("allo --thumb", ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, 1, NULL, 0, false, descrs); + } + + /* Missing short option argument */ + { + const argpar_opt_descr_t descrs[] = {{0, 'k', NULL, true}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_fail("zoom heille -k", ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, 2, NULL, 0, true, descrs); + } + + /* Missing short option argument (multiple glued) */ + { + const argpar_opt_descr_t descrs[] = {{0, 'a', NULL, false}, + {0, 'b', NULL, false}, + {0, 'c', NULL, true}, + ARGPAR_OPT_DESCR_SENTINEL}; + + test_fail("-abc", ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, 0, NULL, 2, true, descrs); + } + + /* Unexpected long option argument */ + { + const argpar_opt_descr_t descrs[] = {{0, 'c', "chevre", false}, ARGPAR_OPT_DESCR_SENTINEL}; + + test_fail("ambulance --chevre=fromage tar -cjv", ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG, 1, + NULL, 0, false, descrs); + } +} + +int main(void) +{ + plan_tests(309); + succeed_tests(); + fail_tests(); + return exit_status(); +} diff --git a/tests/test_argpar.c b/tests/test_argpar.c deleted file mode 100644 index 87646a9..0000000 --- a/tests/test_argpar.c +++ /dev/null @@ -1,787 +0,0 @@ -/* - * Copyright (c) 2019-2021 Philippe Proulx - * Copyright (c) 2020-2021 Simon Marchi - * - * 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; under version 2 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include -#include -#include -#include -#include - -#include "tap/tap.h" -#include "argpar/argpar.h" - -/* - * Formats `item` and appends the resulting string to `res_str` to - * incrementally build an expected command line string. - * - * This function: - * - * * Prefers the `--long-opt=arg` style over the `-s arg` style. - * - * * Uses the `arg` form for non-option arguments, where `A` is the - * original argument index and `B` is the non-option argument index. - */ -static -void append_to_res_str(GString * const res_str, - const struct argpar_item * const item) -{ - if (res_str->len > 0) { - g_string_append_c(res_str, ' '); - } - - switch (argpar_item_type(item)) { - case ARGPAR_ITEM_TYPE_OPT: - { - const struct argpar_opt_descr * const descr = - argpar_item_opt_descr(item); - const char * const arg = argpar_item_opt_arg(item); - - if (descr->long_name) { - g_string_append_printf(res_str, "--%s", - descr->long_name); - - if (arg) { - g_string_append_printf(res_str, "=%s", arg); - } - } else if (descr->short_name) { - g_string_append_printf(res_str, "-%c", - descr->short_name); - - if (arg) { - g_string_append_printf(res_str, " %s", arg); - } - } - - break; - } - case ARGPAR_ITEM_TYPE_NON_OPT: - { - const char * const arg = argpar_item_non_opt_arg(item); - const unsigned int orig_index = - argpar_item_non_opt_orig_index(item); - const unsigned int non_opt_index = - argpar_item_non_opt_non_opt_index(item); - - g_string_append_printf(res_str, "%s<%u,%u>", arg, orig_index, - non_opt_index); - break; - } - default: - abort(); - } -} - -/* - * Parses `cmdline` with the argpar API using the option descriptors - * `descrs`, and ensures that the resulting effective command line is - * `expected_cmd_line` and that the number of ingested original - * arguments is `expected_ingested_orig_args`. - * - * This function splits `cmdline` on spaces to create an original - * argument array. - * - * This function builds the resulting command line from parsing items - * by space-separating each formatted item (see append_to_res_str()). - */ -static -void test_succeed(const char * const cmdline, - const char * const expected_cmd_line, - const struct argpar_opt_descr * const descrs, - const unsigned int expected_ingested_orig_args) -{ - struct argpar_iter *iter = NULL; - const struct argpar_item *item = NULL; - const struct argpar_error *error = NULL; - GString * const res_str = g_string_new(NULL); - gchar ** const argv = g_strsplit(cmdline, " ", 0); - unsigned int i, actual_ingested_orig_args; - - assert(argv); - assert(res_str); - iter = argpar_iter_create(g_strv_length(argv), - (const char * const *) argv, descrs); - assert(iter); - - for (i = 0; ; i++) { - enum argpar_iter_next_status status; - - ARGPAR_ITEM_DESTROY_AND_RESET(item); - status = argpar_iter_next(iter, &item, &error); - - ok(status == ARGPAR_ITER_NEXT_STATUS_OK || - status == ARGPAR_ITER_NEXT_STATUS_END, - "argpar_iter_next() returns the expected status " - "(%d) for command line `%s` (call %u)", - status, cmdline, i + 1); - ok(!error, - "argpar_iter_next() doesn't set an error for " - "command line `%s` (call %u)", - cmdline, i + 1); - - if (status == ARGPAR_ITER_NEXT_STATUS_END) { - ok(!item, - "argpar_iter_next() doesn't set an item " - "for status `ARGPAR_ITER_NEXT_STATUS_END` " - "and command line `%s` (call %u)", - cmdline, i + 1); - break; - } - - append_to_res_str(res_str, item); - } - - actual_ingested_orig_args = argpar_iter_ingested_orig_args(iter); - ok(actual_ingested_orig_args == expected_ingested_orig_args, - "argpar_iter_ingested_orig_args() returns the expected " - "number of ingested original arguments for command line `%s`", - cmdline); - - if (actual_ingested_orig_args != expected_ingested_orig_args) { - diag("Expected: %u Got: %u", expected_ingested_orig_args, - actual_ingested_orig_args); - } - - ok(strcmp(expected_cmd_line, res_str->str) == 0, - "argpar_iter_next() returns the expected parsing items " - "for command line `%s`", cmdline); - - if (strcmp(expected_cmd_line, res_str->str) != 0) { - diag("Expected: `%s`", expected_cmd_line); - diag("Got: `%s`", res_str->str); - } - - argpar_item_destroy(item); - argpar_iter_destroy(iter); - assert(!error); - g_string_free(res_str, TRUE); - g_strfreev(argv); -} - -static -void succeed_tests(void) -{ - /* No arguments */ - { - const struct argpar_opt_descr descrs[] = { - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "", - "", - descrs, 0); - } - - /* Single long option */ - { - const struct argpar_opt_descr descrs[] = { - { 0, '\0', "salut", false }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "--salut", - "--salut", - descrs, 1); - } - - /* Single short option */ - { - const struct argpar_opt_descr descrs[] = { - { 0, 'f', NULL, false }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "-f", - "-f", - descrs, 1); - } - - /* Short and long option (aliases) */ - { - const struct argpar_opt_descr descrs[] = { - { 0, 'f', "flaw", false }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "-f --flaw", - "--flaw --flaw", - descrs, 2); - } - - /* Long option with argument (space form) */ - { - const struct argpar_opt_descr descrs[] = { - { 0, '\0', "tooth", true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "--tooth 67", - "--tooth=67", - descrs, 2); - } - - /* Long option with argument (equal form) */ - { - const struct argpar_opt_descr descrs[] = { - { 0, '\0', "polish", true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "--polish=brick", - "--polish=brick", - descrs, 1); - } - - /* Short option with argument (space form) */ - { - const struct argpar_opt_descr descrs[] = { - { 0, 'c', NULL, true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "-c chilly", - "-c chilly", - descrs, 2); - } - - /* Short option with argument (glued form) */ - { - const struct argpar_opt_descr descrs[] = { - { 0, 'c', NULL, true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "-cchilly", - "-c chilly", - descrs, 1); - } - - /* Short and long option (aliases) with argument (all forms) */ - { - const struct argpar_opt_descr descrs[] = { - { 0, 'd', "dry", true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "--dry=rate -dthing --dry street --dry=shape", - "--dry=rate --dry=thing --dry=street --dry=shape", - descrs, 5); - } - - /* Many short options, last one with argument (glued form) */ - { - const struct argpar_opt_descr descrs[] = { - { 0, 'd', NULL, false }, - { 0, 'e', NULL, false }, - { 0, 'f', NULL, true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "-defmeow", - "-d -e -f meow", - descrs, 1); - } - - /* Many options */ - { - const struct argpar_opt_descr descrs[] = { - { 0, 'd', NULL, false }, - { 0, 'e', "east", true }, - { 0, '\0', "mind", false }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "-d --mind -destart --mind --east cough -d --east=itch", - "-d --mind -d --east=start --mind --east=cough -d --east=itch", - descrs, 8); - } - - /* Single non-option argument */ - { - const struct argpar_opt_descr descrs[] = { - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "kilojoule", - "kilojoule<0,0>", - descrs, 1); - } - - /* Two non-option arguments */ - { - const struct argpar_opt_descr descrs[] = { - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "kilojoule mitaine", - "kilojoule<0,0> mitaine<1,1>", - descrs, 2); - } - - /* Single non-option argument mixed with options */ - { - const struct argpar_opt_descr descrs[] = { - { 0, 'd', NULL, false }, - { 0, '\0', "squeeze", true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "-d sprout yes --squeeze little bag -d", - "-d sprout<1,0> yes<2,1> --squeeze=little bag<5,2> -d", - descrs, 7); - } - - /* Valid `---opt` */ - { - const struct argpar_opt_descr descrs[] = { - { 0, '\0', "-fuel", true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "---fuel=three", - "---fuel=three", - descrs, 1); - } - - /* Long option containing `=` in argument (equal form) */ - { - const struct argpar_opt_descr descrs[] = { - { 0, '\0', "zebra", true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "--zebra=three=yes", - "--zebra=three=yes", - descrs, 1); - } - - /* Short option's argument starting with `-` (glued form) */ - { - const struct argpar_opt_descr descrs[] = { - { 0, 'z', NULL, true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "-z-will", - "-z -will", - descrs, 1); - } - - /* Short option's argument starting with `-` (space form) */ - { - const struct argpar_opt_descr descrs[] = { - { 0, 'z', NULL, true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "-z -will", - "-z -will", - descrs, 2); - } - - /* Long option's argument starting with `-` (space form) */ - { - const struct argpar_opt_descr descrs[] = { - { 0, '\0', "janine", true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "--janine -sutto", - "--janine=-sutto", - descrs, 2); - } - - /* Long option's argument starting with `-` (equal form) */ - { - const struct argpar_opt_descr descrs[] = { - { 0, '\0', "janine", true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "--janine=-sutto", - "--janine=-sutto", - descrs, 1); - } - - /* Long option's empty argument (equal form) */ - { - const struct argpar_opt_descr descrs[] = { - { 0, 'f', NULL, false }, - { 0, '\0', "yeah", true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "-f --yeah= -f", - "-f --yeah= -f", - descrs, 3); - } - - /* `-` non-option argument */ - { - const struct argpar_opt_descr descrs[] = { - { 0, 'f', NULL, false }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "-f - -f", - "-f -<1,0> -f", - descrs, 3); - } - - /* `--` non-option argument */ - { - const struct argpar_opt_descr descrs[] = { - { 0, 'f', NULL, false }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_succeed( - "-f -- -f", - "-f --<1,0> -f", - descrs, 3); - } - - /* Very long name of long option */ - { - const char opt_name[] = - "kale-chips-waistcoat-yr-bicycle-rights-gochujang-" - "woke-tumeric-flexitarian-biodiesel-chillwave-cliche-" - "ethical-cardigan-listicle-pok-pok-sustainable-live-" - "edge-jianbing-gochujang-butcher-disrupt-tattooed-" - "tumeric-prism-photo-booth-vape-kogi-jean-shorts-" - "blog-williamsburg-fingerstache-palo-santo-artisan-" - "affogato-occupy-skateboard-adaptogen-neutra-celiac-" - "put-a-bird-on-it-kombucha-everyday-carry-hot-chicken-" - "craft-beer-subway-tile-tote-bag-disrupt-selvage-" - "raclette-art-party-readymade-paleo-heirloom-trust-" - "fund-small-batch-kinfolk-woke-cardigan-prism-" - "chambray-la-croix-hashtag-unicorn-edison-bulb-tbh-" - "cornhole-cliche-tattooed-green-juice-adaptogen-" - "kitsch-lo-fi-vexillologist-migas-gentrify-" - "viral-raw-denim"; - const struct argpar_opt_descr descrs[] = { - { 0, '\0', opt_name, true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - char cmdline[1024]; - - sprintf(cmdline, "--%s=23", opt_name); - test_succeed(cmdline, cmdline, descrs, 1); - } -} - -/* - * Parses `cmdline` with the argpar API using the option descriptors - * `descrs`, and ensures that argpar_iter_next() fails with status - * `expected_status` and that it sets an error having: - * - * * The original argument index `expected_orig_index`. - * - * * If applicable: - * - * * The unknown option name `expected_unknown_opt_name`. - * - * * The option descriptor at index `expected_opt_descr_index` of - * `descrs`. - * - * * The option type `expected_is_short`. - * - * This function splits `cmdline` on spaces to create an original - * argument array. - */ -static -void test_fail(const char * const cmdline, - const enum argpar_error_type expected_error_type, - const unsigned int expected_orig_index, - const char * const expected_unknown_opt_name, - const unsigned int expected_opt_descr_index, - const bool expected_is_short, - const struct argpar_opt_descr * const descrs) -{ - struct argpar_iter *iter = NULL; - const struct argpar_item *item = NULL; - gchar ** const argv = g_strsplit(cmdline, " ", 0); - unsigned int i; - const struct argpar_error *error = NULL; - - iter = argpar_iter_create(g_strv_length(argv), - (const char * const *) argv, descrs); - assert(iter); - - for (i = 0; ; i++) { - enum argpar_iter_next_status status; - - ARGPAR_ITEM_DESTROY_AND_RESET(item); - status = argpar_iter_next(iter, &item, &error); - ok(status == ARGPAR_ITER_NEXT_STATUS_OK || - (status == ARGPAR_ITER_NEXT_STATUS_ERROR && - argpar_error_type(error) == expected_error_type), - "argpar_iter_next() returns the expected status " - "and error type (%d) for command line `%s` (call %u)", - expected_error_type, cmdline, i + 1); - - if (status != ARGPAR_ITER_NEXT_STATUS_OK) { - ok(!item, - "argpar_iter_next() doesn't set an item " - "for other status than " - "`ARGPAR_ITER_NEXT_STATUS_OK` " - "and command line `%s` (call %u)", - cmdline, i + 1); - ok(error, - "argpar_iter_next() sets an error for " - "other status than " - " `ARGPAR_ITER_NEXT_STATUS_OK` " - "and command line `%s` (call %u)", - cmdline, i + 1); - ok(argpar_error_orig_index(error) == - expected_orig_index, - "argpar_iter_next() sets an error with " - "the expected original argument index " - "for command line `%s` (call %u)", - cmdline, i + 1); - - if (argpar_error_type(error) == ARGPAR_ERROR_TYPE_UNKNOWN_OPT) { - ok(strcmp(argpar_error_unknown_opt_name(error), - expected_unknown_opt_name) == 0, - "argpar_iter_next() sets an error with " - "the expected unknown option name " - "for command line `%s` (call %u)", - cmdline, i + 1); - } else { - bool is_short; - - ok(argpar_error_opt_descr(error, &is_short) == - &descrs[expected_opt_descr_index], - "argpar_iter_next() sets an error with " - "the expected option descriptor " - "for command line `%s` (call %u)", - cmdline, i + 1); - ok(is_short == expected_is_short, - "argpar_iter_next() sets an error with " - "the expected option type " - "for command line `%s` (call %u)", - cmdline, i + 1); - } - break; - } - - ok(item, - "argpar_iter_next() sets an item for status " - "`ARGPAR_ITER_NEXT_STATUS_OK` " - "and command line `%s` (call %u)", - cmdline, i + 1); - ok(!error, - "argpar_iter_next() doesn't set an error for status " - "`ARGPAR_ITER_NEXT_STATUS_OK` " - "and command line `%s` (call %u)", - cmdline, i + 1); - } - - /* - ok(strcmp(expected_error, error) == 0, - "argpar_iter_next() sets the expected error string " - "for command line `%s`", cmdline); - - if (strcmp(expected_error, error) != 0) { - diag("Expected: `%s`", expected_error); - diag("Got: `%s`", error); - } - */ - - argpar_item_destroy(item); - argpar_iter_destroy(iter); - argpar_error_destroy(error); - g_strfreev(argv); -} - -static -void fail_tests(void) -{ - - /* Unknown short option (space form) */ - { - const struct argpar_opt_descr descrs[] = { - { 0, 'd', NULL, true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_fail( - "-d salut -e -d meow", - ARGPAR_ERROR_TYPE_UNKNOWN_OPT, - 2, "-e", 0, false, - descrs); - } - - /* Unknown short option (glued form) */ - { - const struct argpar_opt_descr descrs[] = { - { 0, 'd', 0, true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_fail( - "-dsalut -e -d meow", - ARGPAR_ERROR_TYPE_UNKNOWN_OPT, - 1, "-e", 0, false, - descrs); - } - - /* Unknown long option (space form) */ - { - const struct argpar_opt_descr descrs[] = { - { 0, '\0', "sink", true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_fail( - "--sink party --food --sink impulse", - ARGPAR_ERROR_TYPE_UNKNOWN_OPT, - 2, "--food", 0, false, - descrs); - } - - /* Unknown long option (equal form) */ - { - const struct argpar_opt_descr descrs[] = { - { 0, '\0', "sink", true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_fail( - "--sink=party --food --sink=impulse", - ARGPAR_ERROR_TYPE_UNKNOWN_OPT, - 1, "--food", 0, false, - descrs); - } - - /* Unknown option before non-option argument */ - { - const struct argpar_opt_descr descrs[] = { - { 0, '\0', "thumb", true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_fail( - "--thumb=party --food=18 bateau --thumb waves", - ARGPAR_ERROR_TYPE_UNKNOWN_OPT, - 1, "--food", 0, false, - descrs); - } - - /* Unknown option after non-option argument */ - { - const struct argpar_opt_descr descrs[] = { - { 0, '\0', "thumb", true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_fail( - "--thumb=party wound --food --thumb waves", - ARGPAR_ERROR_TYPE_UNKNOWN_OPT, - 2, "--food", 0, false, - descrs); - } - - /* Missing long option argument */ - { - const struct argpar_opt_descr descrs[] = { - { 0, '\0', "thumb", true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_fail( - "allo --thumb", - ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, - 1, NULL, 0, false, - descrs); - } - - /* Missing short option argument */ - { - const struct argpar_opt_descr descrs[] = { - { 0, 'k', NULL, true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_fail( - "zoom heille -k", - ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, - 2, NULL, 0, true, - descrs); - } - - /* Missing short option argument (multiple glued) */ - { - const struct argpar_opt_descr descrs[] = { - { 0, 'a', NULL, false }, - { 0, 'b', NULL, false }, - { 0, 'c', NULL, true }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_fail( - "-abc", - ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, - 0, NULL, 2, true, - descrs); - } - - /* Unexpected long option argument */ - { - const struct argpar_opt_descr descrs[] = { - { 0, 'c', "chevre", false }, - ARGPAR_OPT_DESCR_SENTINEL - }; - - test_fail( - "ambulance --chevre=fromage tar -cjv", - ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG, - 1, NULL, 0, false, - descrs); - } -} - -int main(void) -{ - plan_tests(309); - succeed_tests(); - fail_tests(); - return exit_status(); -}