From 9af3bb53fb77dbe121c0711bdf6616608b1f55d9 Mon Sep 17 00:00:00 2001 From: Constantin P <papizh.konstantin@demlabs.net> Date: Mon, 21 Mar 2022 17:48:35 +0000 Subject: [PATCH] Features 5769 --- 3rdparty/libconfini/AUTHORS | 1 + 3rdparty/libconfini/COPYING | 675 ++ 3rdparty/libconfini/README | 211 + 3rdparty/libconfini/README.md | 1 + 3rdparty/libconfini/confini.c | 5763 +++++++++++++++++ 3rdparty/libconfini/confini.h | 589 ++ cmake/OS_Detection.cmake | 1 + dap-sdk/core/CMakeLists.txt | 10 +- .../notify_server/include/dap_notify_srv.h | 1 + .../server/notify_server/src/dap_notify_srv.c | 13 + modules/chain/dap_chain_ledger.c | 40 + modules/net/dap_chain_net.c | 211 +- 12 files changed, 7388 insertions(+), 128 deletions(-) create mode 100644 3rdparty/libconfini/AUTHORS create mode 100644 3rdparty/libconfini/COPYING create mode 100644 3rdparty/libconfini/README create mode 100644 3rdparty/libconfini/README.md create mode 100644 3rdparty/libconfini/confini.c create mode 100644 3rdparty/libconfini/confini.h diff --git a/3rdparty/libconfini/AUTHORS b/3rdparty/libconfini/AUTHORS new file mode 100644 index 0000000000..513363c831 --- /dev/null +++ b/3rdparty/libconfini/AUTHORS @@ -0,0 +1 @@ +madmurphy <madmurphy333 AT gmail DOT com> diff --git a/3rdparty/libconfini/COPYING b/3rdparty/libconfini/COPYING new file mode 100644 index 0000000000..10926e87f1 --- /dev/null +++ b/3rdparty/libconfini/COPYING @@ -0,0 +1,675 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<http://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<http://www.gnu.org/philosophy/why-not-lgpl.html>. + diff --git a/3rdparty/libconfini/README b/3rdparty/libconfini/README new file mode 100644 index 0000000000..5320380169 --- /dev/null +++ b/3rdparty/libconfini/README @@ -0,0 +1,211 @@ +libconfini {#readme} +==================== + +**libconfini** is the ultimate and most consistent INI file parser library +written in C. Originally designed for parsing configuration files written by +other programs, it focuses on standardization and parsing exactness and is at +ease with almost every type of file containing key/value pairs. + +The library is fast and suitable for embedded systems. Its algorithms are +written from scratch and do not depend on any external library, except for the +C standard headers **stdio.h**, **stdlib.h**, **stdbool.h** and **stdint.h** +(and for extreme platforms the code [can be also compiled as “bare metalâ€][1], +with few or no strings attached to the C Standard Library). + +Rather than storing the parsed data, **libconfini** gives the developer the +freedom to choose what to do with them through a custom callback invoked for +each INI node read. The API has been designed to be powerful, flexible and +simple to use. + +With **libconfini** you will find in INI files the same serialization power +that you would normally find in other heavily structured formats (such as JSON, +YAML, TOML), but with the advantage of using the most human-friendly +configuration format ever invented (thanks to their informal status, INI files +are indeed more fluid, expressive and human-friendly than formats explicitly +designed with the same purpose, such as YAML and TOML). The library's main goal +is to be uncommonly powerful in the most tedious and error-prone task when +parsing a text file in C: string handling. Thanks to this, the programmer is +left free to organize the content parsed as (s)he pleases, assisted by a rich +set of fine-tuning tools. + + +Features +-------- + +* Typed data support (each value can be parsed as a boolean, a number, a + string, an array) +* Single/double quotes support in _Bash style_ (i.e. quotes can be opened and + closed multiple times within the same value) +* Multi-line support +* Comment support +* Disabled entry support +* INI sectioning support (single-level sectioning, as in `[foo]`; absolute + nesting, as in `[foo.bar]`; relative nesting, as in `[.bar]`) +* Automatic sanitization of values, key names and section paths +* Comparison functions designed just for INI source code (capable, for example, + to recognize the equality between <code>"Hello world"</code> and + <code>"He"l'lo' world</code>, or between <code>foo bar</code> and + <code>foo bar</code>) +* Callback pattern +* Thread-safety (each parsing process is fully reentrant) +* Highly optimized code (single memory allocation for each parsing, heuristic + programming, optimization flags) +* Function modularity (each public function is independent from all other + public functions) +* K.I.S.S. (no public functions are automatically invoked during the parsing -- + for example, not even single/double quotes are automatically removed from + values unless the developer explicitly decides to use the formatting + functions provided by the API) +* Robust and cross-platform file access (UTF-8 support; protection against null + byte injection; support of all code representations of new lines -- i.e. + ubiquitous support of Classic Mac OS' `CR`, Unix' `LF`, Windows' `CR`+`LF`, + RISC OS Open's `LF`+`CR`) + + +Sample usage +------------ + +log.ini: + +`````````````````````````````````````` ini +[users] +have_visited = ronnie, lilly82, robrob + +[last_update] +date = 12.03.2017 +`````````````````````````````````````` + +example.c: + +``````````````````````````````````````````````````````````````````````````` c +#include <confini.h> + +static int callback (IniDispatch * disp, void * v_other) { + + #define IS_KEY(SECTION, KEY) \ + (ini_array_match(SECTION, disp->append_to, '.', disp->format) && \ + ini_string_match_ii(KEY, disp->data, disp->format)) + + if (disp->type == INI_KEY) { + + if (IS_KEY("users", "have_visited")) { + + /* No need to parse this field as an array right now */ + printf("People who have visited: %s\n", disp->value); + + } else if (IS_KEY("last_update", "date")) { + + printf("Last update: %s\n", disp->value); + + } + + } + + #undef IS_KEY + + return 0; + +} + +int main () { + + if (load_ini_path("log.ini", INI_DEFAULT_FORMAT, NULL, callback, NULL)) { + + fprintf(stderr, "Sorry, something went wrong :-(\n"); + return 1; + + } + + return 0; + +} +``````````````````````````````````````````````````````````````````````````` + +Output: + +```````````````````````````````````````````````` +People who have visited: ronnie, lilly82, robrob +Last update: 12.03.2017 +```````````````````````````````````````````````` + +If you are using C++, under `examples/cplusplus/map.cpp` you can find a snippet +for storing an INI file into a class that relies on a `std::unordered_map` +object. + +For more details, please read the [Library Functions Manual][2] (`man +libconfini`) -- a standalone HTML version is available [here][3] -- and the +manual of the header file (`man confini.h`). The code is available on +[GitHub][4] under [madmurphy/libconfini][5]). + + +Installation +------------ + +Despite the small footprint, **libconfini** has been conceived as a shared +library (but it can be used as a static library as well). An automatic list of +the distributions that ship the library already compiled is available [here][6]. + +If a pre-compiled package for your platform is not available, on most Unix-like +systems it is possible to install **libconfini** using the following common +steps: + +`````````````````` sh +./configure +make +make install-strip +`````````````````` + +If the `strip` utility is not available on your machine, use `make install` +instead (it will produce larger binaries) + +For a minimum installation without development files (i.e. static libraries, +headers, documentation, examples, etc.) use `./configure --disable-devel`. + +If the `configure` script is missing you need to generate it by running the +`bootstrap` script. By default, `bootstrap` will also run the `configure` +script immediately after having generated it, so you may type the `make` +command directly after `bootstrap`. To list different options use `./bootstrap +--help`. + +If you are using **Microsoft Windows**, a batch script for compiling +**libconfini** with [**MinGW**][7] without **Bash** is available +(`mgwmake.bat`). If you are interested in using **GNU Make** for compiling +**libconfini**, you can integrate **MinGW** with [**MSYS**][8], or you can +directly use [**MSYS2**][9] and [its official port][10] of the library. +Alternatively, [an unofficial port][11] of **libconfini** for [**Cygwin**][12] +is available. + +For further information, see [INSTALL][13]. + + +Bindings +-------- + +[Danilo Spinella][14] maintains [a D binding package][15] of **libconfini**. + + +Free software +------------- + +This library is free software. You can redistribute it and/or modify it under +the terms of the GPL license version 3 or any later version. See [COPYING][16] +for details. + + + [1]: https://madmurphy.github.io/libconfini/html/baremetal.html + [2]: https://madmurphy.github.io/libconfini/html/libconfini.html + [3]: https://madmurphy.github.io/libconfini/manual.html + [4]: https://github.com/ + [5]: https://github.com/madmurphy/libconfini + [6]: https://repology.org/project/libconfini/versions + [7]: http://www.mingw.org/ + [8]: http://www.mingw.org/wiki/MSYS + [9]: https://www.msys2.org/ + [10]: https://packages.msys2.org/base/mingw-w64-libconfini + [11]: https://github.com/fd00/yacp/tree/master/libconfini + [12]: https://www.cygwin.com/ + [13]: https://madmurphy.github.io/libconfini/html/install.html + [14]: https://danyspin97.org/ + [15]: https://github.com/DanySpin97/libconfini-d + [16]: https://github.com/madmurphy/libconfini/blob/master/COPYING + diff --git a/3rdparty/libconfini/README.md b/3rdparty/libconfini/README.md new file mode 100644 index 0000000000..100b93820a --- /dev/null +++ b/3rdparty/libconfini/README.md @@ -0,0 +1 @@ +README \ No newline at end of file diff --git a/3rdparty/libconfini/confini.c b/3rdparty/libconfini/confini.c new file mode 100644 index 0000000000..57f7bbcfd8 --- /dev/null +++ b/3rdparty/libconfini/confini.c @@ -0,0 +1,5763 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ +/* Please make sure that the TAB width in your editor is set to 4 spaces */ + +/** + + @file confini.c + @brief libconfini functions + @author Stefano Gioffré + @copyright GNU General Public License, version 3 or any later version + @version 1.16.3 + @date 2016-2021 + @see https://madmurphy.github.io/libconfini + +**/ + + + /*/| + (_|_) _ _ _ __ _ _ + | (_) |__ ___ ___ _ __ / _(_)_ __ (_) + | | | '_ \ / __/ _ \| '_ \| |_| | '_ \| | + | | | |_) | (_| (_) | | | | _| | | | | | + |_|_|_.__/ \___\___/|_| |_|_| |_|_| |_|_| _ _ + ( | ) + |/*/ + + + +/** + + + @def INIFORMAT_TABLE_AS(_____) + + Content of the table: + + - Bits 1-19: INI syntax + - Bits 20-22: INI semantics + - Bits 23-24: Human syntax (disabled entries) + + + + @typedef int (* IniStatsHandler) ( + IniStatistics * statistics, + void * user_data + ) + + @param statistics + A pointer to the #IniStatistics to handle + @param user_data + The custom argument previously passed to the caller function + + + + @typedef int (* IniDispHandler) ( + IniDispatch *dispatch, + void * user_data + ) + + @param dispatch + A pointer to the #IniDispatch to handle + @param user_data + The custom argument previously passed to the caller function + + + + @typedef int (* IniStrHandler) ( + char * ini_string, + size_t string_length, + size_t string_num, + IniFormat format, + void * user_data + ) + + @param ini_string + The INI string to handle + @param string_length + The length of the INI string in bytes + @param string_num + The unique number that identifies @p ini_string within a + sequence of INI strings; it equals zero if @p ini_string is the + first or the only member of the sequence + @param format + The format of the INI file from which @p ini_string has been + extracted + @param user_data + The custom argument previously passed to the caller function + + + + @typedef int (* IniSubstrHandler) ( + const char * ini_string, + size_t fragm_offset, + size_t fragm_length, + size_t fragm_num, + IniFormat format, + void * user_data + ) + + @param ini_string + The INI string containing the fragment to handle + @param fragm_offset + The offset of the selected fragment in bytes + @param fragm_length + The length of the selected fragment in bytes + @param fragm_num + The unique number that identifies the selected fragment within a + sequence of fragments of @p ini_string; it equals zero if the + fragment is the first or the only member of the sequence + @param format + The format of the INI file from which @p ini_string has been + extracted + @param user_data + The custom argument previously passed to the caller function + + + + @struct IniFormat + + @version 1.0 + @date October 6th, 2018 (version 1.7.0 of the library) + + @property IniFormat::delimiter_symbol + The key-value delimiter character (ASCII only allowed); if set + to `\0`, any space is delimiter + (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`); if, within the format + given, `IniFormat::delimiter_symbol` matches a metacharacter + (`'\\'`, `'\''`, `'\"'`), its role as metacharacter will have + higher priority than its role as delimiter symbol (i.e., the + format will have no key-value delimiter); you may use the + #IniDelimiters `enum` for this. + @property IniFormat::case_sensitive + If set to `true`, string comparisons will be always + case-sensitive. + @property IniFormat::semicolon_marker + The role of the semicolon character (use `enum` + #IniCommentMarker for this). + @property IniFormat::hash_marker + The role of the hash character (use `enum` #IniCommentMarker for + this). + @property IniFormat::section_paths + Defines whether and how the format supports sections (use `enum` + #IniSectionPaths for this). + @property IniFormat::multiline_nodes + Defines which class of entries are allowed to be multi-line (use + `enum` #IniMultiline for this). + @property IniFormat::no_spaces_in_names + If set to `true`, key and section names containing spaces (even + within quotes) will be dispatched as #INI_UNKNOWN. Note that + setting #IniFormat::delimiter_symbol to #INI_ANY_SPACE will not + automatically set this option to `true` (spaces will still be + allowed within quotes, and in section names independently of + quotes). + @property IniFormat::no_single_quotes + If set to `true`, the single-quote character (`'`) will be + considered as a normal character. + @property IniFormat::no_double_quotes + If set to `true`, the double-quote character (`"`) will be + considered as a normal character. + @property IniFormat::implicit_is_not_empty + If set to `true`, implicit keys (see @ref libconfini) will + be always dispatched using the values provided by the global + variables #INI_GLOBAL_IMPLICIT_VALUE and + #INI_GLOBAL_IMPLICIT_V_LEN for the fields #IniDispatch::value + and to #IniDispatch::v_len respectively; if set to `false`, + implicit keys will be considered to be empty keys. + @property IniFormat::do_not_collapse_values + If set to `true`, sequences of one or more spaces in values + (`/\s+/`) will be dispatched verbatim. + @property IniFormat::preserve_empty_quotes + If set to `true`, and if single/double quotes are + metacharacters, ensures that, within values, empty strings + enclosed between quotes (`""` or `''`) will not be collapsed + together with the spaces that surround them. This option is + useful for values containing space-delimited arrays, in order to + preserve their empty members -- as in, for instance: + `coordinates = "" ""`. Note that, in section and key names, + empty strings enclosed between quotes are _always_ collapsed + together with their surrounding spaces. + @property IniFormat::disabled_after_space + If set to `true`, what follows `/[#;]\s/` is allowed to be + parsed as a disabled entry. + @property IniFormat::disabled_can_be_implicit + If set to `false`, comments that do not contain a key-value + delimiter will never be parsed as disabled keys, but always as + simple comments (even if the format supports implicit keys). + + + + @struct IniStatistics + + @property IniStatistics::format + The format of the INI file (see #IniFormat) + @property IniStatistics::bytes + The size of the parsed file in bytes + @property IniStatistics::members + The size of the parsed file in number of members (nodes) -- this + number always equals the number of dispatches that will be + produced by #load_ini_file(), #load_ini_path() or + #strip_ini_cache() + + + + @struct IniDispatch + + @property IniDispatch::format + The format of the INI file (see #IniFormat) + @property IniDispatch::type + The dispatch type (see `enum` #IniNodeType) + @property IniDispatch::data + It can contain a comment, a section path or a key name, + depending on #IniDispatch::type; it cannot be `NULL` + @property IniDispatch::value + It can contain the value of a key element, an empty string or it + can point to the address pointed by the global variable + #INI_GLOBAL_IMPLICIT_VALUE (_the latter is the only case in + which `IniDispatch::value` can be `NULL`_) + @property IniDispatch::append_to + The current section path; it cannot be `NULL` + @property IniDispatch::d_len + The length of the string #IniDispatch::data + @property IniDispatch::v_len + The length of the string #IniDispatch::value + @property IniDispatch::at_len + The length of the string #IniDispatch::append_to + @property IniDispatch::dispatch_id + The dispatch number (the first dispatch is number zero) + + + + @def INI_DISABLED_FLAG + + Example #1: + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + if (dispatch->type & INI_DISABLED_FLAG) { + printf("This is not a real INI node...\n"); + } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Example #2: + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + if ((dispatch->type | INI_DISABLED_FLAG) == INI_DISABLED_KEY) { + printf("Hey! This is either an INI_KEY or an INI_DISABLED_KEY!\n"); + } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + See also #IniNodeType. + + +**/ + + + + /*\ + |*| + |*| LOCAL ENVIRONMENT + |*| ________________________________ + \*/ + + + + /* PREPROCESSOR PREAMBLE */ + + +/* String concatenation facilities */ +#define __PP_CAT__(STEM, ...) STEM ## __VA_ARGS__ +#define __PP_UCAT__(STEM, ...) STEM ## _ ## __VA_ARGS__ +#define __PP_EVALUCAT__(STEM, ...) __PP_UCAT__(STEM, __VA_ARGS__) + +/* Conditionals */ +#define __PP_IIF__(CONDITION, ...) \ + __PP_EVALUCAT__(__PP_UCAT__(__COND, CONDITION), _)(__VA_ARGS__, , ) +#define __COND_0__(IF_TRUE, IF_FALSE, ...) IF_FALSE +#define __COND_1__(IF_TRUE, ...) IF_TRUE + + + /* PREPROCESSOR ENVIRONMENT */ + + +#ifndef CONFINI_IO_FLAVOR +/** + + @brief The I/O API to use (possibly overridden via + `-DCONFINI_IO_FLAVOR=[FLAVOR]`) + + Possible values are `CONFINI_STANDARD` and `CONFINI_POSIX` + +**/ +#define CONFINI_IO_FLAVOR CONFINI_STANDARD +#endif + + + + /* PREPROCESSOR GLUE */ + + +#define _CONFINI_PRIVATE_ITEM_(GROUP, ITEM) \ + __PP_EVALUCAT__(__PP_CAT__(_, GROUP), __PP_CAT__(ITEM, _)) +#define _CONFINI_CURRENT_FLAVOR_GET_(NAME) \ + _CONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, NAME) +#define _CONFINI_IS_FLAVOR_(NAME) \ + _CONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, FLAVOR) == \ + _CONFINI_PRIVATE_ITEM_(NAME, FLAVOR) + + + + /* AVAILABLE I/O FLAVORS */ + + +/* `-DCONFINI_IO_FLAVOR=CONFINI_STANDARD` (C99 Standard, default) */ +#define _CONFINI_STANDARD_SEOF_FN_(FILEPTR) fseek(FILEPTR, 0, SEEK_END) +#define _CONFINI_STANDARD_FT_FN_(FILEPTR) ftell(FILEPTR) +#define _CONFINI_STANDARD_FT_T_ long signed int +/* Any unique non-zero integer to identify this I/O API */ +#define _CONFINI_STANDARD_FLAVOR_ 1 + +/* `-DCONFINI_IO_FLAVOR=CONFINI_POSIX` */ +#define _CONFINI_POSIX_SEOF_FN_(FILEPTR) fseeko(FILEPTR, 0, SEEK_END) +#define _CONFINI_POSIX_FT_FN_(FILEPTR) ftello(FILEPTR) +#define _CONFINI_POSIX_FT_T_ off_t +/* Any unique non-zero integer to identify this I/O API */ +#define _CONFINI_POSIX_FLAVOR_ 2 + +/* Define `_POSIX_C_SOURCE` macro when `CONFINI_IO_FLAVOR == CONFINI_POSIX` */ +#if _CONFINI_IS_FLAVOR_(CONFINI_POSIX) +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#endif + +/* It is possible to add other I/O APIs here. Feel free to contribute! */ + + + + /* CHECKS */ + + +#if _CONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, FLAVOR) == 0 +#error Unsupported I/O API defined in macro CONFINI_IO_FLAVOR +#endif + + + + /* HEADERS */ + +#include <stdlib.h> +#include "confini.h" + + + + /* DEPRECATED FUNCTIONS AND VARIABLES */ + + +#ifdef ini_global_set_lowercase_mode +#undef ini_global_set_lowercase_mode +#endif + +#ifdef INI_GLOBAL_LOWERCASE_MODE +#undef INI_GLOBAL_LOWERCASE_MODE +#endif + + + + /* ALIASES */ + + +#define _CONFINI_FALSE_ 0 +#define _CONFINI_TRUE_ 1 +#define _CONFINI_CHARBOOL_ unsigned char +#define _CONFINI_SIMPLE_SPACE_ '\x20' +#define _CONFINI_HT_ '\t' +#define _CONFINI_FF_ '\f' +#define _CONFINI_VT_ '\v' +#define _CONFINI_CR_ '\r' +#define _CONFINI_LF_ '\n' +#define _CONFINI_BACKSLASH_ '\\' +#define _CONFINI_OPEN_SECTION_ '[' +#define _CONFINI_CLOSE_SECTION_ ']' +#define _CONFINI_SUBSECTION_ '.' +#define _CONFINI_SEMICOLON_ ';' +#define _CONFINI_HASH_ '#' +#define _CONFINI_D_QUOTES_ '"' +#define _CONFINI_S_QUOTES_ '\'' +#define _CONFINI_SEEK_EOF_(FILEPTR) \ + _CONFINI_CURRENT_FLAVOR_GET_(SEOF_FN)(FILEPTR) +#define _CONFINI_FTELL_(FILEPTR) \ + _CONFINI_CURRENT_FLAVOR_GET_(FT_FN)(FILEPTR) +#define _CONFINI_OFF_T_ \ + _CONFINI_CURRENT_FLAVOR_GET_(FT_T) + + + +/* The character that will replace sequences of one or more spaces (`/\s+/`) */ +#define _CONFINI_COLLAPSED_ _CONFINI_SIMPLE_SPACE_ + + +/* + + These may be any characters in theory... But after left-trimming each node + leading spaces work pretty well as markers... + +*/ +/* Internal marker of comment blocks */ +#define _CONFINI_BC_INT_MARKER_ _CONFINI_SIMPLE_SPACE_ +/* Internal marker of inline comments */ +#define _CONFINI_IC_INT_MARKER_ _CONFINI_HT_ + + + + /* FUNCTIONAL MACROS AND CONSTANTS */ + + +/* + + Check whether a character can be escaped within a given format + +*/ +#define _CONFINI_IS_ESC_CHAR_(CHR, FMT) ( \ + CHR == _CONFINI_BACKSLASH_ ? \ + !INIFORMAT_HAS_NO_ESC(FMT) \ + : CHR == _CONFINI_D_QUOTES_ ? \ + !FMT.no_double_quotes \ + : \ + CHR == _CONFINI_S_QUOTES_ && !FMT.no_single_quotes \ + ) + + +/* + + Check whether a character represents any marker within a given format + +*/ +#define _CONFINI_IS_ANY_MARKER_(CHR, FMT) ( \ + CHR == _CONFINI_HASH_ ? \ + FMT.hash_marker != INI_IS_NOT_A_MARKER \ + : \ + CHR == _CONFINI_SEMICOLON_ && \ + FMT.semicolon_marker != INI_IS_NOT_A_MARKER \ + ) + + +/* + + Check whether a character represents any marker except `INI_IGNORE` within a + given format + +*/ +#define _CONFINI_IS_N_I_MARKER_(CHR, FMT) ( \ + CHR == _CONFINI_HASH_ ? \ + FMT.hash_marker != INI_IS_NOT_A_MARKER && \ + FMT.hash_marker != INI_IGNORE \ + : \ + CHR == _CONFINI_SEMICOLON_ && \ + FMT.semicolon_marker != INI_IS_NOT_A_MARKER && \ + FMT.semicolon_marker != INI_IGNORE \ + ) + + +/* + + Check whether a character represents a marker of type `INI_DISABLED_OR_COMMENT` + within a given format + +*/ +#define _CONFINI_IS_DIS_MARKER_(CHR, FMT) ( \ + CHR == _CONFINI_HASH_ ? \ + FMT.hash_marker == INI_DISABLED_OR_COMMENT \ + : \ + CHR == _CONFINI_SEMICOLON_ && \ + FMT.semicolon_marker == INI_DISABLED_OR_COMMENT \ + ) + + +/* + + Check whether a character represents a marker of type `INI_ONLY_COMMENT` within + a given format + +*/ +#define _CONFINI_IS_COM_MARKER_(CHR, FMT) ( \ + CHR == _CONFINI_HASH_ ? \ + FMT.hash_marker == INI_ONLY_COMMENT \ + : \ + CHR == _CONFINI_SEMICOLON_ && \ + FMT.semicolon_marker == INI_ONLY_COMMENT \ + ) + + +/* + + Check whether a character represents a marker of type `INI_IGNORE` within a + given format + +*/ +#define _CONFINI_IS_IGN_MARKER_(CHR, FMT) ( \ + CHR == _CONFINI_HASH_ ? \ + FMT.hash_marker == INI_IGNORE \ + : \ + CHR == _CONFINI_SEMICOLON_ && \ + FMT.semicolon_marker == INI_IGNORE \ + ) + + +/* + + Maybe in the future there will be support for UTF-8 case folding (although + probably not -- see "Code considerations" in the Manual), but for now only + ASCII... + +*/ +#define _CONFINI_CHR_CASEFOLD_(CHR) \ + (CHR > 0x40 && CHR < 0x5b ? CHR | 0x60 : CHR) + + +/* + + Constants related to `_CONFINI_SPACES_` + +*/ +#define _CONFINI_EOL_IDX_ 0 +#define _CONFINI_SPALEN_ 6 + + +/* + + The list of space characters -- do not change its order or its content! + +*/ +static const char _CONFINI_SPACES_[_CONFINI_SPALEN_] = { + _CONFINI_LF_, + _CONFINI_CR_, + _CONFINI_VT_, + _CONFINI_FF_, + _CONFINI_HT_, + _CONFINI_SIMPLE_SPACE_ +}; + + +/* + + Possible depths of `_CONFINI_SPACES_` (see function #is_some_space()). + + Please, consider the following three constants as belonging together to a + virtual opaque `enum`. + +*/ +#define _CONFINI_WITH_EOL_ -1 +#define _CONFINI_NO_EOL_ 1 +#define _CONFINI_JUST_S_T_ 3 + + +/** + + @brief A list of possible string representations of boolean pairs + + There may be infinite pairs here. Each pair must be organized according to the + following order: + + 1. Signifier of `false` + 2. Signifier of `true` + + @note Everything **must** be lowercase in this list. + +**/ +static const char * const INI_BOOLEANS[][2] = { + { "no", "yes" }, + { "false", "true" }, + { "off", "on" } +}; + + +/* + + Other constants related to `INI_BOOLEANS` + +*/ +#define _CONFINI_BOOLLEN_ (sizeof(INI_BOOLEANS) / sizeof(const char * const [2])) + + + + /* ABSTRACT UTILITIES */ + + +/** + + @brief Check whether a character is a space + @param chr The target character + @param depth What is actually considered a space (possible + values: `_CONFINI_WITH_EOL_`, `_CONFINI_NO_EOL_`, + `_CONFINI_JUST_S_T_`) + @return A boolean: `true` if the character matches, `false` otherwise + +**/ +static inline _CONFINI_CHARBOOL_ is_some_space ( + const char chr, + const int_least8_t depth +) { + register int_least8_t idx = depth; + while (++idx < _CONFINI_SPALEN_ && chr != _CONFINI_SPACES_[idx]); + return idx < _CONFINI_SPALEN_; +} + + +/** + + @brief Soft left trim -- does not change the buffer + @param str The target string + @param offs The offset where to start the left trim + @param depth What is actually considered a space (possible + values: `_CONFINI_WITH_EOL_`, `_CONFINI_NO_EOL_`, + `_CONFINI_JUST_S_T_`) + @return The offset of the first non-space character + +**/ +static inline size_t ltrim_s ( + const char * const str, + const size_t offs, + const int_least8_t depth +) { + register size_t idx = offs; + while (is_some_space(str[idx++], depth)); + return idx - 1; +} + + +/** + + @brief Hard left trim -- **does** change the buffer + @param str The target string + @param offs The offset where to start the left trim + @param depth What is actually considered a space (possible + values: `_CONFINI_WITH_EOL_`, `_CONFINI_NO_EOL_`, + `_CONFINI_JUST_S_T_`) + @return The offset of the first non-space character + +**/ +static inline size_t ltrim_h ( + char * const str, + const size_t offs, + const int_least8_t depth +) { + register size_t idx = offs; + while (is_some_space(str[idx], depth)) { str[idx++] = '\0'; } + return idx; +} + + +/** + + @brief Shifting left trim -- **does** change the buffer + @param str The target string + @param offs The offset where to start the left trim + @param depth What is actually considered a space (possible + values: `_CONFINI_WITH_EOL_`, `_CONFINI_NO_EOL_`, + `_CONFINI_JUST_S_T_`) + @return The new length of the string + +**/ +static inline size_t ltrim_hh ( + char * const str, + const size_t offs, + const int_least8_t depth +) { + register size_t idx_d = offs, idx_s = offs; + while (is_some_space(str[idx_s++], depth)); + if (--idx_s - idx_d) { + while ((str[idx_d++] = str[idx_s++])); + for (idx_s = idx_d; str[idx_s]; str[idx_s++] = '\0'); + return idx_d - 1; + } + while (str[idx_s++]); + return idx_s - 1; +} + + +/** + + @brief Soft right trim -- does not change the buffer + @param str The target string + @param len The length of the string + @param depth What is actually considered a space (possible + values: `_CONFINI_WITH_EOL_`, `_CONFINI_NO_EOL_`, + `_CONFINI_JUST_S_T_`) + @return The length of the string until the last non-space character + +**/ +static inline size_t rtrim_s ( + const char * const str, + const size_t len, + const int_least8_t depth +) { + register size_t idx = len + 1; + while (--idx > 0 && is_some_space(str[idx - 1], depth)); + return idx; +} + + +/** + + @brief Hard right trim -- **does** change the buffer + @param str The target string + @param len The length of the string + @param depth What is actually considered a space (possible + values: `_CONFINI_WITH_EOL_`, `_CONFINI_NO_EOL_`, + `_CONFINI_JUST_S_T_`) + @return The new length of the string + +**/ +static inline size_t rtrim_h ( + char * const str, + const size_t len, + const int_least8_t depth +) { + register size_t idx = len; + while (idx > 0 && is_some_space(str[idx - 1], depth)) { str[--idx] = '\0'; } + return idx; +} + + +/** + + @brief Unparsed soft right trim (right trim of `/(?:\s|\\[\n\r])+$/`) + -- does not change the buffer + @param str The target string + @param len The length of the string + @return The length of the string until the last non-space character + +**/ +static inline size_t urtrim_s ( + const char * const str, + const size_t len +) { + + register uint_least8_t abcd = 1; + register size_t idx = len; + + + /* \ /\ + \ */ continue_urtrim: /* \ + \/ ______________________ \ */ + + + if (idx < 1) { + + return idx; + + } + + switch (str[--idx]) { + + case _CONFINI_VT_: + case _CONFINI_FF_: + case _CONFINI_HT_: + case _CONFINI_SIMPLE_SPACE_: + + abcd = 1; + goto continue_urtrim; + + case _CONFINI_LF_: + case _CONFINI_CR_: + + abcd = 3; + goto continue_urtrim; + + case _CONFINI_BACKSLASH_: + + if (abcd >>= 1) { + + goto continue_urtrim; + + } + + } + + return idx + 1; + +} + + +/** + + @brief Convert an ASCII string to lower case + @param str The target string + @return Nothing + +**/ +static inline void string_tolower ( + char * const str +) { + for (register char * chrptr = str; *chrptr; chrptr++) { + *chrptr = _CONFINI_CHR_CASEFOLD_(*chrptr); + } +} + + + + /* CONCRETE UTILITIES */ + + +/** + + @brief Unparsed hard left trim (left trim of + `/^(?:\s+|\\[\n\r]|''|"")+/`) -- **does** change the buffer + @param srcstr The target string (it may contain multi-line + escape sequences) + @param offs The offset where to start the left trim + @param format The format of the INI file + @return The offset of the first non-trivial character + +**/ +static inline size_t qultrim_h ( + char * const srcstr, + const size_t offs, + const IniFormat format +) { + + /* + + Mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 Erase the previous character + FLAG_64 Erase this character + FLAG_128 Continue the loop + + */ + + register uint_least8_t abcd = + (format.no_double_quotes ? 130 : 128) | format.no_single_quotes; + + size_t idx = offs; + + do { + + abcd = + + !(abcd & 28) && is_some_space(srcstr[idx], _CONFINI_NO_EOL_) ? + (abcd & 223) | 64 + : !(abcd & 12) && ( + srcstr[idx] == _CONFINI_LF_ || + srcstr[idx] == _CONFINI_CR_ + ) ? + ( + abcd & 16 ? + (abcd & 239) | 96 + : + abcd | 64 + ) + : !(abcd & 22) && srcstr[idx] == _CONFINI_D_QUOTES_ ? + ( + abcd & 8 ? + (abcd & 231) | 96 + : + (abcd & 159) | 8 + ) + : !(abcd & 25) && srcstr[idx] == _CONFINI_S_QUOTES_ ? + ( + abcd & 4 ? + (abcd & 235) | 96 + : + (abcd & 159) | 4 + ) + : srcstr[idx] == _CONFINI_BACKSLASH_ ? + (abcd & 159) ^ 16 + : + abcd & 31; + + + if (abcd & 32) { + + srcstr[idx - 1] = '\0'; + + } + + if (abcd & 64) { + + srcstr[idx] = '\0'; + + } + + idx++; + + } while (abcd & 128); + + return abcd & 28 ? idx - 2 : idx - 1; + +} + + +/** + + @brief Soft left trim within an unparsed disabled entry (left trim of + `/(?:(?:^|\\?[\n\r])[ \t\v\f]*(?:#(?:[ \t\v\f]|''|"")*)?)+/`) + -- does not change the buffer + @param srcstr The target string (it may contain multi-line + escape sequences) + @param offs The offset where to start the left trim + @param format The format of the INI file + @return The offset of the first non-trivial character + +**/ +static inline size_t dqultrim_s ( + const char * const srcstr, + const size_t offs, + const IniFormat format +) { + + /* + + Mask `abcd` (7 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 A new line has just begun + FLAG_64 Continue the left trim + + */ + + + register uint_least8_t abcd = + (format.no_double_quotes ? 98 : 96) | format.no_single_quotes; + + register size_t idx = offs; + + do { + + abcd = + + is_some_space(srcstr[idx], _CONFINI_NO_EOL_) ? + ( + abcd & 28 ? + abcd & 63 + : + abcd + ) + : srcstr[idx] == _CONFINI_LF_ || srcstr[idx] == _CONFINI_CR_ ? + ( + abcd & 12 ? + (abcd & 47) | 32 + : + (abcd & 111) | 32 + ) + : srcstr[idx] == _CONFINI_BACKSLASH_ ? + ( + abcd & 28 ? + (abcd & 31) | 16 + : + (abcd & 95) | 16 + ) + : (abcd & 32) && _CONFINI_IS_ANY_MARKER_(srcstr[idx], format) ? + abcd & 79 + : !(abcd & 54) && srcstr[idx] == _CONFINI_D_QUOTES_ ? + (abcd & 95) ^ 8 + : !(abcd & 57) && srcstr[idx] == _CONFINI_S_QUOTES_ ? + (abcd & 95) ^ 4 + : srcstr[idx] ? + abcd & 31 + : + abcd & 19; + + + idx++; + + } while (abcd & 64); + + return abcd & 28 ? idx - 2 : idx - 1; + +} + + +/** + + @brief Get the position of the first occurrence out of quotes of a + given character, stopping after a given number of charcters + @param str The string where to search + @param chr The character to to search + @param len The maximum number of characters to read + @param format The format of the INI file + @return The offset of the first occurrence of @p chr, or @p len if + @p chr has not been not found + +**/ +static inline size_t getn_metachar_pos ( + const char * const str, + const char chr, + const size_t len, + const IniFormat format +) { + + size_t idx = 0; + + /* + + Mask `abcd` (5 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + + */ + + for ( + + register uint_least8_t abcd = + (format.no_double_quotes << 1) | format.no_single_quotes; + + idx < len && ( + (abcd & 12) || ( + chr ? + str[idx] != chr + : + !is_some_space(str[idx], _CONFINI_WITH_EOL_) + ) + ); + + abcd = + str[idx] == _CONFINI_BACKSLASH_ ? abcd ^ 16 + : !(abcd & 22) && str[idx] == _CONFINI_D_QUOTES_ ? abcd ^ 8 + : !(abcd & 25) && str[idx] == _CONFINI_S_QUOTES_ ? abcd ^ 4 + : abcd & 15, + idx++ + + ); + + return idx; + +} + + +/** + + @brief Get the position of the first occurrence out of quotes of a + given character + @param str The string where to search + @param chr The character to to search + @param format The format of the INI file + @return The offset of the first occurrence of @p chr or the length of + @p str if @p chr has not been not found + +**/ +static inline size_t get_metachar_pos ( + const char * const str, + const char chr, + const IniFormat format +) { + + size_t idx = 0; + + /* + + Mask `abcd` (5 bits used): + + --> As in #getn_metachar_pos() + + */ + + for ( + + register uint_least8_t abcd = + (format.no_double_quotes << 1) | format.no_single_quotes; + + str[idx] && ( + (abcd & 12) || ( + chr ? + str[idx] != chr + : + !is_some_space(str[idx], _CONFINI_NO_EOL_) + ) + ); + + abcd = + str[idx] == _CONFINI_BACKSLASH_ ? abcd ^ 16 + : !(abcd & 22) && str[idx] == _CONFINI_D_QUOTES_ ? abcd ^ 8 + : !(abcd & 25) && str[idx] == _CONFINI_S_QUOTES_ ? abcd ^ 4 + : abcd & 15, + idx++ + + ); + + return idx; + +} + + +/** + + @brief Replace `/\\(\n\r?|\r\n?)[\t \v\f]*[#;]/` or `/\\(\n\r?|\r\n?)/` + with `"$1"` + @param srcstr The target string (it may contain multi-line + escape sequences) + @param len Length of the string + @param is_disabled The string represents a disabled entry + @param format The format of the INI file + @return The new length of the string + +**/ +static size_t unescape_cr_lf ( + char * const srcstr, + const size_t len, + const _CONFINI_CHARBOOL_ is_disabled, + const IniFormat format +) { + + register size_t idx_s = 0, idx_d = 0; + register _CONFINI_CHARBOOL_ eol_i = _CONFINI_EOL_IDX_; + register _CONFINI_CHARBOOL_ is_escaped = _CONFINI_FALSE_; + size_t probe; + + while (idx_s < len) { + + if ( + is_escaped && ( + srcstr[idx_s] == _CONFINI_SPACES_[eol_i] || + srcstr[idx_s] == _CONFINI_SPACES_[eol_i ^= 1] + ) + ) { + + srcstr[idx_d - 1] = srcstr[idx_s++]; + + if (srcstr[idx_s] == _CONFINI_SPACES_[eol_i ^ 1]) { + + srcstr[idx_d++] = srcstr[idx_s++]; + + } + + if (is_disabled) { + + probe = ltrim_s(srcstr, idx_s, _CONFINI_NO_EOL_); + + if (_CONFINI_IS_DIS_MARKER_(srcstr[probe], format)) { + + idx_s = probe + 1; + + } + + } + + is_escaped = _CONFINI_FALSE_; + + } else { + + is_escaped = + srcstr[idx_s] == _CONFINI_BACKSLASH_ ? + !is_escaped + : + _CONFINI_FALSE_; + + srcstr[idx_d++] = srcstr[idx_s++]; + + } + + } + + for ( + idx_s = idx_d; + idx_s < len; + srcstr[idx_s++] = '\0' + ); + + return idx_d; + +} + + +/** + + @brief Sanitize a section path + @param secpath The section path + @param format The format of the INI file + @return The new length of the string + + Out of quotes, similar to ECMAScript + `secpath.replace(/\.*\s*$|(?:\s*(\.))+\s*|^\s+/g, "$1").replace(/\s+/g, " ")` + + A section path can start with a dot (append), but cannot end with a dot. Spaces + surrounding dots will be removed. Fragments surrounded by single or double + quotes (if these are enabled) are prevented from changes. + +**/ +static size_t sanitize_section_path ( + char * const secpath, + const IniFormat format +) { + + /* + + Mask `abcd` (12 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 These are initial spaces + FLAG_64 This is a space out of quotes + FLAG_128 This is a dot out of quotes + FLAG_256 This is anything *but* an opening single/double quote + FLAG_512 Don't ignore the last two characters + FLAG_1024 Don't overwrite the previous character + FLAG_2048 Path contains at least one name + + */ + + register uint_least16_t abcd = + (format.no_double_quotes ? 1826 : 1824) | format.no_single_quotes; + + register size_t idx_s = 0, idx_d = 0; + + for (; secpath[idx_s]; idx_s++) { + + /* Revision #2 */ + + abcd = + + !(abcd & 12) && is_some_space(secpath[idx_s], _CONFINI_WITH_EOL_) ? + ( + abcd & 224 ? + (abcd & 3055) | 832 + : + (abcd & 4079) | 1856 + ) + : !(abcd & 12) && secpath[idx_s] == _CONFINI_SUBSECTION_ ? + ( + abcd & (abcd & 32 ? 128 : 192) ? + (abcd & 2959) | 896 + : + (abcd & 3983) | 1920 + ) + : !(abcd & 25) && secpath[idx_s] == _CONFINI_S_QUOTES_ ? + ( + ~abcd & 4 ? + (abcd & 3839) | 1540 + : abcd & 256 ? + (abcd & 3867) | 3840 + : + (abcd & 3579) | 1280 + ) + : !(abcd & 22) && secpath[idx_s] == _CONFINI_D_QUOTES_ ? + ( + ~abcd & 8 ? + (abcd & 3839) | 1544 + : abcd & 256 ? + (abcd & 3863) | 3840 + : + (abcd & 3575) | 1280 + ) + : secpath[idx_s] == _CONFINI_BACKSLASH_ ? + ((abcd & 3871) | 3840) ^ 16 + : + (abcd & 3855) | 3840; + + + if (abcd & 512) { + + secpath[ + abcd & 1024 ? + idx_d++ + : idx_d ? + idx_d - 1 + : + idx_d + ] = + !(~abcd & 384) ? + _CONFINI_SUBSECTION_ + : !(~abcd & 320) ? + _CONFINI_COLLAPSED_ + : + secpath[idx_s]; + + } else if (idx_d) { + + idx_d--; + + } + + } + + for ( + + idx_s = + idx_d && (abcd & 2048) && (abcd & 192) ? + --idx_d + : + idx_d; + + secpath[idx_s]; + + secpath[idx_s++] = '\0' + + ); + + return idx_d; + +} + + +/** + + @brief Out of quotes similar to ECMAScript + `ini_string.replace(/''|""/g, "").replace(/^[\n\r]\s*|\s+/g, + " ")` + @param ini_string The string to collapse -- multi-line escape + sequences must be already unescaped at + this stage + @param format The format of the INI file + @return The new length of the string + +**/ +static size_t collapse_everything ( + char * const ini_string, + const IniFormat format +) { + + /* + + Mask `abcd` (9 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 This is *not* a space out of quotes + FLAG_64 This is an opening single/double quote + FLAG_128 Don't ignore this character + FLAG_256 Jump this character and the one before this + + */ + + register size_t idx_s = 0, idx_d = 0; + + register uint_least16_t abcd = + (is_some_space(*ini_string, _CONFINI_WITH_EOL_) ? 128 : 160) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + for (; ini_string[idx_s]; idx_s++) { + + /* Revision #2 */ + + abcd = + + !(abcd & 12) && is_some_space(ini_string[idx_s], _CONFINI_WITH_EOL_) ? + ( + abcd & 32 ? + (abcd & 143) | 128 + : + abcd & 47 + ) + : !(abcd & 25) && ini_string[idx_s] == _CONFINI_S_QUOTES_ ? + ( + ~abcd & 4 ? + (abcd & 239) | 196 + : abcd & 64 ? + (abcd & 299) | 256 + : + (abcd & 171) | 160 + ) + : !(abcd & 22) && ini_string[idx_s] == _CONFINI_D_QUOTES_ ? + ( + ~abcd & 8 ? + (abcd & 239) | 200 + : abcd & 64 ? + (abcd & 295) | 256 + : + (abcd & 167) | 160 + ) + : ini_string[idx_s] == _CONFINI_BACKSLASH_ ? + ((abcd & 191) | 160) ^ 16 + : + (abcd & 175) | 160; + + + if (abcd & 256) { + + idx_d--; + + } else if (abcd & 128) { + + ini_string[idx_d++] = + abcd & 44 ? + ini_string[idx_s] + : + _CONFINI_COLLAPSED_; + + } + + } + + for ( + + idx_s = + !(abcd & 32) && idx_d ? + --idx_d + : + idx_d; + + ini_string[idx_s]; + + ini_string[idx_s++] = '\0' + + ); + + return idx_d; + +} + + +/** + + @brief Out of quotes similar to ECMAScript + `ini_string.replace(/\s+/g, " ")` + @param ini_string The string to collapse -- multi-line escape + sequences must be already unescaped at this + stage + @param format The format of the INI file + @return The new length of the string + +**/ +static size_t collapse_spaces ( + char * const ini_string, + const IniFormat format +) { + + /* + + Mask `abcd` (7 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 This is a space out of quotes + FLAG_64 Jump this character + + */ + + register uint_least8_t abcd = + (format.no_double_quotes ? 34 : 32) | format.no_single_quotes; + + register size_t idx_s = 0; + size_t idx_d = 0; + + for (; ini_string[idx_s]; idx_s++) { + + /* Revision #1 */ + + abcd = + + !(abcd & 12) && is_some_space(ini_string[idx_s], _CONFINI_WITH_EOL_) ? + ( + abcd & 32 ? + (abcd & 111) | 64 + : + (abcd & 47) | 32 + ) + : !(abcd & 25) && ini_string[idx_s] == _CONFINI_S_QUOTES_ ? + (abcd & 15) ^ 4 + : !(abcd & 22) && ini_string[idx_s] == _CONFINI_D_QUOTES_ ? + (abcd & 15) ^ 8 + : ini_string[idx_s] == _CONFINI_BACKSLASH_ ? + (abcd & 31) ^ 16 + : + abcd & 15; + + if (~abcd & 64) { + + ini_string[idx_d++] = + abcd & 32 ? + _CONFINI_COLLAPSED_ + : + ini_string[idx_s]; + + } + + } + + for ( + + idx_s = + (abcd & 32) && idx_d ? + --idx_d + : + idx_d; + + ini_string[idx_s]; + + ini_string[idx_s++] = '\0' + + ); + + return idx_d; + +} + + +/** + + @brief Similar to ECMAScript `str.replace(/''|""/g, "")` + @param str The string to collapse + @param format The format of the INI file + @return The new length of the string + +**/ +static size_t collapse_empty_quotes ( + char * const str, + const IniFormat format +) { + + /* + + Mask `abcd` (7 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 This is an opening single/double quote + FLAG_64 These are empty quotes + + */ + + register uint_least8_t abcd = + (format.no_double_quotes << 1) | format.no_single_quotes; + + register size_t lshift = ltrim_s(str, 0, _CONFINI_WITH_EOL_), idx = lshift; + + for (; str[idx]; idx++) { + + /* Revision #1 */ + + abcd = + + str[idx] == _CONFINI_BACKSLASH_ ? + (abcd & 31) ^ 16 + : !(abcd & 22) && str[idx] == _CONFINI_D_QUOTES_ ? + ( + ~abcd & 40 ? + ((abcd & 47) | 32) ^ 8 + : + (abcd & 71) | 64 + ) + : !(abcd & 25) && str[idx] == _CONFINI_S_QUOTES_ ? + ( + ~abcd & 36 ? + ((abcd & 47) | 32) ^ 4 + : + (abcd & 75) | 64 + ) + : + abcd & 15; + + + str[idx - lshift] = str[idx]; + + if (abcd & 64) { + + lshift += 2; + + } + + } + + for (idx -= lshift; str[idx]; str[idx++] = '\0'); + + return rtrim_h(str, idx - lshift, _CONFINI_WITH_EOL_); + +} + + +/** + + @brief Remove all comment initializers (`#` and/or `;`) from the + beginning of each line of a comment + @param srcstr The comment to parse (it may contain multi-line + escape sequences) + @param len The length of @p srcstr + @param format The format of the INI file + @return The new length of the string + + - Multi-line comments: `srcstr.replace(/^[#;]+|(\n\r?|\r\n?)[\t \v\f]*[#;]+/g, + "$1")` + - Single-line comments: `srcstr.replace(/^[#;]+/, "")` + + The argument @p srcstr may begin with a comment initializer (`#` or `;` + depending on the format), or with the character that immediately follows it. + +**/ +static size_t uncomment ( + char * const srcstr, + size_t len, + const IniFormat format +) { + + register size_t idx_s = 0, idx_d = 0; + + if (format.multiline_nodes == INI_MULTILINE_EVERYWHERE) { + + /* The comment can be multi-line */ + + /* + + Mask `abcd` (6 bits used): + + FLAG_1 Don't erase any character + FLAG_2 We are in an odd sequence of backslashes + FLAG_4 This new line character is escaped + FLAG_8 This character is a comment character and follows + `/(\n\s*|\r\s*)/` + FLAG_16 This character is a part of a group of spaces that follow + a new line (`/(\n|\r)[\t \v\f]+/`) + FLAG_32 This character is *not* a new line character (`/[\r\n]/`) + + */ + + for (register uint_least8_t abcd = 8; idx_s < len; idx_s++) { + + abcd = + + srcstr[idx_s] == _CONFINI_BACKSLASH_ ? + ((abcd & 35) | 32) ^ 2 + : srcstr[idx_s] == _CONFINI_LF_ || srcstr[idx_s] == _CONFINI_CR_ ? + (abcd << 1) & 4 + : !(abcd & 32) && _CONFINI_IS_ANY_MARKER_(srcstr[idx_s], format) ? + (abcd & 40) | 8 + : !(abcd & 40) && is_some_space(srcstr[idx_s], _CONFINI_NO_EOL_) ? + (abcd & 57) | 16 + : + (abcd & 33) | 32; + + + if (!(abcd & 25)) { + + srcstr[abcd & 4 ? idx_d - 1 : idx_d++] = srcstr[idx_s]; + + } else if (!(abcd & 28)) { + + idx_d++; + + } + + } + + } else { + + /* The comment cannot be multi-line */ + + while (idx_s < len && _CONFINI_IS_ANY_MARKER_(srcstr[idx_s], format)) { + + idx_s++; + + } + + if (!idx_s) { + + return len; + + } + + while (idx_s < len) { + + srcstr[idx_d++] = srcstr[idx_s++]; + } + + } + + for ( + idx_s = idx_d; + idx_s < len; + srcstr[idx_s++] = '\0' + ); + + return idx_d; + +} + + +/** + + @brief Try to determine the type of a member **assuming it is active** + @param srcstr String containing an individual node (it may + contain multi-line escape sequences) + @param len Length of the node + @param allow_implicit A boolean: `true` if keys without a key-value + delimiter are allowed, `false` otherwise + @param format The format of the INI file + @return The active node type (see confini.h) + +**/ +static uint_least8_t get_type_as_active ( + const char * const srcstr, + const size_t len, + const _CONFINI_CHARBOOL_ allow_implicit, + const IniFormat format +) { + + const _CONFINI_CHARBOOL_ invalid_delimiter = + _CONFINI_IS_ESC_CHAR_(format.delimiter_symbol, format); + + if ( + !len || _CONFINI_IS_ANY_MARKER_(*srcstr, format) || ( + *((unsigned char *) srcstr) == format.delimiter_symbol && + !invalid_delimiter + ) + ) { + + return INI_UNKNOWN; + + } + + register uint_least16_t abcd; + register size_t idx; + + if ( + format.section_paths != INI_NO_SECTIONS && + *srcstr == _CONFINI_OPEN_SECTION_ + ) { + + if (format.no_spaces_in_names) { + + /* + + Search for the CLOSE SECTION character and possible spaces in names + -- i.e., ECMAScript `/[^\.\s]\s+[^\.\s]/g.test(srcstr)`. The + algorithm is made more complex by the fact that LF and CR characters + are still escaped at this stage. + + */ + + /* + + Mask `abcd` (10 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Only one level of nesting is allowed (const) + FLAG_8 Unescaped single quotes are odd right now + FLAG_16 Unescaped double quotes are odd right now + FLAG_32 We are in an odd sequence of backslashes + FLAG_64 This is a space + FLAG_128 What follows cannot contain spaces + FLAG_256 Continue the loop + FLAG_512 Section path is *not* valid + + */ + + idx = 1; + + abcd = + (format.section_paths == INI_ONE_LEVEL_ONLY ? 772 : 768) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + do { + + /* Revision #2 */ + + abcd = + + idx >= len ? + abcd & 767 + : !(abcd & 42) && srcstr[idx] == _CONFINI_D_QUOTES_ ? + (abcd & 991) ^ 16 + : !(abcd & 49) && srcstr[idx] == _CONFINI_S_QUOTES_ ? + (abcd & 991) ^ 8 + : srcstr[idx] == _CONFINI_LF_ || srcstr[idx] == _CONFINI_CR_ ? + (abcd & 991) | 64 + : is_some_space(srcstr[idx], _CONFINI_NO_EOL_) ? + ( + ~abcd & 32 ? + abcd | 64 + : ~abcd & 192 ? + (abcd & 991) | 192 + : + (abcd & 767) | 128 + ) + : !(abcd & 28) && srcstr[idx] == _CONFINI_SUBSECTION_ ? + ( + ~abcd & 224 ? + abcd & 799 + : + abcd & 767 + ) + : !(abcd & 24) && srcstr[idx] == _CONFINI_CLOSE_SECTION_ ? + ( + ~abcd & 224 ? + abcd & 159 + : + abcd & 767 + ) + : srcstr[idx] == _CONFINI_BACKSLASH_ ? + ( + ~abcd & 32 ? + abcd | 32 + : ~abcd & 192 ? + (abcd & 991) | 128 + : + abcd & 735 + ) + : ~abcd & 192 ? + (abcd & 927) | 128 + : + (abcd & 671) | 128; + + + idx++; + + } while (abcd & 256); + + if (abcd & 512) { + + return INI_UNKNOWN; + + } + + } else if ( + (idx = getn_metachar_pos( + srcstr, + _CONFINI_CLOSE_SECTION_, + len, + format + ) + 1) > len + ) { + + return INI_UNKNOWN; + + } + + /* + + Scan for possible non-space characters following the CLOSE SECTION + character: if found the node cannot represent a section path (but it can + possibly represent a key). Empty quotes will be tolerated. + + */ + + /* + + Recycling variable `abcd` (6 bits used)...: + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes + FLAG_32 Continue the loop + + */ + + abcd = (format.no_double_quotes ? 34 : 32) | format.no_single_quotes; + + + /* \ /\ + \ */ non_space_check: /* \ + \/ ______________________ \ */ + + + if (idx >= len) { + + return INI_SECTION; + + } + + switch (srcstr[idx++]) { + + case _CONFINI_VT_: + case _CONFINI_FF_: + case _CONFINI_HT_: + case _CONFINI_SIMPLE_SPACE_: + + if (abcd & 28) { + + break; + + } + + abcd &= 47; + goto non_space_check; + + case _CONFINI_LF_: + case _CONFINI_CR_: + + if (abcd & 12) { + + break; + + } + + abcd &= 47; + goto non_space_check; + + case _CONFINI_BACKSLASH_: + + if (abcd & 28) { + + break; + + } + + abcd |= 16; + goto non_space_check; + + case _CONFINI_S_QUOTES_: + + if (abcd & 25) { + + break; + + } + + abcd = (abcd & 47) ^ 4; + goto non_space_check; + + case _CONFINI_D_QUOTES_: + + if (abcd & 22) { + + break; + + } + + abcd = (abcd & 47) ^ 8; + goto non_space_check; + + } + + } + + /* + + It can be just a key or `INI_UNKNOWN`... + + */ + + if (invalid_delimiter && !allow_implicit) { + + return INI_UNKNOWN; + + } + + /* + + Recycling variable `abcd` (2 bits used)...: + + FLAG_1 The delimiter **must** be present + FLAG_2 Search for spaces in names + + */ + + abcd = (format.no_spaces_in_names << 1) | !allow_implicit; + + if (abcd) { + + idx = getn_metachar_pos( + srcstr, + (char) format.delimiter_symbol, + len, + format + ); + + if ((abcd & 1) && idx == len) { + + return INI_UNKNOWN; + + } + + if (abcd & 2) { + + idx = urtrim_s(srcstr, idx); + + do { + + if (is_some_space(srcstr[--idx], _CONFINI_WITH_EOL_)) { + + return INI_UNKNOWN; + + } + + } while (idx); + + } + + } + + return INI_KEY; + +} + + +/** + + @brief Examine a (single-/multi-line) segment and check whether + it contains more than just one node + @param srcstr Segment to examine (it may contain multi-line + escape sequences) + @param format The format of the INI file + @return Number of entries found + +**/ +static size_t further_cuts ( + char * const srcstr, + const IniFormat format +) { + + /* Abandon hope all ye who enter here */ + + /* + + Shared flags of mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Do not allow disabled entries after space (const) + FLAG_8 Formats supports multi-line entries everywhere (const) + FLAG_16 Formats supports multi-line entries everywhere except in + comments (const) + FLAG_32 Unescaped single quotes are odd right now + FLAG_64 Unescaped double quotes are odd right now + FLAG_128 We are in an odd sequence of backslashes + + */ + + register uint_least16_t abcd = + ((format.disabled_after_space << 2) ^ 4) | + (format.no_double_quotes << 1) | + format.no_single_quotes | ( + format.multiline_nodes == INI_MULTILINE_EVERYWHERE ? + 8 + : format.multiline_nodes == INI_BUT_COMMENTS ? + 16 + : + 0 + ); + + register size_t idx; + size_t focus_at, unparsable_at, search_at = 0, num_entries = 0; + + + /* \ /\ + \ */ search_for_cuts: /* \ + \/ ______________________ \ */ + + + if (!srcstr[search_at]) { + + return num_entries; + + } + + unparsable_at = 0; + + abcd = + + _CONFINI_IS_DIS_MARKER_(srcstr[search_at], format) && ( + !(abcd & 4) || + !is_some_space(srcstr[search_at + 1], _CONFINI_NO_EOL_) + ) ? + (abcd & 31) | 2560 + : _CONFINI_IS_IGN_MARKER_(srcstr[search_at], format) ? + (abcd & 8 ? (abcd & 31) | 1024 : abcd & 31) + : (abcd & 8) && ( + srcstr[search_at] == _CONFINI_IC_INT_MARKER_ || + _CONFINI_IS_ANY_MARKER_(srcstr[search_at], format) + ) ? + (abcd & 31) | 3072 + : + (abcd & 31) | 2048; + + + if (abcd & 2048) { + + num_entries++; + + } + + if (abcd & 1536) { + + /* + + Node starts with `/[;#]/` and can be a disabled entry in any format, or + a simple comment or a block that must be ignored in multi-line formats + + */ + + /* + + Mask `abcd` (14 bits used): + + FLAG_256 This or the previous character was not a space + FLAG_512 We are in a disabled entry or a comment (semi-const) + FLAG_1024 We are in a simple comment or in a block that must be + ignored and format supports multi-line entries (semi-const) + FLAG_2048 We are *not* in a block that must be ignored (semi-const) + FLAG_4096 We have *not* just found an inline comment nested within a + disabled entry + FLAG_8192 We had previously found an inline comment nested in this + segment, but the entry that preceded it had been checked and + did not seem to represent a valid disabled entry + + NOTE: As for FLAG_1-FLAG_16 I keep the values already assigned at the + beginning of the function; all other flags are already set to zero. + For the meaning of flags FLAG_1-FLAG_128 see the beginning of the + function. + + */ + + idx = ltrim_s(srcstr, search_at + 1, _CONFINI_NO_EOL_) - 1; + + + /* \ /\ + \ */ inactive_cut: /* \ + \/ ______________________ \ */ + + + switch (srcstr[++idx]) { + + case '\0': + + /* End of string */ + + if (~abcd & 8) { + + /* + + Check if this is a valid disabled entry. If it is not, + search for line breaks. + + If the code has reached this point it means that according + to the format disabled entries can be multi-line but + comments cannot, and #get_type_as_active() has never been + invoked on this entry. + + */ + + focus_at = dqultrim_s(srcstr, search_at, format); + + if ( + srcstr[focus_at] && !get_type_as_active( + srcstr + focus_at, + idx - focus_at, + format.disabled_can_be_implicit, + format + ) + ) { + + srcstr[search_at] = _CONFINI_BC_INT_MARKER_; + unparsable_at = search_at + 1; + + } + + } + + break; + + case _CONFINI_LF_: + case _CONFINI_CR_: + + /* + + Line break has been found in a multi-line disabled entry or + a comment. Search for `/\\(?:\n\r?|\r\n?)\s*[^;#]/`. + + */ + + focus_at = dqultrim_s(srcstr, search_at, format); + idx = ltrim_s(srcstr, idx + 1, _CONFINI_WITH_EOL_); + + if ( + idx <= focus_at && (abcd & 2048) && ( + ( + _CONFINI_IS_DIS_MARKER_(srcstr[idx], format) && !( + (abcd & 24) && ( + (~abcd & 516) || !is_some_space( + srcstr[idx + 1], + _CONFINI_NO_EOL_ + ) + ) + ) + ) || _CONFINI_IS_COM_MARKER_(srcstr[idx], format) + ) + ) { + + /* + + See issue #16 + + This entry is composed of one or more empty lines + followed by a simple a comment. The whole block will be + marked as a comment. + + */ + + srcstr[search_at] = _CONFINI_BC_INT_MARKER_; + + if (abcd & 8) { + + abcd = (abcd & 15871) | 8192; + goto inactive_cut; + + } + + unparsable_at = search_at + 1; + break; + + } + + if ( + abcd & 512 ? + !(abcd & 24) || ( + ( + !_CONFINI_IS_DIS_MARKER_(srcstr[idx], format) || ( + (abcd & 4) && is_some_space( + srcstr[idx + 1], + _CONFINI_NO_EOL_ + ) + ) + ) && /* see issue #16 */ idx > focus_at && + get_type_as_active( + srcstr + focus_at, + idx - focus_at, + format.disabled_can_be_implicit, + format + ) + ) || !_CONFINI_IS_ANY_MARKER_(srcstr[idx], format) + : abcd & 2048 ? + !(abcd & 8) || + !_CONFINI_IS_N_I_MARKER_(srcstr[idx], format) + : + !_CONFINI_IS_IGN_MARKER_(srcstr[idx], format) + ) { + + /* Split this line from the previous entry */ + + rtrim_h(srcstr, idx, _CONFINI_WITH_EOL_); + search_at = qultrim_h(srcstr, idx, format); + goto search_for_cuts; + + } + + /* + + No case break here, keep it like this! `case /[ \t\v\f]/` must + follow (switch case fallthrough). + + */ + + case _CONFINI_VT_: + case _CONFINI_FF_: + case _CONFINI_HT_: + case _CONFINI_SIMPLE_SPACE_: + + abcd = (abcd & 15999) | 4096; + goto inactive_cut; + + case _CONFINI_BACKSLASH_: + + abcd = (abcd | 4352) ^ 128; + goto inactive_cut; + + default: + + abcd = + + !(abcd & 1376) && (~abcd & 8200) && + _CONFINI_IS_ANY_MARKER_(srcstr[idx], format) ? + (abcd & 12159) | 256 + : !(abcd & 162) && srcstr[idx] == _CONFINI_D_QUOTES_ ? + ((abcd & 16255) | 4352) ^ 64 + : !(abcd & 193) && srcstr[idx] == _CONFINI_S_QUOTES_ ? + ((abcd & 16255) | 4352) ^ 32 + : + (abcd & 16255) | 4352; + + + if (abcd & 4096) { + + /* Nothing special has been found */ + goto inactive_cut; + + } + + if (~abcd & 8192) { + + /* + + An inline comment has been found in a (supposedly) disabled + entry. + + */ + + focus_at = dqultrim_s(srcstr, search_at, format); + + if ( + get_type_as_active( + srcstr + focus_at, + idx - focus_at, + format.disabled_can_be_implicit, + format + ) + ) { + + if (!_CONFINI_IS_IGN_MARKER_(srcstr[idx], format)) { + + srcstr[idx] = _CONFINI_IC_INT_MARKER_; + num_entries++; + + } + + srcstr[idx - 1] = '\0'; + + if (abcd & 8) { + + abcd &= 15871; + goto inactive_cut; + + } + + unparsable_at = idx + 1; + + } else { + + srcstr[search_at] = _CONFINI_BC_INT_MARKER_; + + if (abcd & 8) { + + abcd = (abcd & 15871) | 8192; + goto inactive_cut; + + } + + unparsable_at = search_at + 1; + + } + + } + + /* No case break here (last case) */ + + } + + } else if (_CONFINI_IS_ANY_MARKER_(srcstr[search_at], format)) { + + /* + + Node starts with `/[;#]/` but cannot be multi-line or represent a + disabled entry + + */ + + unparsable_at = search_at + 1; + + } else { + + /* + + Node is active: search for inline comments + + */ + + /* + + Recycling variable `abcd` (11 bits used)...: + + FLAG_256 Comment marker follows an escaped new line made of only one + character (i.e., `"\\\n"` or `"\\\r"` but neither `"\\\r\n"` + nor `"\\\n\r"`) + FLAG_512 This was neither a hash nor a semicolon character + FLAG_1024 This was not a space + + NOTE: As for FLAG_1-FLAG_16 I keep the values already assigned at the + beginning of the function; all other flags are already set to zero + (see previous usage of `abcd` within this function), with the only + exception of FLAG_2048, which I am going to overwrite immediately. + For the meaning of flags FLAG_1-FLAG_128 see the beginning of the + function. + + */ + + abcd = (abcd & 2047) | 1536; + idx = search_at; + + + /* \ /\ + \ */ active_cut: /* \ + \/ ______________________ \ */ + + + switch (srcstr[++idx]) { + + case '\0': + + /* End of string */ + break; + + case _CONFINI_VT_: + case _CONFINI_FF_: + case _CONFINI_HT_: + case _CONFINI_SIMPLE_SPACE_: + + abcd = (abcd & 639) | 512; + goto active_cut; + + case _CONFINI_LF_: + case _CONFINI_CR_: + + abcd = (abcd & 639) | ((abcd << 1) & 256) | 512; + goto active_cut; + + case _CONFINI_BACKSLASH_: + + abcd = ((abcd & 1791) | 1536) ^ 128; + goto active_cut; + + default: + + abcd = + + _CONFINI_IS_ANY_MARKER_(srcstr[idx], format) ? + abcd & 1407 + : !(abcd & 162) && srcstr[idx] == _CONFINI_D_QUOTES_ ? + ((abcd & 1791) | 1536) ^ 64 + : !(abcd & 193) && srcstr[idx] == _CONFINI_S_QUOTES_ ? + ((abcd & 1791) | 1536) ^ 32 + : + (abcd & 1791) | 1536; + + + if (abcd & 1760) { + + /* Nothing special has been found */ + goto active_cut; + + } + + /* + + Inline comment has been found in an active entry. + + */ + + if (abcd & 256) { + + /* + + Remove the backslash if the comment immediately follows an + escaped new line expressed by one chararacter + (`/\\[\r\n]/`). In case of CR + LF or LF + CR + (`/\\\n\r|\\\r\n/`) the backslash will be removed later by + #strip_ini_cache(). + + */ + + srcstr[idx - 2] = '\0'; + + } + + srcstr[idx - 1] = '\0'; + + if (!_CONFINI_IS_IGN_MARKER_(srcstr[idx], format)) { + + srcstr[idx] = _CONFINI_IC_INT_MARKER_; + + if (abcd & 8) { + + search_at = idx; + goto search_for_cuts; + + } + + num_entries++; + + } else if (abcd & 8) { + + search_at = idx; + goto search_for_cuts; + + } + + unparsable_at = idx + 1; + /* No case break here (last case) */ + + } + + } + + if (unparsable_at) { + + /* + + Cut unparsable multi-line comments + + */ + + for (idx = unparsable_at; srcstr[idx]; idx++) { + + if (srcstr[idx] == _CONFINI_LF_ || srcstr[idx] == _CONFINI_CR_) { + + search_at = qultrim_h(srcstr, idx, format); + goto search_for_cuts; + + } + + } + + } + + return num_entries; + +} + +/** @startfnlist **/ + + + + /*\ + |*| + |*| GLOBAL ENVIRONMENT + |*| ________________________________ + \*/ + + + + /* LIBRARY'S MAIN FUNCTIONS */ + + + /** @utility{strip_ini_cache} **/ +/** + + @brief Parse and tokenize a buffer containing an INI file, then + dispatch its content to a custom callback + @param ini_source The buffer containing the INI file to tokenize + @param ini_length The length of @p ini_source without counting the + NUL terminator (if any -- se below) + @param format The format of the INI file + @param f_init The function that will be invoked before the + first dispatch, or `NULL` + @param f_foreach The function that will be invoked for each + dispatch, or `NULL` + @param user_data A custom argument, or `NULL` + @return Zero for success, otherwise an error code (see `enum` + #ConfiniInterruptNo) + + The @p ini_source parameter must be a valid pointer to a buffer of size + @p ini_length + 1 and cannot be `NULL`. The @p ini_source string does not need + to be NUL-terminated, but _it does need one extra byte where to append a NUL + terminator_ -- in fact, as soon as this function is invoked, + `ini_source[ini_length]` will be immediately set to `\0`. + + In most cases, as when using `strlen()` for computing @p ini_length, this is not + a concern, since `ini_source[ini_length]` will always be `\0` by the very + definition of `strlen()`, and will only get overwritten with the same value. + However, if you are passing a substring of a string, for example the fragment + `foo=bar` of the string `foo=barracuda`, you must expect the string to be + immediately truncated into `foo=bar\0acuda`. + + In other words, @p ini_source must point to a memory location where at least + `ini_length + 1` bytes are freely usable. + + The user given function @p f_init (see #IniStatsHandler data type) will be + invoked with two arguments: `statistics` (a pointer to an #IniStatistics + structure containing some properties about the file read) and `user_data` (the + custom argument @p user_data previously passed). If @p f_init returns a non-zero + value the caller function will be interrupted. + + The user given function @p f_foreach (see #IniDispHandler data type) will be + invoked with two arguments: `dispatch` (a pointer to an #IniDispatch structure + containing the parsed member of the INI file) and `user_data` (the custom + argument @p user_data previously passed). If @p f_foreach returns a non-zero + value the caller function will be interrupted. + + After invoking `strip_ini_cache()`, the buffer pointed by the @p ini_source + parameter must be considered as a _corrupted buffer_ and should be freed or + overwritten. For more information about this function, please refer to the + @ref libconfini. + + The parsing algorithms used by **libconfini** are able to parse any type of file + encoded in 8-bit code units, as long as the characters that match the regular + expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in + ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of + platform-specific conventions. + + @note In order to be null-byte-injection-safe, before dispatching the parsed + content this function strips all `NUL` characters possibly present in + the buffer (with the exception of the last one). + + Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR, + #CONFINI_EOOR. + + @include topics/strip_ini_cache.c + +**/ +int strip_ini_cache ( + register char * const ini_source, + const size_t ini_length, + const IniFormat format, + const IniStatsHandler f_init, + const IniDispHandler f_foreach, + void * const user_data +) { + + const _CONFINI_CHARBOOL_ valid_delimiter = + !_CONFINI_IS_ESC_CHAR_(format.delimiter_symbol, format); + + _CONFINI_CHARBOOL_ tmp_bool; + register size_t idx, tmp_fast_size_t_1, tmp_fast_size_t_2; + size_t tmp_size_t_1, tmp_size_t_2; + + ini_source[ini_length] = '\0'; + + /* + + PART ONE: Examine and isolate each segment + + */ + + #define __ISNT_ESCAPED__ tmp_bool + #define __LSHIFT__ tmp_fast_size_t_1 + #define __EOL_N__ tmp_fast_size_t_2 + #define __NL_AT__ tmp_size_t_1 + #define __N_MEMBERS__ tmp_size_t_2 + + /* UTF-8 BOM */ + __LSHIFT__ = + *((unsigned char *) ini_source) == 0xEF && + *((unsigned char *) ini_source + 1) == 0xBB && + *((unsigned char *) ini_source + 2) == 0xBF + ? 3 : 0; + + for ( + + __N_MEMBERS__ = 0, + __EOL_N__ = _CONFINI_EOL_IDX_, + __ISNT_ESCAPED__ = _CONFINI_TRUE_, + __NL_AT__ = 0, + idx = __LSHIFT__; + + idx < ini_length; + + idx++ + + ) { + + ini_source[idx - __LSHIFT__] = ini_source[idx]; + + if ( + ini_source[idx] == _CONFINI_SPACES_[__EOL_N__] || + ini_source[idx] == _CONFINI_SPACES_[__EOL_N__ ^= 1] + ) { + + if (format.multiline_nodes == INI_NO_MULTILINE || __ISNT_ESCAPED__) { + + ini_source[idx - __LSHIFT__] = '\0'; + __N_MEMBERS__ += further_cuts( + ini_source + qultrim_h(ini_source, __NL_AT__, format), + format + ); + __NL_AT__ = idx - __LSHIFT__ + 1; + + } else if (ini_source[idx + 1] == _CONFINI_SPACES_[__EOL_N__ ^ 1]) { + + idx++; + ini_source[idx - __LSHIFT__] = ini_source[idx]; + + } + + __ISNT_ESCAPED__ = _CONFINI_TRUE_; + + } else if (ini_source[idx] == _CONFINI_BACKSLASH_) { + + __ISNT_ESCAPED__ = !__ISNT_ESCAPED__; + + } else if (ini_source[idx]) { + + __ISNT_ESCAPED__ = _CONFINI_TRUE_; + + } else { + + /* Remove `NUL` characters from the buffer (if any) */ + __LSHIFT__++; + + } + + } + + const size_t real_length = idx - __LSHIFT__; + + while (idx > real_length) { + + ini_source[--idx] = '\0'; + + } + + __N_MEMBERS__ += further_cuts( + ini_source + qultrim_h(ini_source, __NL_AT__, format), + format + ); + + /* Debug */ + + /* + + for (size_t tmp = 0; tmp < ini_length + 1; tmp++) { + putchar(ini_source[tmp] == 0 ? '$' : ini_source[tmp]); + } + putchar('\n'); + + */ + + IniStatistics this_doc = { + .format = format, + .bytes = ini_length, + .members = __N_MEMBERS__ + }; + + if (f_init && f_init(&this_doc, user_data)) { + + return CONFINI_IINTR; + + } + + #undef __N_MEMBERS__ + #undef __NL_AT__ + #undef __EOL_N__ + #undef __LSHIFT__ + #undef __ISNT_ESCAPED__ + + /* + + PART TWO: Dispatch the parsed input + + */ + + if (!f_foreach) { + + return CONFINI_SUCCESS; + + } + + #define __NODE_AT__ tmp_fast_size_t_1 + #define __CURR_PARENT_LEN__ tmp_fast_size_t_2 + #define __SUBPARENT_LEN__ tmp_size_t_1 + #define __REAL_PARENT_LEN__ tmp_size_t_2 + #define __PARENT_IS_DISABLED__ tmp_bool + + __REAL_PARENT_LEN__ = __CURR_PARENT_LEN__ = __SUBPARENT_LEN__ = 0; + + char + * curr_parent_str = ini_source + real_length, + * subparent_str = curr_parent_str, + * real_parent_str = curr_parent_str; + + IniDispatch dsp = { + .format = format, + .dispatch_id = 0 + }; + + __PARENT_IS_DISABLED__ = _CONFINI_FALSE_; + + for (__NODE_AT__ = 0, idx = 0; idx <= real_length; idx++) { + + if (ini_source[idx]) { + + continue; + + } + + if ( + !ini_source[__NODE_AT__] || + _CONFINI_IS_IGN_MARKER_(ini_source[__NODE_AT__], format) + ) { + + __NODE_AT__ = idx + 1; + continue; + + } + + if (dsp.dispatch_id >= this_doc.members) { + + return CONFINI_EOOR; + + } + + dsp.data = ini_source + __NODE_AT__; + dsp.d_len = idx - __NODE_AT__; + + /* We won't need `__NODE_AT__` for a while, so let's recycle it... */ + #undef __NODE_AT__ + #define __ITER__ tmp_fast_size_t_1 + + /* Set `dsp.value` to an empty string */ + dsp.value = ini_source + idx; + dsp.v_len = 0; + + if ( + _CONFINI_IS_DIS_MARKER_(*dsp.data, format) && ( + format.disabled_after_space || + !is_some_space(dsp.data[1], _CONFINI_NO_EOL_) + ) + ) { + + __ITER__ = dqultrim_s(dsp.data, 0, format); + + dsp.type = get_type_as_active( + dsp.data + __ITER__, + dsp.d_len - __ITER__, + format.disabled_can_be_implicit, + format + ); + + if (dsp.type) { + + dsp.data += __ITER__; + dsp.d_len -= __ITER__; + + /* + + // Not strictly needed... + for (; __ITER__ > 0; dsp.data[--__ITER__] = '\0'); + + */ + + } + + dsp.type |= INI_DISABLED_FLAG; + + } else { + + switch (*dsp.data) { + + default: + + if (!_CONFINI_IS_ANY_MARKER_(*dsp.data, format)) { + + dsp.type = get_type_as_active( + dsp.data, + dsp.d_len, + _CONFINI_TRUE_, + format + ); + + break; + + } + + /* + + No case break here, keep it like this! + `case _CONFINI_BC_INT_MARKER_` must follow + (switch case fallthrough). + + */ + + case _CONFINI_BC_INT_MARKER_: + + /* + + // Not strictly needed... + *dsp.data = '\0'; + + */ + + dsp.type = INI_COMMENT; + break; + + case _CONFINI_IC_INT_MARKER_: + + /* + + // Not strictly needed... + *dsp.data = '\0'; + + */ + + dsp.type = INI_INLINE_COMMENT; + /* No case break here (last case) */ + + } + + } + + if (__CURR_PARENT_LEN__ && __SUBPARENT_LEN__) { + + __ITER__ = 0; + + do { + + curr_parent_str[__CURR_PARENT_LEN__ + __ITER__] = + subparent_str[__ITER__]; + + } while (__ITER__++ < __SUBPARENT_LEN__); + + __CURR_PARENT_LEN__ += __SUBPARENT_LEN__; + subparent_str = curr_parent_str + __CURR_PARENT_LEN__; + __SUBPARENT_LEN__ = 0; + + } + + if (__PARENT_IS_DISABLED__ && !(dsp.type & INI_DISABLED_FLAG)) { + + real_parent_str[__REAL_PARENT_LEN__] = '\0'; + __CURR_PARENT_LEN__ = __REAL_PARENT_LEN__; + curr_parent_str = real_parent_str; + __PARENT_IS_DISABLED__ = _CONFINI_FALSE_; + + } else if (!__PARENT_IS_DISABLED__ && dsp.type == INI_DISABLED_SECTION) { + + __REAL_PARENT_LEN__ = __CURR_PARENT_LEN__; + real_parent_str = curr_parent_str; + __PARENT_IS_DISABLED__ = _CONFINI_TRUE_; + + } + + dsp.append_to = curr_parent_str; + dsp.at_len = __CURR_PARENT_LEN__; + + if (dsp.type == INI_COMMENT || dsp.type == INI_INLINE_COMMENT) { + + dsp.d_len = uncomment(++dsp.data, dsp.d_len - 1, format); + + } else if (format.multiline_nodes != INI_NO_MULTILINE) { + + dsp.d_len = unescape_cr_lf( + dsp.data, + dsp.d_len, + dsp.type & INI_DISABLED_FLAG, + format + ); + + } + + switch (dsp.type) { + + /* + + case INI_UNKNOWN: + + // Do nothing + + break; + + */ + + case INI_SECTION: + case INI_DISABLED_SECTION: + + *dsp.data++ = '\0'; + + __ITER__ = getn_metachar_pos( + dsp.data, + _CONFINI_CLOSE_SECTION_, + dsp.d_len, + format + ); + + while (dsp.data[__ITER__]) { + + dsp.data[__ITER__++] = '\0'; + + } + + dsp.d_len = + format.section_paths == INI_ONE_LEVEL_ONLY ? + collapse_everything(dsp.data, format) + : + sanitize_section_path(dsp.data, format); + + if ( + format.section_paths == INI_ONE_LEVEL_ONLY || + *dsp.data != _CONFINI_SUBSECTION_ + ) { + + /* + + Append to root (this is an absolute path) + + */ + + curr_parent_str = dsp.data; + __CURR_PARENT_LEN__ = dsp.d_len; + subparent_str = ini_source + idx; + __SUBPARENT_LEN__ = 0; + dsp.append_to = subparent_str; + dsp.at_len = 0; + + } else if ( + format.section_paths == INI_ABSOLUTE_ONLY || + !__CURR_PARENT_LEN__ + ) { + + /* + + Append to root and remove the leading dot (parent is root or + relative paths are not allowed) + + */ + + curr_parent_str = ++dsp.data; + __CURR_PARENT_LEN__ = --dsp.d_len; + subparent_str = ini_source + idx; + __SUBPARENT_LEN__ = 0; + dsp.append_to = subparent_str; + dsp.at_len = 0; + + } else if (dsp.d_len != 1) { + + /* + + Append to the current parent (this is a relative path + and parent is not root) + + */ + + subparent_str = dsp.data; + __SUBPARENT_LEN__ = dsp.d_len; + + } + + if (INI_GLOBAL_LOWERCASE_MODE && !format.case_sensitive) { + + string_tolower(dsp.data); + + } + + break; + + case INI_KEY: + case INI_DISABLED_KEY: + + if ( + valid_delimiter && + (__ITER__ = getn_metachar_pos( + dsp.data, + (char) dsp.format.delimiter_symbol, + dsp.d_len, + format + )) < dsp.d_len + ) { + + dsp.data[__ITER__] = '\0'; + dsp.value = dsp.data + __ITER__ + 1; + + switch ( + (format.preserve_empty_quotes << 1) | + format.do_not_collapse_values + ) { + + case 0: dsp.v_len = collapse_everything(dsp.value, format); break; + + case 1: dsp.v_len = collapse_empty_quotes(dsp.value, format); break; + + case 2: dsp.v_len = collapse_spaces(dsp.value, format); break; + + case 4: + + dsp.value += ltrim_h(dsp.value, 0, _CONFINI_WITH_EOL_); + + dsp.v_len = rtrim_h( + dsp.value, + dsp.d_len + dsp.data - dsp.value, + _CONFINI_WITH_EOL_ + ); + + /* No case break here (last case) */ + + } + + } else if (format.implicit_is_not_empty) { + + dsp.value = INI_GLOBAL_IMPLICIT_VALUE; + dsp.v_len = INI_GLOBAL_IMPLICIT_V_LEN; + + } + + dsp.d_len = collapse_everything(dsp.data, format); + + if (INI_GLOBAL_LOWERCASE_MODE && !format.case_sensitive) { + + string_tolower(dsp.data); + + } + + break; + + case INI_COMMENT: + case INI_INLINE_COMMENT: + + dsp.append_to = ini_source + idx; + dsp.at_len = 0; + /* No case break here (last case) */ + + } + + if (f_foreach(&dsp, user_data)) { + + return CONFINI_FEINTR; + + } + + dsp.dispatch_id++; + + /* Restore `__NODE_AT__` on **exactly** the same variable as before */ + #undef __ITER__ + #define __NODE_AT__ tmp_fast_size_t_1 + __NODE_AT__ = idx + 1; + + } + + #undef __PARENT_IS_DISABLED__ + #undef __REAL_PARENT_LEN__ + #undef __SUBPARENT_LEN__ + #undef __CURR_PARENT_LEN__ + #undef __NODE_AT__ + + return CONFINI_SUCCESS; + +} + + + /** @utility{load_ini_file} **/ +/** + + @brief Parse an INI file and dispatch its content to a custom callback + using a `FILE` structure as argument + @param ini_file The `FILE` handle pointing to the INI file to + parse + @param format The format of the INI file + @param f_init The function that will be invoked before the + first dispatch, or `NULL` + @param f_foreach The function that will be invoked for each + dispatch, or `NULL` + @param user_data A custom argument, or `NULL` + @return Zero for success, otherwise an error code (see `enum` + #ConfiniInterruptNo) + + @note This function is absent if the `--without-io-api` option was passed to + the `configure` script when the library was compiled + + The @p ini_file parameter must be a `FILE` handle with read privileges. On some + platforms, such as Microsoft Windows, it might be necessary to add the binary + specifier to the mode string (`"b"`) in order to prevent discrepancies between + the physical size of the file and its computed size. Adding the binary specifier + guarantees portability across all platforms: + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + FILE * my_file = fopen("example.conf", "rb"); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + For the two parameters @p f_init and @p f_foreach see function + #strip_ini_cache(). + + The parsing algorithms used by **libconfini** are able to parse any type of file + encoded in 8-bit code units, as long as the characters that match the regular + expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in + ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of + platform-specific conventions. + + @note In order to be null-byte-injection safe, `NUL` characters, if present in + the file, will be removed from the dispatched strings. + + Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR, + #CONFINI_ENOMEM, #CONFINI_EIO, #CONFINI_EOOR, #CONFINI_EBADF, #CONFINI_EFBIG. + + @include topics/load_ini_file.c + +**/ +int load_ini_file ( + FILE * const ini_file, + const IniFormat format, + const IniStatsHandler f_init, + const IniDispHandler f_foreach, + void * const user_data +) { + + _CONFINI_OFF_T_ file_size; + + if ( + _CONFINI_SEEK_EOF_(ini_file) || + (file_size = _CONFINI_FTELL_(ini_file)) < 0 + ) { + + return CONFINI_EBADF; + + } + + if (file_size > SIZE_MAX) { + + return CONFINI_EFBIG; + + } + + char * const cache = (char *) malloc((size_t) file_size + 1); + + if (!cache) { + + return CONFINI_ENOMEM; + + } + + rewind(ini_file); + + if (fread(cache, 1, (size_t) file_size, ini_file) < file_size) { + + free(cache); + return CONFINI_EIO; + + } + + const int return_value = strip_ini_cache( + cache, + (size_t) file_size, + format, + f_init, + f_foreach, + user_data + ); + + free(cache); + return return_value; + +} + + + /** @utility{load_ini_path} **/ +/** + + @brief Parse an INI file and dispatch its content to a custom callback + using a path as argument + @param path The path of the INI file + @param format The format of the INI file + @param f_init The function that will be invoked before the + first dispatch, or `NULL` + @param f_foreach The function that will be invoked for each + dispatch, or `NULL` + @param user_data A custom argument, or `NULL` + @return Zero for success, otherwise an error code (see `enum` + #ConfiniInterruptNo) + + @note This function is absent if the `--without-io-api` option was passed to + the `configure` script when the library was compiled + + For the two parameters @p f_init and @p f_foreach see function + #strip_ini_cache(). + + The parsing algorithms used by **libconfini** are able to parse any type of file + encoded in 8-bit code units, as long as the characters that match the regular + expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in + ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of + platform-specific conventions. + + @note In order to be null-byte-injection safe, `NUL` characters, if present in + the file, will be removed from the dispatched strings. + + Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR, + #CONFINI_ENOENT, #CONFINI_ENOMEM, #CONFINI_EIO, #CONFINI_EOOR, #CONFINI_EBADF, + #CONFINI_EFBIG. + + @include topics/load_ini_path.c + +**/ +int load_ini_path ( + const char * const path, + const IniFormat format, + const IniStatsHandler f_init, + const IniDispHandler f_foreach, + void * const user_data +) { + + FILE * const ini_file = fopen(path, "rb"); + + if (!ini_file) { + + return CONFINI_ENOENT; + + } + + _CONFINI_OFF_T_ file_size; + + if ( + _CONFINI_SEEK_EOF_(ini_file) || + (file_size = _CONFINI_FTELL_(ini_file)) < 0 + ) { + + return CONFINI_EBADF; + + } + + if (file_size > SIZE_MAX) { + + return CONFINI_EFBIG; + + } + + char * const cache = (char *) malloc((size_t) file_size + 1); + + if (!cache) { + + return CONFINI_ENOMEM; + + } + + rewind(ini_file); + + if (fread(cache, 1, (size_t) file_size, ini_file) < file_size) { + + free(cache); + return CONFINI_EIO; + + } + + /* No checks here, as there is nothing we can do about it... */ + fclose(ini_file); + + const int return_value = strip_ini_cache( + cache, + (size_t) file_size, + format, + f_init, + f_foreach, + user_data + ); + + free(cache); + return return_value; + +} + + + + /* OTHER UTILITIES (NOT REQUIRED BY LIBCONFINI'S MAIN FUNCTIONS) */ + + + /** @utility{ini_string_match_ss} **/ +/** + + @brief Compare two simple strings and check whether they match + @param simple_string_a The first simple string + @param simple_string_b The second simple string + @param format The format of the INI file + @return A boolean: `true` if the two strings match, `false` otherwise + + Simple strings are user-given strings or the result of #ini_string_parse(). The + @p format argument is used for the following fields: + + - `format.case_sensitive` + +**/ +bool ini_string_match_ss ( + const char * const simple_string_a, + const char * const simple_string_b, + const IniFormat format +) { + + register size_t idx = 0; + + if (format.case_sensitive) { + + do { + + if (simple_string_a[idx] != simple_string_b[idx]) { + + return _CONFINI_FALSE_; + + } + + } while (simple_string_a[idx++]); + + return _CONFINI_TRUE_; + + } + + do { + + if ( + _CONFINI_CHR_CASEFOLD_(simple_string_a[idx]) != + _CONFINI_CHR_CASEFOLD_(simple_string_b[idx]) + ) { + + return _CONFINI_FALSE_; + + } + + } while (simple_string_a[idx++]); + + return _CONFINI_TRUE_; + +} + + + /** @utility{ini_string_match_si} **/ +/** + + @brief Compare a simple string and an INI string and and check whether + they match + @param ini_string The INI string escaped according to + @p format + @param simple_string The simple string + @param format The format of the INI file + @return A boolean: `true` if the two strings match, `false` otherwise + + This function grants that the result of the comparison between a simple string + and an INI string + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + printf( + "%s\n", + ini_string_match_si(my_simple_string, my_ini_string, format) ? + "They match" + : + "They don't match" + ); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + will always match the result of the _literal_ comparison between the simple + string and the INI string after the latter has been parsed by + #ini_string_parse() when `format.do_not_collapse_values` is set to `false`. + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + ini_string_parse(my_ini_string, format); + + printf( + "%s\n", + ini_string_match_ss(my_simple_string, my_ini_string, format) ? + "They match" + : + "They don't match" + ); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + INI strings are the strings typically dispatched by #load_ini_file(), + #load_ini_path() or #strip_ini_cache(), which may contain quotes and the three + escape sequences `\\`, `\'` and `\"`. Simple strings are user-given strings or + the result of #ini_string_parse(). + + In order to be suitable for both names and values, **this function always + considers sequences of one or more spaces out of quotes in the INI string as + collapsed**, even when `format.do_not_collapse_values` is set to `true`. + + The @p format argument is used for the following fields: + + - `format.case_sensitive` + - `format.no_double_quotes` + - `format.no_single_quotes` + - `format.multiline_nodes` (`INIFORMAT_HAS_NO_ESC()`) + + @include topics/ini_string_match_si.c + +**/ +bool ini_string_match_si ( + const char * const simple_string, + const char * const ini_string, + const IniFormat format +) { + + /* + + Mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Format supports escape sequences (const) + FLAG_8 Unescaped single quotes are odd right now + FLAG_16 Unescaped double quotes are odd right now + FLAG_32 This is an escaped single/double quote in a format that supports + single/double quotes + FLAG_64 This is a space + FLAG_128 Skip this character + + */ + + register uint_least8_t abcd = + INIFORMAT_HAS_NO_ESC(format) ? + 67 + : + (format.no_double_quotes ? 70 : 68) | format.no_single_quotes; + + register size_t idx_i = 0; + size_t idx_s = 0, nbacksl = 0; + + + /* \ /\ + \ */ si_match: /* \ + \/ ______________________ \ */ + + + if ((abcd & 4) && ini_string[idx_i] == _CONFINI_BACKSLASH_) { + + for ( + abcd &= 63, nbacksl++; + ini_string[++idx_i] == _CONFINI_BACKSLASH_; + nbacksl++ + ); + + } + + /* Keep this algorithm identical to #ini_get_bool_i() */ + + abcd = + + !(abcd & 10) && ini_string[idx_i] == _CONFINI_D_QUOTES_ ? + ( + nbacksl & 1 ? + (abcd & 63) | 32 + : + ((abcd & 223) | 128) ^ 16 + ) + : !(abcd & 17) && ini_string[idx_i] == _CONFINI_S_QUOTES_ ? + ( + nbacksl & 1 ? + (abcd & 63) | 32 + : + ((abcd & 223) | 128) ^ 8 + ) + : (abcd & 24) || !is_some_space(ini_string[idx_i], _CONFINI_WITH_EOL_) ? + abcd & 31 + : abcd & 64 ? + (abcd & 223) | 128 + : + (abcd & 95) | 64; + + + if (nbacksl) { + + nbacksl = (abcd & 32 ? nbacksl + 2 : nbacksl + 3) >> 1; + + while (--nbacksl) { + + if (simple_string[idx_s++] != _CONFINI_BACKSLASH_) { + + return _CONFINI_FALSE_; + + } + + } + + } + + if ( + (abcd & 128) || ( + (abcd & 64) && !simple_string[idx_s] + ) + ) { + + idx_i++; + goto si_match; + + } + + if ( + abcd & 64 ? + simple_string[idx_s] != _CONFINI_COLLAPSED_ || + !simple_string[idx_s + 1] + : format.case_sensitive ? + ini_string[idx_i] != simple_string[idx_s] + : + _CONFINI_CHR_CASEFOLD_(ini_string[idx_i]) != + _CONFINI_CHR_CASEFOLD_(simple_string[idx_s]) + ) { + + return _CONFINI_FALSE_; + + } + + idx_s++; + + if (ini_string[idx_i++]) { + + goto si_match; + + } + + return _CONFINI_TRUE_; + +} + + + /** @utility{ini_string_match_ii} **/ +/** + + @brief Compare two INI strings and check whether they match + @param ini_string_a The first INI string unescaped according to + @p format + @param ini_string_b The second INI string unescaped according to + @p format + @param format The format of the INI file + @return A boolean: `true` if the two strings match, `false` otherwise + + This function grants that the result of the comparison between two INI strings + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + printf( + "%s\n", + ini_string_match_ii(my_ini_string_1, my_ini_string_2, format) ? + "They match" + : + "They don't match" + ); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + will always match the result of the _literal_ comparison between the same two + INI strings after these have been parsed by #ini_string_parse() when + `format.do_not_collapse_values` is set to `false`. + + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + ini_string_parse(my_ini_string_1, format); + ini_string_parse(my_ini_string_2, format); + + printf("%s\n", + ini_string_match_ss(my_ini_string_1, my_ini_string_2, format) ? + "They match" + : + "They don't match" + ); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + INI strings are the strings typically dispatched by #load_ini_file(), + #load_ini_path() or #strip_ini_cache(), which may contain quotes and the three + escape sequences `\\`, `\'` and `\"`. + + In order to be suitable for both names and values, **this function always + considers sequences of one or more spaces out of quotes in both strings as + collapsed**, even when `format.do_not_collapse_values` is set to `true`. + + The @p format argument is used for the following fields: + + - `format.case_sensitive` + - `format.no_double_quotes` + - `format.no_single_quotes` + - `format.multiline_nodes` (`INIFORMAT_HAS_NO_ESC()`) + +**/ +bool ini_string_match_ii ( + const char * const ini_string_a, + const char * const ini_string_b, + const IniFormat format +) { + + const _CONFINI_CHARBOOL_ has_escape = !INIFORMAT_HAS_NO_ESC(format); + register _CONFINI_CHARBOOL_ side = 1; + register _CONFINI_CHARBOOL_ turn_allowed = _CONFINI_TRUE_; + uint_least8_t abcd_pair[2]; + const char * ptr_pair[2] = { ini_string_a, ini_string_b }; + size_t nbacksl_pair[2]; + + /* + + Masks `abcd_pair[0]` and `abcd_pair[1]` (7 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes and format supports + escape sequences + FLAG_32 This is a space + FLAG_64 Skip this character + + */ + + abcd_pair[1] = abcd_pair[0] = + 32 | (format.no_double_quotes << 1) | format.no_single_quotes; + + + /* \ /\ + \ */ ii_match: /* \ + \/ ______________________ \ */ + + + nbacksl_pair[side] = 0; + + if (has_escape && *ptr_pair[side] == _CONFINI_BACKSLASH_) { + + for ( + nbacksl_pair[side]++; + *(++ptr_pair[side]) == _CONFINI_BACKSLASH_; + nbacksl_pair[side]++ + ); + + abcd_pair[side] = + nbacksl_pair[side] & 1 ? + (abcd_pair[side] & 31) | 16 + : + abcd_pair[side] & 15; + + if ( + ( + (abcd_pair[side] & 9) || + *ptr_pair[side] != _CONFINI_S_QUOTES_ + ) && ( + (abcd_pair[side] & 6) || + *ptr_pair[side] != _CONFINI_D_QUOTES_ + ) + ) { + + nbacksl_pair[side]++; + + } + + } else { + + abcd_pair[side] = + + !(abcd_pair[side] & 25) && + *ptr_pair[side] == _CONFINI_S_QUOTES_ ? + ((abcd_pair[side] & 111) | 64) ^ 4 + : !(abcd_pair[side] & 22) && + *ptr_pair[side] == _CONFINI_D_QUOTES_ ? + ((abcd_pair[side] & 111) | 64) ^ 8 + : !(abcd_pair[side] & 12) && + is_some_space(*ptr_pair[side], _CONFINI_WITH_EOL_) ? + (abcd_pair[side] & 111) | 96 + : *ptr_pair[side] ? + abcd_pair[side] & 47 + : + abcd_pair[side] & 15; + + + if (abcd_pair[side] & 64) { + + ptr_pair[side]++; + goto ii_match; + + } + + } + + if (side && turn_allowed) { + + side ^= 1; + goto ii_match; + + } + + turn_allowed = _CONFINI_TRUE_; + + if (nbacksl_pair[0] || nbacksl_pair[1]) { + + if (nbacksl_pair[0] >> 1 != nbacksl_pair[1] >> 1) { + + return _CONFINI_FALSE_; + + } + + side = 1; + goto ii_match; + + } + + if ( + ( + abcd_pair[side ^ 1] & 32 ? + !(abcd_pair[side] & 32) + : + abcd_pair[(side ^= 1) ^ 1] & 32 + ) && *ptr_pair[side] + ) { + + if (*ptr_pair[side]++ != _CONFINI_COLLAPSED_) { + + return _CONFINI_FALSE_; + + } + + abcd_pair[side ^ 1] &= 95; + turn_allowed = _CONFINI_FALSE_; + goto ii_match; + + } + + if ( + format.case_sensitive ? + *ptr_pair[0] != *ptr_pair[1] + : + _CONFINI_CHR_CASEFOLD_(*ptr_pair[0]) != + _CONFINI_CHR_CASEFOLD_(*ptr_pair[1]) + ) { + + return _CONFINI_FALSE_; + + } + + if (*ptr_pair[0]) { + + ptr_pair[0]++; + + } + + if (*ptr_pair[1]) { + + ptr_pair[1]++; + + } + + if (*ptr_pair[0] || *ptr_pair[1]) { + + abcd_pair[0] &= 95; + abcd_pair[1] &= 95; + side = 1; + goto ii_match; + + } + + return _CONFINI_TRUE_; + +} + + + /** @utility{ini_array_match} **/ +/** + + @brief Compare two INI arrays and check whether they match + @param ini_string_a The first INI array + @param ini_string_b The second INI array + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @return A boolean: `true` if the two arrays match, `false` otherwise + + This function grants that the result of the comparison between two INI arrays + will always match the the _literal_ comparison between the individual members + of both arrays after these have been parsed, one by one, by #ini_string_parse() + (with `format.do_not_collapse_values` set to `false`). + + This function can be used, with `'.'` as delimiter, to compare section paths. + + INI strings are the strings typically dispatched by #load_ini_file(), + #load_ini_path() or #strip_ini_cache(), which may contain quotes and the three + escape sequences `\\`, `\'` and `\"`. + + In order to be suitable for both names and values, **this function always + considers sequences of one or more spaces out of quotes in both strings as + collapsed**, even when `format.do_not_collapse_values` is set to `true`. + + The @p format argument is used for the following fields: + + - `format.case_sensitive` + - `format.no_double_quotes` + - `format.no_single_quotes` + - `format.multiline_nodes` (`INIFORMAT_HAS_NO_ESC()`) + +**/ +bool ini_array_match ( + const char * const ini_string_a, + const char * const ini_string_b, + const char delimiter, + const IniFormat format +) { + + if (_CONFINI_IS_ESC_CHAR_(delimiter, format)) { + + /* We have no delimiters (array has only one member) */ + return ini_string_match_ii(ini_string_a, ini_string_b, format); + + } + + const _CONFINI_CHARBOOL_ has_escape = !INIFORMAT_HAS_NO_ESC(format); + register _CONFINI_CHARBOOL_ side = 1; + register _CONFINI_CHARBOOL_ turn_allowed = _CONFINI_TRUE_; + uint_least8_t abcd_pair[2]; + size_t nbacksl_pair[2]; + + const char * ptr_pair[2] = { + ini_string_a + ltrim_s(ini_string_a, 0, _CONFINI_WITH_EOL_), + ini_string_b + ltrim_s(ini_string_b, 0, _CONFINI_WITH_EOL_) + }; + + /* + + Masks `abcd_pair[0]` and `abcd_pair[1]` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 We are in an odd sequence of backslashes and format supports + escape sequences + FLAG_32 This is a space + FLAG_64 This is a delimiter + FLAG_128 Skip this character + + */ + + abcd_pair[1] = abcd_pair[0] = + 32 | (format.no_double_quotes << 1) | format.no_single_quotes; + + + /* \ /\ + \ */ delimited_match: /* \ + \/ ______________________ \ */ + + + nbacksl_pair[side] = 0; + + if (has_escape && *ptr_pair[side] == _CONFINI_BACKSLASH_) { + + for ( + nbacksl_pair[side]++; + *(++ptr_pair[side]) == _CONFINI_BACKSLASH_; + nbacksl_pair[side]++ + ); + + abcd_pair[side] = + nbacksl_pair[side] & 1 ? + (abcd_pair[side] & 31) | 16 + : + abcd_pair[side] & 15; + + if ( + ( + (abcd_pair[side] & 9) || *ptr_pair[side] != _CONFINI_S_QUOTES_ + ) && ( + (abcd_pair[side] & 6) || *ptr_pair[side] != _CONFINI_D_QUOTES_ + ) + ) { + + nbacksl_pair[side]++; + + } + + } else { + + abcd_pair[side] = + + !(abcd_pair[side] & 12) && is_some_space( + *ptr_pair[side], + _CONFINI_WITH_EOL_ + ) ? + ( + delimiter || (abcd_pair[side] & 64) ? + (abcd_pair[side] & 239) | 160 + : + (abcd_pair[side] & 111) | 96 + ) + : delimiter && !(abcd_pair[side] & 12) && *ptr_pair[side] == delimiter ? + (abcd_pair[side] & 111) | 96 + : !(abcd_pair[side] & 25) && *ptr_pair[side] == _CONFINI_S_QUOTES_ ? + ((abcd_pair[side] & 175) | 128) ^ 4 + : !(abcd_pair[side] & 22) && *ptr_pair[side] == _CONFINI_D_QUOTES_ ? + ((abcd_pair[side] & 175) | 128) ^ 8 + : *ptr_pair[side] ? + abcd_pair[side] & 47 + : delimiter ? + abcd_pair[side] & 15 + : + (abcd_pair[side] & 79) ^ 64; + + + if (abcd_pair[side] & 128) { + + ptr_pair[side]++; + goto delimited_match; + + } + + } + + if (side && turn_allowed) { + + side ^= 1; + goto delimited_match; + + } + + turn_allowed = _CONFINI_TRUE_; + + if (nbacksl_pair[0] || nbacksl_pair[1]) { + + if (nbacksl_pair[0] >> 1 != nbacksl_pair[1] >> 1) { + + return _CONFINI_FALSE_; + + } + + side = 1; + goto delimited_match; + + } + + if ((abcd_pair[0] ^ abcd_pair[1]) & 64) { + + return _CONFINI_FALSE_; + + } + + if ( + !( + abcd_pair[side ^ 1] & 32 ? + abcd_pair[side] & 96 + : + (abcd_pair[(side ^= 1) ^ 1] & 96) ^ 32 + ) && *ptr_pair[side] + ) { + + if (*ptr_pair[side]++ != _CONFINI_COLLAPSED_) { + + return _CONFINI_FALSE_; + + } + + abcd_pair[side ^ 1] &= 223; + turn_allowed = _CONFINI_FALSE_; + goto delimited_match; + + } + + if (~abcd_pair[0] & 64) { + + if ( + format.case_sensitive ? + *ptr_pair[0] != *ptr_pair[1] + : + _CONFINI_CHR_CASEFOLD_(*ptr_pair[0]) != + _CONFINI_CHR_CASEFOLD_(*ptr_pair[1]) + ) { + + return _CONFINI_FALSE_; + + } + + abcd_pair[0] &= 223; + abcd_pair[1] &= 223; + + } + + if (*ptr_pair[0]) { + + ptr_pair[0]++; + + } + + if (*ptr_pair[1]) { + + ptr_pair[1]++; + + } + + if (*ptr_pair[0] || *ptr_pair[1]) { + + side = 1; + goto delimited_match; + + } + + return _CONFINI_TRUE_; + +} + + + /** @utility{ini_unquote} **/ +/** + + @brief Unescape `\'`, `\"`, and `\\` and remove all unescaped quotes + (when single/double quotes are considered metacharacters in + respect to the format given) + @param ini_string The string to be unescaped + @param format The format of the INI file + @return The new length of the string + + This function is very similar to #ini_string_parse(), except that does not + bother collapsing the sequences of more than one space that might result from + removing empty quotes. Its purpose is to be used to parse key and section names, + since these are always dispatched as already collapsed. In order to parse + values, or array parts listed in values, please use #ini_string_parse(). + + If you only need to compare @p ini_string with another string, consider to use + #ini_string_match_si() and #ini_string_match_ii() instead of parsing the former + and perform a simple comparison afterwards. These two functions are in fact able + to check directly for equality between unparsed INI strings without actually + modifiyng them. + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). If the string does not contain quotes, or if quotes are + considered to be normal characters, no changes will be made. + + @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is + no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus + the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE. + + The @p format argument is used for the following fields: + + - `format.no_single_quotes` + - `format.no_double_quotes` + - `format.multiline_nodes` (`INIFORMAT_HAS_NO_ESC()`) + + @include topics/ini_string_parse.c + +**/ +size_t ini_unquote ( + char * const ini_string, + const IniFormat format +) { + + if (INI_IS_IMPLICIT_SUBSTR(ini_string)) { + + return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string; + + } + + register size_t idx = 0; + + if (INIFORMAT_HAS_NO_ESC(format)) { + + /* + + There are no escape sequences... I will just return the length of the + string... + + */ + + while (ini_string[idx++]); + + return idx - 1; + + } + + size_t lshift = 0, nbacksl = 0; + + /* + + Mask `abcd` (6 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Unescaped single quotes are odd right now + FLAG_8 Unescaped double quotes are odd right now + FLAG_16 This is an unescaped single quote and format supports single + quotes + FLAG_32 This is an unescaped double quote and format supports double + quotes + + */ + + for ( + + register uint_least8_t + abcd = (format.no_double_quotes << 1) | format.no_single_quotes; + + ini_string[idx]; + + idx++ + + ) { + + abcd = + + !(abcd & 6) && ini_string[idx] == _CONFINI_D_QUOTES_ ? + (abcd & 47) | 32 + : !(abcd & 9) && ini_string[idx] == _CONFINI_S_QUOTES_ ? + (abcd & 31) | 16 + : + abcd & 15; + + + if (!(nbacksl & 1) && (abcd & 48)) { + + abcd ^= (abcd >> 2) & 12; + lshift++; + continue; + + } + + if (ini_string[idx] == _CONFINI_BACKSLASH_) { + + nbacksl++; + + } else { + + if ((nbacksl & 1) && (abcd & 48)) { + + lshift++; + + } + + lshift += nbacksl >> 1; + nbacksl = 0; + + } + + if (lshift) { + + ini_string[idx - lshift] = ini_string[idx]; + + } + + } + + lshift += nbacksl >> 1; + + for (idx -= lshift; ini_string[idx]; ini_string[idx++] = '\0'); + + return idx - lshift; + +} + + + /** @utility{ini_string_parse} **/ +/** + + @brief Unescape `\'`, `\"`, and `\\` and remove all unescaped quotes + (when single/double quotes are considered metacharacters in + respect to the format given); if the format allows it, sequences + of one or more spaces out of quotes will be collapsed + @param ini_string The string to be unescaped + @param format The format of the INI file + @return The new length of the string + + This function is meant to be used to parse values. In order to parse key and + section names please use #ini_unquote(). + + If you only need to compare @p ini_string with another string, consider to use + #ini_string_match_si() and #ini_string_match_ii() instead of parsing the former + and perform a simple comparison afterwards. These two functions are in fact able + to check directly for equality between unparsed INI strings without actually + modifying them. + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). If `format.do_not_collapse_values` is set to non-zero, spaces + surrounding empty quotes will be collapsed together with the latter. + + @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is + no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus + the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE. + + The @p format argument is used for the following fields: + + - `format.no_single_quotes` + - `format.no_double_quotes` + - `format.multiline_nodes` (`INIFORMAT_HAS_NO_ESC()`) + - `format.do_not_collapse_values` + + @include topics/ini_string_parse.c + +**/ +size_t ini_string_parse (char * const ini_string, const IniFormat format) { + + if (INI_IS_IMPLICIT_SUBSTR(ini_string)) { + + return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string; + + } + + /* + + Mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Do not collapse spaces within members (const) + FLAG_8 Unescaped single quotes are odd right now + FLAG_16 Unescaped double quotes are odd right now + FLAG_32 This is an *escaped* single/double quote and format supports + single/double quotes + FLAG_64 This is a space + FLAG_128 Skip this character + + */ + + register uint_least8_t abcd = + (format.do_not_collapse_values ? 68 : 64) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + size_t idx, lshift; + + if (format.multiline_nodes == INI_NO_MULTILINE) { + + switch (abcd) { + + case 64 | 2 | 1 : + + /* + + There are no escape sequences, but spaces might still need to + be collapsed. + + */ + + for (idx = 0, lshift = 0; ini_string[idx]; idx++) { + + abcd = + !is_some_space(ini_string[idx], _CONFINI_WITH_EOL_) ? + 3 + : abcd & 64 ? + 195 + : + 67; + + if (abcd & 128) { + + lshift++; + + } else { + + ini_string[idx - lshift] = + abcd & 64 ? + _CONFINI_COLLAPSED_ + : + ini_string[idx]; + + } + + } + + for ( + + idx -= + (abcd & 64) && lshift < idx ? + ++lshift + : + lshift; + + ini_string[idx]; + + ini_string[idx++] = '\0' + + ); + + return idx - lshift; + + case 64 | 4 | 2 | 1 : + + /* + + There are no escape sequences and spaces do not need to be + collapsed, but left and right trim might still be necessary... + + */ + + return rtrim_h( + ini_string, + ltrim_hh(ini_string, 0, _CONFINI_WITH_EOL_), + _CONFINI_WITH_EOL_ + ); + + } + + } + + /* + + There might be escape sequences... + + */ + + size_t nbacksl = 0; + + for (idx = lshift = 0; ini_string[idx]; idx++) { + + abcd = + + !(abcd & 10) && ini_string[idx] == _CONFINI_D_QUOTES_ ? + ( + nbacksl & 1 ? + (abcd & 63) | 32 + : + ((abcd & 223) | 128) ^ 16 + ) + : !(abcd & 17) && ini_string[idx] == _CONFINI_S_QUOTES_ ? + ( + nbacksl & 1 ? + (abcd & 63) | 32 + : + ((abcd & 223) | 128) ^ 8 + ) + : (abcd & 28) || !is_some_space(ini_string[idx], _CONFINI_WITH_EOL_) ? + abcd & 31 + : abcd & 64 ? + (abcd & 223) | 128 + : + (abcd & 95) | 64; + + + if (abcd & 128) { + + lshift++; + continue; + + } + + if (ini_string[idx] == _CONFINI_BACKSLASH_) { + + nbacksl++; + + } else { + + if (abcd & 32) { + + lshift++; + + } + + lshift += nbacksl >> 1; + nbacksl = 0; + + } + + ini_string[idx - lshift] = + abcd & 64 ? + _CONFINI_COLLAPSED_ + : + ini_string[idx]; + + } + + lshift += nbacksl >> 1; + + for ( + + idx -= + (abcd & 64) && lshift < idx ? + ++lshift + : + lshift; + + ini_string[idx]; + + ini_string[idx++] = '\0' + + ); + + return + (abcd & 28) ^ 4 ? + idx - lshift + : + rtrim_h(ini_string, idx - lshift, _CONFINI_WITH_EOL_); + +} + + + /** @utility{ini_array_get_length} **/ +/** + + @brief Get the length of a stringified INI array in number of members + @param ini_string The stringified array (it can be `NULL`) + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @return The length of the INI array in number of members + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + +**/ +size_t ini_array_get_length ( + const char * const ini_string, + const char delimiter, + const IniFormat format +) { + + if (!ini_string) { + + return 0; + + } + + if (_CONFINI_IS_ESC_CHAR_(delimiter, format)) { + + /* We have no delimiters (array has only one member) */ + return 1; + + } + + /* + + Mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Delimiter is not any space (const) + FLAG_8 Unescaped single quotes are odd right now + FLAG_16 Unescaped double quotes are odd right now + FLAG_32 We are in an odd sequence of backslashes + FLAG_64 This is a space + FLAG_128 This is a delimiter + + */ + + register uint_least8_t abcd = + (delimiter ? 64 : 68) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + size_t counter = 0; + + for (size_t idx = 0; ini_string[idx]; idx++) { + + /* Revision #1 */ + + abcd = + + !(abcd & 28) && ini_string[idx] == delimiter ? + (abcd & 159) | 128 + : !(abcd & 24) && is_some_space(ini_string[idx], _CONFINI_WITH_EOL_) ? + ( + (abcd & 68) ^ 4 ? + (abcd & 95) | 64 + : + (abcd & 223) | 192 + ) + : ini_string[idx] == _CONFINI_BACKSLASH_ ? + (abcd & 63) ^ 32 + : !(abcd & 42) && ini_string[idx] == _CONFINI_D_QUOTES_ ? + (abcd & 31) ^ 16 + : !(abcd & 49) && ini_string[idx] == _CONFINI_S_QUOTES_ ? + (abcd & 31) ^ 8 + : + abcd & 31; + + + if (abcd & 128) { + + counter++; + + } + + } + + return + !counter || (~abcd & 68) ? + counter + 1 + : + counter; + +} + + + /** @utility{ini_array_foreach} **/ +/** + + @brief Call a custom function for each member of a stringified INI + array, without modifying the content of the buffer -- useful for + read-only (`const`) stringified arrays + @param ini_string The stringified array (it can be `NULL`) + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @param f_foreach The function that will be invoked for each array + member + @param user_data A custom argument, or `NULL` + @return Zero for success, otherwise an error code (see `enum` + #ConfiniInterruptNo) + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). + + The user given function @p f_foreach (see #IniSubstrHandler data type) will be + invoked with six arguments: `ini_string`, `memb_offset` (the offset of the + member in bytes), `memb_length` (the length of the member in bytes), `memb_num` + (the offset of the member in number of members), `format` (the format of the INI + file), `user_data` (the custom argument @p user_data previously passed). If + @p f_foreach returns a non-zero value the function #ini_array_foreach() will be + interrupted. + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + + Possible return values are: #CONFINI_SUCCESS, #CONFINI_FEINTR. + + @include topics/ini_array_foreach.c + +**/ +int ini_array_foreach ( + const char * const ini_string, + const char delimiter, + const IniFormat format, + const IniSubstrHandler f_foreach, + void * const user_data +) { + + if (!ini_string) { + + return CONFINI_SUCCESS; + + } + + /* + + Mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Delimiter is not any space (const) + FLAG_8 Unescaped single quotes are odd until now + FLAG_16 Unescaped double quotes are odd until now + FLAG_32 We are in an odd sequence of backslashes + FLAG_64 This is not a delimiter + FLAG_128 Stop the loop + + */ + + register size_t idx; + size_t offs = ltrim_s(ini_string, 0, _CONFINI_WITH_EOL_); + + if (_CONFINI_IS_ESC_CHAR_(delimiter, format)) { + + /* We have no delimiters (array has only one member) */ + + idx = 0; + + while (ini_string[idx++]); + + return f_foreach( + ini_string, + offs, + rtrim_s(ini_string + offs, idx - offs - 1, _CONFINI_WITH_EOL_), + 0, + format, + user_data + ) ? CONFINI_FEINTR : CONFINI_SUCCESS; + + } + + register uint_least8_t abcd = + (delimiter ? 4 : 0) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + size_t memb_num = 0; + + idx = offs; + + do { + + abcd = + + ( + delimiter ? + ini_string[idx] == delimiter + : + is_some_space(ini_string[idx], _CONFINI_WITH_EOL_) + ) ? + abcd & 159 + : ini_string[idx] == _CONFINI_BACKSLASH_ ? + (abcd | 64) ^ 32 + : !(abcd & 42) && ini_string[idx] == _CONFINI_D_QUOTES_ ? + ((abcd & 223) | 64) ^ 16 + : !(abcd & 49) && ini_string[idx] == _CONFINI_S_QUOTES_ ? + ((abcd & 223) | 64) ^ 8 + : ini_string[idx] ? + (abcd & 223) | 64 + : + 128; + + + if (!(abcd & 88)) { + + if ( + f_foreach( + ini_string, + offs, + rtrim_s(ini_string + offs, idx - offs, _CONFINI_WITH_EOL_), + memb_num++, + format, + user_data + ) + ) { + + return CONFINI_FEINTR; + + } + + offs = + abcd & 128 ? + idx + 1 + : + ltrim_s(ini_string, idx + 1, _CONFINI_WITH_EOL_); + + } + + idx = abcd & 216 ? idx + 1 : offs; + + } while ( + !(abcd & 128) && ( + (abcd & 92) || ini_string[idx] + ) + ); + + return CONFINI_SUCCESS; + +} + + + /** @utility{ini_array_shift} **/ +/** + + @brief Shift the location pointed by @p ini_strptr to the next member + of the INI array (without modifying the content of the buffer), + or to `NULL` if the INI array has no more members -- useful for + read-only (`const`) stringified arrays + @param ini_strptr The memory location of the stringified array -- + it cannot be `NULL`, but it can point to `NULL` + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @return The length of the array member that has been left behind + + Usually @p ini_strptr comes from an #IniDispatch (but any other string may be + used as well). + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + + @include topics/ini_array_shift.c + +**/ +size_t ini_array_shift ( + const char ** const ini_strptr, + const char delimiter, + const IniFormat format +) { + + size_t toklen = 0; + + if (*ini_strptr && !_CONFINI_IS_ESC_CHAR_(delimiter, format)) { + + if (!delimiter) { + + toklen = ltrim_s(*ini_strptr, 0, _CONFINI_WITH_EOL_); + + } + + toklen += get_metachar_pos(*ini_strptr + toklen, delimiter, format); + *ini_strptr += toklen; + toklen = rtrim_s(*ini_strptr - toklen, toklen, _CONFINI_WITH_EOL_); + + if (**ini_strptr) { + + *ini_strptr += ltrim_s(*ini_strptr, 1, _CONFINI_WITH_EOL_); + + if (delimiter || **ini_strptr) { + + return toklen; + + } + + } + + } + + *ini_strptr = (char *) 0; + return toklen; + +} + + + /** @utility{ini_array_collapse} **/ +/** + + @brief Compress the distribution of the data in a stringified INI array + by removing all the white spaces that surround its delimiters, + empty quotes, collapsable spaces, etc + @param ini_string The stringified array + @param delimiter The delimiter between the array members -- + if zero (`INI_ANY_SPACE`) any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @return The new length of the stringified array + + Out of quotes similar to ECMAScript `ini_string.replace(new + RegExp("^\\s+|\\s*(?:(" + delimiter + ")\\s*|($))", "g"), "$1$2")`. If + #INI_ANY_SPACE (`0`) is used as delimiter, one or more different spaces + (`/[\t \v\f\n\r]+/`) will be always collapsed to one space, independently of + what the format says. + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). + + This function can be useful before invoking `memcpy()` using @p ini_string as + source, when saving memory is a priority. + + The @p format argument is used for the following fields: + + - `format.no_single_quotes` + - `format.no_double_quotes` + - `format.do_not_collapse_values` + - `format.preserve_empty_quotes` + + Examples: + + 1. Using comma as delimiter: + - Before: ` first , second + , third , etc. ` + - After: `first,second,third,etc.` + 2. Using `INI_ANY_SPACE` as delimiter: + - Before: ` first second + third etc. ` + - After: `first second third etc.` + + @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is + no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus + the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE. + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + + @include topics/ini_array_collapse.c + + @note The actual space occupied by the array might get reduced further after + each member is parsed by #ini_string_parse(). + +**/ +size_t ini_array_collapse ( + char * const ini_string, + const char delimiter, + const IniFormat format +) { + + if (INI_IS_IMPLICIT_SUBSTR(ini_string)) { + + return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string; + + } + + if (_CONFINI_IS_ESC_CHAR_(delimiter, format)) { + + /* We have no delimiters (array has only one member) */ + + switch ( + (format.preserve_empty_quotes << 1) | + format.do_not_collapse_values + ) { + + case 0: return collapse_everything(ini_string, format); + case 1: return collapse_empty_quotes(ini_string, format); + case 2: return collapse_spaces(ini_string, format); + + case 3: + + return rtrim_h( + ini_string, + ltrim_hh(ini_string, 0, _CONFINI_WITH_EOL_), + _CONFINI_WITH_EOL_ + ); + + } + + } + + /* + + Mask `abcd` (16 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Do not collapse spaces within members (const) + FLAG_8 Preserve empty quotes (const) + FLAG_16 Any space is delimiter (const) + FLAG_32 Unescaped single quotes are odd right now + FLAG_64 Unescaped double quotes are odd right now + FLAG_128 We are in an odd sequence of backslashes + FLAG_256 This is *not* a delimiter out of quotes + FLAG_512 This is *not* a space out of quotes + FLAG_1024 These are some quotes + FLAG_2048 These are some quotes or among the last spaces are some empty + quotes + FLAG_4096 Save current `idx_d` in `fallback` + FLAG_8192 Restore `idx_d` from `fallback` before writing + FLAG_16384 Decrease `idx_d` before writing + FLAG_32768 Keep increasing `idx_d` after writing + + */ + + size_t idx_s = 0, idx_d = 0, fallback = 0; + + register uint_least16_t abcd = + (delimiter ? 0 : 16) | + (format.preserve_empty_quotes << 3) | + (format.do_not_collapse_values << 2) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + + for (; ini_string[idx_s]; idx_s++) { + + /* Revision #1 */ + + abcd = + + !(abcd & 112) && ini_string[idx_s] == delimiter ? + ( + (abcd & 536) && ((abcd & 1560) ^ 8) && + ((abcd & 1560) ^ 1544) && ((abcd & 1304) ^ 1032) ? + (abcd & 33407) | 33280 + : + (abcd & 41599) | 41472 + ) + : !(abcd & 96) && is_some_space(ini_string[idx_s], _CONFINI_WITH_EOL_) ? + ( + !((abcd & 1816) ^ 1800) ? + (abcd & 43391) | 40960 + : !(~abcd & 1560) ? + (abcd & 41087) | 40960 + : !((abcd & 536) ^ 528) || !((abcd & 1560) ^ 536) || + !((abcd & 1560) ^ 1048) ? + (abcd & 32895) | 32768 + : !(abcd & 540) || !((abcd & 1564) ^ 8) || + !((abcd & 536) ^ 16) || !((abcd & 1560) ^ 24) ? + abcd & 2431 + : ((abcd & 540) ^ 4) && ((abcd & 796) ^ 12) && + ((abcd & 1564) ^ 12) && ((abcd & 1308) ^ 1032) ? + (abcd & 39295) | 36864 + : + (abcd & 35199) | 32768 + ) + : !(abcd & 193) && ini_string[idx_s] == _CONFINI_S_QUOTES_ ? + ( + !((abcd & 3896) ^ 8) ? + (abcd & 44927) | 44064 + : !((abcd & 3896) ^ 2056) ? + (abcd & 36735) | 36128 + : !((abcd & 1056) ^ 32) ? + (abcd & 33631) | 33536 + : !(abcd & 40) || !(~abcd & 1064) ? + ((abcd & 36735) | 35840) ^ 32 + : ((abcd & 1064) ^ 1032) && ((abcd & 1064) ^ 1056) ? + (abcd & 40831) | 39968 + : + ((abcd & 20351) | 19456) ^ 32 + ) + : !(abcd & 162) && ini_string[idx_s] == _CONFINI_D_QUOTES_ ? + ( + !((abcd & 3928) ^ 8) ? + (abcd & 44927) | 44096 + : !((abcd & 3928) ^ 2056) ? + (abcd & 36735) | 36160 + : !((abcd & 1088) ^ 64) ? + (abcd & 33599) | 33536 + : !(abcd & 72) || !(~abcd & 1096) ? + ((abcd & 36735) | 35840) ^ 64 + : ((abcd & 1096) ^ 1088) && ((abcd & 1096) ^ 1032) ? + (abcd & 40831) | 40000 + : + ((abcd & 20351) | 19456) ^ 64 + ) + : ini_string[idx_s] == _CONFINI_BACKSLASH_ ? + ( + (abcd & 888) && ((abcd & 1144) ^ 1032) && + ((abcd & 1144) ^ 1048) && ((abcd & 2936) ^ 8) ? + ((abcd & 33791) | 33536) ^ 128 + : + ((abcd & 41983) | 41728) ^ 128 + ) + : (abcd & 888) && ((abcd & 1144) ^ 1032) && + ((abcd & 1144) ^ 1048) && ((abcd & 2936) ^ 8) ? + (abcd & 33663) | 33536 + : + (abcd & 41855) | 41728; + + + ini_string[ + abcd & 16384 ? + --idx_d + : abcd & 8192 ? + (idx_d = fallback) + : abcd & 4096 ? + (fallback = idx_d) + : + idx_d + ] = + (abcd & 1636) && ((abcd & 1392) ^ 16) ? + ini_string[idx_s] + : + _CONFINI_COLLAPSED_; + + + if (abcd & 32768) { + + idx_d++; + + } + + } + + for ( + + idx_s = + ((abcd & 16) && !idx_d) || (!(~abcd & 1040) && idx_d < 4) ? + (idx_d = 0) + : !(abcd & 536) || !(~abcd & 1544) || !((abcd & 1560) ^ 8) || + !((abcd & 1304) ^ 1032) ? + (idx_d = fallback) + : !((abcd & 1624) ^ 1104) || !((abcd & 1592) ^ 1072) ? + (idx_d -= 2) + : ((abcd & 1552) ^ 16) && ((abcd & 632) ^ 16) && + ((abcd & 1624) ^ 1616) && ((abcd & 1592) ^ 1584) ? + idx_d + : + --idx_d; + + ini_string[idx_s]; + + ini_string[idx_s++] = '\0' + + ); + + return idx_d; + +} + + + /** @utility{ini_array_break} **/ +/** + + @brief Replace the first delimiter found (together with the spaces that + surround it) with `\0` + @param ini_string The stringified array (it can be `NULL`) + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @return A pointer to the remaining INI array or `NULL` if the remaining + array is empty + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). + + Similarly to `strtok_r()` this function can be used only once for a given + string. + + @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is + no-op and will return `NULL`. + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + + @include topics/ini_array_break.c + +**/ +char * ini_array_break ( + char * const ini_string, + const char delimiter, + const IniFormat format +) { + + if (ini_string && !INI_IS_IMPLICIT_SUBSTR(ini_string)) { + + char * remnant; + + if (_CONFINI_IS_ESC_CHAR_(delimiter, format)) { + + /* We have no delimiters (array has only one member) */ + + remnant = ini_string; + + while (*remnant++); + + rtrim_h(ini_string, remnant - ini_string - 1, _CONFINI_WITH_EOL_); + + } else { + + remnant = ini_string + get_metachar_pos(ini_string, delimiter, format); + + if (*remnant) { + + *remnant = '\0'; + rtrim_h(ini_string, remnant - ini_string, _CONFINI_WITH_EOL_); + remnant += ltrim_h(remnant, 1, _CONFINI_WITH_EOL_); + + if (delimiter || *remnant) { + + return remnant; + + } + + } + + } + + } + + return (char *) 0; + +} + + + /** @utility{ini_array_release} **/ +/** + + @brief Replace the first delimiter found (together with the spaces that + surround it) with `\0`, then shifts the location pointed by + @p ini_strptr to the next member of the INI array, or to `NULL` + if the INI array has no more members + @param ini_strptr The memory location of the stringified array -- + it cannot be `NULL`, but it can point to `NULL` + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @return The array member that has been released + + Usually @p ini_strptr comes from an #IniDispatch (but any other string may be + used as well). + + Similarly to `strtok_r()` this function can be used only once for a given + string. + + @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is + no-op and will set @p ini_strptr to `NULL`. + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + + @include topics/ini_array_release.c + +**/ +char * ini_array_release ( + char ** const ini_strptr, + const char delimiter, + const IniFormat format +) { + + char * const token = *ini_strptr; + + if ( + token && + !INI_IS_IMPLICIT_SUBSTR(token) && + !_CONFINI_IS_ESC_CHAR_(delimiter, format) + ) { + + *ini_strptr += get_metachar_pos(*ini_strptr, delimiter, format); + + if (**ini_strptr) { + + **ini_strptr = '\0'; + rtrim_h(token, *ini_strptr - token, _CONFINI_WITH_EOL_); + *ini_strptr += ltrim_h(*ini_strptr, 1, _CONFINI_WITH_EOL_); + + if (delimiter || **ini_strptr) { + + return token; + + } + + } + + } + + *ini_strptr = (char *) 0; + return token; + +} + + + /** @utility{ini_array_split} **/ +/** + + @brief Split a stringified INI array into NUL-separated members and + call a custom function for each member + @param ini_string The stringified array (it cannot be `NULL`) + @param delimiter The delimiter between the array members -- if + zero (see #INI_ANY_SPACE), any space is + delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) + @param format The format of the INI file + @param f_foreach The function that will be invoked for each array + member + @param user_data A custom argument, or `NULL` + @return Zero for success, otherwise an error code (see `enum` + #ConfiniInterruptNo) + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). + + The user given function @p f_foreach (see #IniStrHandler data type) will be + invoked with five arguments: `member` (the member of the array), `memb_length` + (the length of the member in bytes), `memb_num` (the offset of the member in + number of members), `format` (the format of the INI file), `user_data` (the + custom argument @p user_data previously passed). If @p f_foreach returns a + non-zero value the function #ini_array_split() will be interrupted. + + Similarly to `strtok_r()` this function can be used only once for a given + string. + + @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE or is `NULL` this + function is no-op and will return an error code. + + @note If @p delimiter matches a metacharacter within the format given (`'\\'`, + `'\''` or `'\"'`), its role as metacharacter will have higher priority + than its role as delimiter (i.e., the array will have no delimiters and + will contain only one member). + + Possible return values are: #CONFINI_SUCCESS, #CONFINI_EROADDR, #CONFINI_FEINTR. + + @include topics/ini_array_split.c + +**/ +int ini_array_split ( + char * const ini_string, + const char delimiter, + const IniFormat format, + const IniStrHandler f_foreach, + void * const user_data +) { + + if (!ini_string || INI_IS_IMPLICIT_SUBSTR(ini_string)) { + + return CONFINI_EROADDR; + + } + + /* + + Mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Delimiter is not any space (const) + FLAG_8 Unescaped single quotes are odd until now + FLAG_16 Unescaped double quotes are odd until now + FLAG_32 We are in an odd sequence of backslashes + FLAG_64 This is not a delimiter + FLAG_128 Stop the loop + + */ + + register size_t idx; + size_t offs = ltrim_h(ini_string, 0, _CONFINI_WITH_EOL_); + + if (_CONFINI_IS_ESC_CHAR_(delimiter, format)) { + + /* We have no delimiters (array has only one member) */ + + idx = 0; + + while (ini_string[idx++]); + + return f_foreach( + ini_string + offs, + rtrim_h(ini_string + offs, idx - offs - 1, _CONFINI_WITH_EOL_), + 0, + format, + user_data + ) ? CONFINI_FEINTR : CONFINI_SUCCESS; + + } + + register uint_least8_t + abcd = + (delimiter ? 4 : 0) | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + size_t memb_num = 0; + + idx = offs; + + do { + + abcd = + + ( + delimiter ? + ini_string[idx] == delimiter + : + is_some_space(ini_string[idx], _CONFINI_WITH_EOL_) + ) ? + abcd & 159 + : ini_string[idx] == _CONFINI_BACKSLASH_ ? + (abcd | 64) ^ 32 + : !(abcd & 42) && ini_string[idx] == _CONFINI_D_QUOTES_ ? + ((abcd & 223) | 64) ^ 16 + : !(abcd & 49) && ini_string[idx] == _CONFINI_S_QUOTES_ ? + ((abcd & 223) | 64) ^ 8 + : ini_string[idx] ? + (abcd & 223) | 64 + : + 128; + + + if (!(abcd & 88)) { + + ini_string[idx] = '\0'; + + if ( + f_foreach( + ini_string + offs, + rtrim_h(ini_string + offs, idx - offs, _CONFINI_WITH_EOL_), + memb_num++, + format, + user_data + ) + ) { + + return CONFINI_FEINTR; + + } + + offs = + abcd & 128 ? + idx + 1 + : + ltrim_h(ini_string, idx + 1, _CONFINI_WITH_EOL_); + + } + + idx = abcd & 216 ? idx + 1 : offs; + + } while ( + !(abcd & 128) && ( + (abcd & 92) || ini_string[idx] + ) + ); + + return CONFINI_SUCCESS; + +} + + +/** + + @brief Set the value of the global variable + #INI_GLOBAL_LOWERCASE_MODE + @deprecated Deprecated since version 1.15.0 (it will be removed in version + 2.0.0) + @param lowercase The new value + @return Nothing + + If @p lowercase is `true`, key and section names in case-insensitive INI formats + will be dispatched lowercase, verbatim otherwise (default value: `true`). + + @warning This function changes the value of one or more global variables. In + order to be thread-safe this function should be used only once at + beginning of execution, or otherwise a mutex logic must be + introduced. + +**/ +void ini_global_set_lowercase_mode ( + const bool lowercase +) { + + INI_GLOBAL_LOWERCASE_MODE = lowercase; + +} + + + /** @utility{ini_global_set_implicit_value} **/ +/** + + @brief Set the value to be to be assigned to implicit keys + @param implicit_value The string to be used as implicit value + (usually `"YES"`, `"TRUE"`, or `"ON"`, or + any other string; it can be `NULL`) + @param implicit_v_len The length of @p implicit_value (without + counting the NUL terminator; use `0` for + both an empty string and `NULL`) + @return Nothing + + @warning This function changes the value of one or more global variables. In + order to be thread-safe this function should be used only once at + beginning of execution, or otherwise a mutex logic must be + introduced. + + @include topics/ini_global_set_implicit_value.c + +**/ +void ini_global_set_implicit_value ( + char * const implicit_value, + const size_t implicit_v_len +) { + + INI_GLOBAL_IMPLICIT_VALUE = implicit_value; + INI_GLOBAL_IMPLICIT_V_LEN = implicit_v_len; + +} + + + /** @utility{ini_fton} **/ +/** + + @brief Calculate the #IniFormatNum of an #IniFormat + @param source The #IniFormat to compute + @return The unique unsigned integer that identifies the format given + +**/ +IniFormatNum ini_fton ( + const IniFormat source +) { + + #define __INIFORMAT_ID__(NAME, OFFSET, SIZE, DEFVAL) (source.NAME << OFFSET) | + + return INIFORMAT_TABLE_AS(__INIFORMAT_ID__) 0; + + #undef __INIFORMAT_ID__ + +} + + + /** @utility{ini_ntof} **/ +/** + + @brief Construct a new #IniFormat according to an #IniFormatNum + @param format_num The #IniFormatNum to parse + @return The new #IniFormat constructed + + @note If @p format_num `>` `16777215` it will be truncated to 24 bits. + +**/ +IniFormat ini_ntof ( + const IniFormatNum format_num +) { + + #define __MAX_1_BITS__ 1 + #define __MAX_2_BITS__ 3 + #define __MAX_3_BITS__ 7 + #define __MAX_4_BITS__ 15 + #define __MAX_5_BITS__ 31 + #define __MAX_6_BITS__ 63 + #define __MAX_7_BITS__ 127 + #define __MAX_8_BITS__ 255 + #define __INIFORMAT_PROPERTIES__(NAME, OFFSET, SIZE, DEFVAL) \ + (unsigned char) ((format_num >> OFFSET) & __MAX_##SIZE##_BITS__), + + return (IniFormat) { INIFORMAT_TABLE_AS(__INIFORMAT_PROPERTIES__) }; + + #undef __INIFORMAT_PROPERTIES__ + #undef __MAX_8_BITS__ + #undef __MAX_7_BITS__ + #undef __MAX_6_BITS__ + #undef __MAX_5_BITS__ + #undef __MAX_4_BITS__ + #undef __MAX_3_BITS__ + #undef __MAX_2_BITS__ + #undef __MAX_1_BITS__ + +} + + + /** @utility{ini_get_bool} **/ +/** + + @brief Check whether a simple string matches one of the booleans listed + in the private constant #INI_BOOLEANS (case-insensitive) + @param simple_string A string to check (it can be `NULL`) + @param when_fail The value that is returned if no matching + boolean is found + @return The matching boolean (`0` or `1`) or @p when_fail if + @p simple_string does not contain a valid INI boolean + + @include miscellanea/typed_ini.c + +**/ +int ini_get_bool ( + const char * const simple_string, + const int when_fail +) { + + if (!simple_string) { + + return when_fail; + + } + + register _CONFINI_CHARBOOL_ bool_idx; + register size_t pair_idx, chr_idx; + + for (pair_idx = 0; pair_idx < _CONFINI_BOOLLEN_; pair_idx++) { + + bool_idx = 0; + + do { + + chr_idx = 0; + + while ( + _CONFINI_CHR_CASEFOLD_(simple_string[chr_idx]) == + INI_BOOLEANS[pair_idx][bool_idx][chr_idx] + ) { + + if (!simple_string[chr_idx++]) { + + return (int) bool_idx; + + } + + } + + bool_idx ^= 1; + + } while (bool_idx); + + } + + return when_fail; + +} + + + /** @utility{ini_get_bool_i} **/ +/** + + @brief Check whether an INI string matches one of the booleans listed + in the private constant #INI_BOOLEANS (case-insensitive) + @param ini_string A string to check (it can be `NULL`) + @param when_fail The value that is returned if no matching + boolean is found + @param format The format of the INI file + @return The matching boolean (`0` or `1`) or @p when_fail if + @p ini_string does not contain a valid INI boolean + + Usually @p ini_string comes from an #IniDispatch (but any other string may be + used as well). + + The @p format argument is used for the following fields: + + - `format.no_double_quotes` + - `format.no_single_quotes` + + @include miscellanea/typed_ini.c + +**/ +int ini_get_bool_i ( + const char * const ini_string, + const int when_fail, + const IniFormat format +) { + + if (!ini_string) { + + return when_fail; + + } + + register size_t chr_idx_i; + + /* + + Mask `abcd` (8 bits used): + + FLAG_1 Single quotes are not metacharacters (const) + FLAG_2 Double quotes are not metacharacters (const) + FLAG_4 Format supports escape sequences (const) + FLAG_8 Unescaped single quotes are odd right now + FLAG_16 Unescaped double quotes are odd right now + FLAG_32 This is an escaped single/double quote in a format that supports + single/double quotes + FLAG_64 This is a space + FLAG_128 Skip this character + + */ + + register uint_least8_t abcd = + INIFORMAT_HAS_NO_ESC(format) ? + 67 + : + 68 | + (format.no_double_quotes << 1) | + format.no_single_quotes; + + register _CONFINI_CHARBOOL_ bool_idx; + size_t pair_idx, chr_idx_s, nbacksl; + + for (pair_idx = 0; pair_idx < _CONFINI_BOOLLEN_; pair_idx++) { + + bool_idx = 0; + + + /* \ /\ + \ */ pair_match: /* \ + \/ ______________________ \ */ + + + abcd = (abcd & 7) | 64; + chr_idx_s = chr_idx_i = nbacksl = 0; + + do { + + if ((abcd & 4) && ini_string[chr_idx_i] == _CONFINI_BACKSLASH_) { + + for ( + abcd &= 63, nbacksl++; + ini_string[++chr_idx_i] == _CONFINI_BACKSLASH_; + nbacksl++ + ); + + } + + /* Keep this algorithm identical to #ini_string_match_si() */ + + abcd = + + !(abcd & 10) && ini_string[chr_idx_i] == _CONFINI_D_QUOTES_ ? + ( + nbacksl & 1 ? + (abcd & 63) | 32 + : + ((abcd & 223) | 128) ^ 16 + ) + : !(abcd & 17) && ini_string[chr_idx_i] == _CONFINI_S_QUOTES_ ? + ( + nbacksl & 1 ? + (abcd & 63) | 32 + : + ((abcd & 223) | 128) ^ 8 + ) + : (abcd & 24) || !is_some_space( + ini_string[chr_idx_i], + _CONFINI_WITH_EOL_ + ) ? + abcd & 31 + : abcd & 64 ? + (abcd & 223) | 128 + : + (abcd & 95) | 64; + + + if (nbacksl) { + + nbacksl = (abcd & 32 ? nbacksl + 2 : nbacksl + 3) >> 1; + + while (--nbacksl) { + + if ( + INI_BOOLEANS[pair_idx][bool_idx][chr_idx_s++] != + _CONFINI_BACKSLASH_ + ) { + + goto next_bool; + + } + + } + + } + + if ( + (abcd & 128) || ( + (abcd & 64) && !INI_BOOLEANS[pair_idx][bool_idx][chr_idx_s] + ) + ) { + + continue; + + } + + if ( + abcd & 64 ? + INI_BOOLEANS[pair_idx][bool_idx][chr_idx_s] != _CONFINI_COLLAPSED_ || + !INI_BOOLEANS[pair_idx][bool_idx][chr_idx_s + 1] + : + _CONFINI_CHR_CASEFOLD_(ini_string[chr_idx_i]) != + _CONFINI_CHR_CASEFOLD_(INI_BOOLEANS[pair_idx][bool_idx][chr_idx_s]) + ) { + + goto next_bool; + + } + + chr_idx_s++; + + } while (ini_string[chr_idx_i++]); + + return (int) bool_idx; + + + /* \ /\ + \ */ next_bool: /* \ + \/ ______________________ \ */ + + + if ((bool_idx ^= 1)) { + + goto pair_match; + + } + + } + + return when_fail; + +} + + + + /* LINKS - In case you don't have `#include <stdlib.h>` in your source */ + + +/** + + @alias{ini_get_int} + Pointer to + [`atoi()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atoi) + @alias{ini_get_lint} + Pointer to + [`atol()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atol) + @alias{ini_get_llint} + Pointer to + [`atoll()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atoll) + @alias{ini_get_double} + Pointer to + [`atof()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atof) + +**/ + +int (* const ini_get_int) (const char * ini_string) = atoi; + +long int (* const ini_get_lint) (const char * ini_string) = atol; + +long long int (* const ini_get_llint) (const char * ini_string) = atoll; + +double (* const ini_get_double) (const char * ini_string) = atof; + +/* Legacy support -- please **do not use this**! */ +#ifdef ini_get_float +#undef ini_get_float +#endif + +/** + + @brief Legacy support for parsing a `double` data type -- please _do + not use this function_: use `ini_get_double()` instead + @deprecated Deprecated since version 1.12.0 (it will be removed in version + 2.0.0) -- please use #ini_get_double() instead + @param ini_string The string to parse as a `double` + +**/ +double (* const ini_get_float) (const char * ini_string) = atof; + + + + /* GLOBAL VARIABLES */ + + +bool INI_GLOBAL_LOWERCASE_MODE = _CONFINI_FALSE_; + +char * INI_GLOBAL_IMPLICIT_VALUE = (char *) 0; + +size_t INI_GLOBAL_IMPLICIT_V_LEN = 0; + + + +/** @endfnlist **/ + + +/* EOF */ + diff --git a/3rdparty/libconfini/confini.h b/3rdparty/libconfini/confini.h new file mode 100644 index 0000000000..b4498e346b --- /dev/null +++ b/3rdparty/libconfini/confini.h @@ -0,0 +1,589 @@ +/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ + +/** + + @file confini.h + @brief libconfini header + @author Stefano Gioffré + @copyright GNU General Public License, version 3 or any later version + @version 1.16.3 + @date 2016-2021 + @see https://madmurphy.github.io/libconfini + +**/ + + +#ifndef __CONFINI_H__ +#define __CONFINI_H__ + + + +#include <stdio.h> +#include <stdbool.h> +#include <stdint.h> + + + +#ifdef __cplusplus +extern "C" { +#endif + + + +/* PRIVATE (HEADER-SCOPED) MACROS */ + + +#ifdef _LIBCONFINI_NOCCWARN_ +#define _LIBCONFINI_DO_PRAGMA_(PBODY) +#define _LIBCONFINI_WARNING_(MSG) +#elif defined(_MSC_VER) +#define _LIBCONFINI_DO_PRAGMA_(PBODY) __pragma(PBODY) +#define _LIBCONFINI_WARNING_(MSG) _LIBCONFINI_DO_PRAGMA_(message("*WARNING*: " MSG)) +#else +#define _LIBCONFINI_DO_PRAGMA_(PBODY) _Pragma(#PBODY) +#define _LIBCONFINI_WARNING_(MSG) _LIBCONFINI_DO_PRAGMA_(GCC warning #MSG) +#endif + +#define __INIFORMAT_TABLE_CB_FIELDS__(NAME, OFFSET, SIZE, DEFVAL) \ + unsigned char NAME:SIZE; +#define __INIFORMAT_TABLE_CB_DEFAULT__(NAME, OFFSET, SIZE, DEFVAL) DEFVAL, +#define __INIFORMAT_TABLE_CB_ZERO__(NAME, OFFSET, SIZE, DEFVAL) 0, +#define _LIBCONFINI_INIFORMAT_TYPE_ \ + struct IniFormat { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_FIELDS__) } +#define _LIBCONFINI_DEFAULT_FORMAT_ \ + { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_DEFAULT__) } +#define _LIBCONFINI_UNIXLIKE_FORMAT_ \ + { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_ZERO__) } + + + +/* PUBLIC MACROS */ + + +/** + @brief Call a user-given macro (that accepts four arguments) for each row + of the table +**/ +/* + NOTE: The following table and the order of its rows **define** (and link + together) both the #IniFormat and #IniFormatNum data types declared in this + header +*/ +#define INIFORMAT_TABLE_AS(_____) /* IniFormat table *\ + + NAME BIT SIZE DEFAULT + */\ + _____( delimiter_symbol, 0, 7, INI_EQUALS ) \ + _____( case_sensitive, 7, 1, false )/* + */\ + _____( semicolon_marker, 8, 2, INI_DISABLED_OR_COMMENT ) \ + _____( hash_marker, 10, 2, INI_DISABLED_OR_COMMENT ) \ + _____( section_paths, 12, 2, INI_ABSOLUTE_AND_RELATIVE ) \ + _____( multiline_nodes, 14, 2, INI_MULTILINE_EVERYWHERE )/* + */\ + _____( no_single_quotes, 16, 1, false ) \ + _____( no_double_quotes, 17, 1, false ) \ + _____( no_spaces_in_names, 18, 1, false ) \ + _____( implicit_is_not_empty, 19, 1, false ) \ + _____( do_not_collapse_values, 20, 1, false ) \ + _____( preserve_empty_quotes, 21, 1, false ) \ + _____( disabled_after_space, 22, 1, false ) \ + _____( disabled_can_be_implicit, 23, 1, false ) + + + +/** + @brief Check whether a format does _not_ support escape sequences +**/ +#define INIFORMAT_HAS_NO_ESC(FORMAT) \ + (FORMAT.multiline_nodes == INI_NO_MULTILINE && \ + FORMAT.no_double_quotes && FORMAT.no_single_quotes) + + +/** + @brief Check whether a given `char *` data type points to the global + variable #INI_GLOBAL_IMPLICIT_VALUE or to any fragment of it +**/ +#define INI_IS_IMPLICIT_SUBSTR(CHAR_PTR) \ + (CHAR_PTR >= INI_GLOBAL_IMPLICIT_VALUE && CHAR_PTR <= \ + INI_GLOBAL_IMPLICIT_VALUE + INI_GLOBAL_IMPLICIT_V_LEN) + + + +/* PUBLIC TYPEDEFS */ + + +/** + @brief 24-bit bitfield representing the format of an INI file (INI + dialect) +**/ +typedef _LIBCONFINI_INIFORMAT_TYPE_ IniFormat; + + +/** + @brief Global statistics about an INI file +**/ +typedef struct IniStatistics { + const IniFormat format; + const size_t bytes; + const size_t members; +} IniStatistics; + + +/** + @brief Dispatch of a single INI node +**/ +typedef struct IniDispatch { + const IniFormat format; + uint_least8_t type; + char * data; + char * value; + const char * append_to; + size_t d_len; + size_t v_len; + size_t at_len; + size_t dispatch_id; +} IniDispatch; + + +/** + @brief The unique ID of an INI format (24-bit maximum) +**/ +typedef uint_least32_t IniFormatNum; + + +/** + @brief Callback function for handling an #IniStatistics structure +**/ +typedef int (* IniStatsHandler) ( + IniStatistics * statistics, + void * user_data +); + + +/** + @brief Callback function for handling an #IniDispatch structure +**/ +typedef int (* IniDispHandler) ( + IniDispatch * dispatch, + void * user_data +); + + +/** + @brief Callback function for handling an INI string belonging to a + sequence of INI strings +**/ +typedef int (* IniStrHandler) ( + char * ini_string, + size_t string_length, + size_t string_num, + IniFormat format, + void * user_data +); + + +/** + @brief Callback function for handling a selected fragment of an INI string +**/ +typedef int (* IniSubstrHandler) ( + const char * ini_string, + size_t fragm_offset, + size_t fragm_length, + size_t fragm_num, + IniFormat format, + void * user_data +); + + + +/* PUBLIC FUNCTIONS */ + + +extern int strip_ini_cache ( + register char * const ini_source, + const size_t ini_length, + const IniFormat format, + const IniStatsHandler f_init, + const IniDispHandler f_foreach, + void * const user_data +); + + +extern int load_ini_file ( + FILE * const ini_file, + const IniFormat format, + const IniStatsHandler f_init, + const IniDispHandler f_foreach, + void * const user_data +); + + +extern int load_ini_path ( + const char * const path, + const IniFormat format, + const IniStatsHandler f_init, + const IniDispHandler f_foreach, + void * const user_data +); + + +extern bool ini_string_match_ss ( + const char * const simple_string_a, + const char * const simple_string_b, + const IniFormat format +); + + +extern bool ini_string_match_si ( + const char * const simple_string, + const char * const ini_string, + const IniFormat format +); + + +extern bool ini_string_match_ii ( + const char * const ini_string_a, + const char * const ini_string_b, + const IniFormat format +); + + +extern bool ini_array_match ( + const char * const ini_string_a, + const char * const ini_string_b, + const char delimiter, + const IniFormat format +); + + +extern size_t ini_unquote ( + char * const ini_string, + const IniFormat format +); + + +extern size_t ini_string_parse ( + char * const ini_string, + const IniFormat format +); + + +extern size_t ini_array_get_length ( + const char * const ini_string, + const char delimiter, + const IniFormat format +); + + +extern int ini_array_foreach ( + const char * const ini_string, + const char delimiter, + const IniFormat format, + const IniSubstrHandler f_foreach, + void * const user_data +); + + +extern size_t ini_array_shift ( + const char ** const ini_strptr, + const char delimiter, + const IniFormat format +); + + +extern size_t ini_array_collapse ( + char * const ini_string, + const char delimiter, + const IniFormat format +); + + +extern char * ini_array_break ( + char * const ini_string, + const char delimiter, + const IniFormat format +); + + +extern char * ini_array_release ( + char ** const ini_strptr, + const char delimiter, + const IniFormat format +); + + +extern int ini_array_split ( + char * const ini_string, + const char delimiter, + const IniFormat format, + const IniStrHandler f_foreach, + void * const user_data +); + + +extern void ini_global_set_lowercase_mode ( + const bool lowercase +); +#define ini_global_set_lowercase_mode \ + _LIBCONFINI_WARNING_("deprecated function `ini_global_set_lowercase_mode()`") \ + ini_global_set_lowercase_mode + + +extern void ini_global_set_implicit_value ( + char * const implicit_value, + const size_t implicit_v_len +); + + +extern IniFormatNum ini_fton ( + const IniFormat format +); + + +extern IniFormat ini_ntof ( + const IniFormatNum format_id +); + + +extern int ini_get_bool ( + const char * const simple_string, + const int when_fail +); + + +extern int ini_get_bool_i ( + const char * const ini_string, + const int when_fail, + const IniFormat format +); + + + +/* PUBLIC LINKS */ + + +extern int (* const ini_get_int) ( + const char * ini_string +); + + +extern long int (* const ini_get_lint) ( + const char * ini_string +); + + +extern long long int (* const ini_get_llint) ( + const char * ini_string +); + + +extern double (* const ini_get_double) ( + const char * ini_string +); + + +/* + Legacy support -- please **do not use these functions**! +*/ +#define ini_get_float \ + _LIBCONFINI_WARNING_("function `ini_get_float()` is deprecated for parsing a `double` data type; use `ini_get_double()` instead") \ + ini_get_double + + + +/* PUBLIC CONSTANTS AND VARIABLES */ + + +/** + @brief Error mask (flags not present in user-generated interruptions) +**/ +#define CONFINI_ERROR 252 + + +/** + @brief Error codes +**/ +enum ConfiniInterruptNo { + CONFINI_SUCCESS = 0, /**< There have been no interruptions, everything + went well [value=0] **/ + CONFINI_IINTR = 1, /**< Interrupted by the user during `f_init()` + [value=1] **/ + CONFINI_FEINTR = 2, /**< Interrupted by the user during `f_foreach()` + [value=2] **/ + CONFINI_ENOENT = 4, /**< File inaccessible [value=4] **/ + CONFINI_ENOMEM = 5, /**< Error allocating virtual memory [value=5] **/ + CONFINI_EIO = 6, /**< Error reading the file [value=6] **/ + CONFINI_EOOR = 7, /**< Out-of-range error: callbacks are more than + expected [value=7] **/ + CONFINI_EBADF = 8, /**< The stream specified is not a seekable stream + [value=8] **/ + CONFINI_EFBIG = 9, /**< File too large [value=9] **/ + CONFINI_EROADDR = 10 /**< Address is read-only [value=10] **/ +}; + + +/** + @brief Disabled flag (i.e. third bit, present only in non-active node + types) +**/ +#define INI_DISABLED_FLAG 4 + + +/** + @brief INI node types and possible values of #IniDispatch::type + + See also #INI_DISABLED_FLAG. +**/ +enum IniNodeType { + INI_UNKNOWN = 0, /**< This is a node impossible to categorize + [value=0] **/ + INI_VALUE = 1, /**< Not used by **libconfini** (values are + dispatched together with keys) -- but + available for user's implementations + [value=1] **/ + INI_KEY = 2, /**< This is a key [value=2] **/ + INI_SECTION = 3, /**< This is a section path [value=3] **/ + INI_COMMENT = 4, /**< This is a comment [value=4] **/ + INI_INLINE_COMMENT = 5, /**< This is an inline comment [value=5] **/ + INI_DISABLED_KEY = 6, /**< This is a disabled key [value=6] **/ + INI_DISABLED_SECTION = 7 /**< This is a disabled section path + [value=7] **/ +}; + + +/** + @brief Common array and key-value delimiters (but a delimiter may also be + any other ASCII character not present in this list) +**/ +enum IniDelimiters { + INI_ANY_SPACE = 0, /**< In multi-line INIs: + `/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`, in + non-multi-line INIs: `/[\t \v\f])+/` **/ + INI_EQUALS = '=', /**< Equals character (`=`) **/ + INI_COLON = ':', /**< Colon character (`:`) **/ + INI_DOT = '.', /**< Dot character (`.`) **/ + INI_COMMA = ',' /**< Comma character (`,`) **/ +}; + + +/** + @brief Possible values of #IniFormat::semicolon_marker and + #IniFormat::hash_marker (i.e., meaning of `/\s+;/` and `/\s+#/` in + respect to a format) +**/ +enum IniCommentMarker { + INI_DISABLED_OR_COMMENT = 0, /**< This marker opens a comment or a + disabled entry **/ + INI_ONLY_COMMENT = 1, /**< This marker opens a comment **/ + INI_IGNORE = 2, /**< This marker opens a comment that has + been marked for deletion and must not + be dispatched or counted **/ + INI_IS_NOT_A_MARKER = 3 /**< This is not a marker at all, but a + normal character instead **/ +}; + + +/** + @brief Possible values of #IniFormat::section_paths +**/ +enum IniSectionPaths { + INI_ABSOLUTE_AND_RELATIVE = 0, /**< Section paths starting with a dot + express nesting to the current parent, + to root otherwise **/ + INI_ABSOLUTE_ONLY = 1, /**< Section paths starting with a dot will + be cleaned of their leading dot and + appended to root **/ + INI_ONE_LEVEL_ONLY = 2, /**< Format supports sections, but the dot + does not express nesting and is not a + meta-character **/ + INI_NO_SECTIONS = 3 /**< Format does *not* support sections -- + `/\[[^\]]*\]/g`, if any, will be + treated as keys! **/ +}; + + +/** + @brief Possible values of #IniFormat::multiline_nodes +**/ +enum IniMultiline { + INI_MULTILINE_EVERYWHERE = 0, /**< Comments, section paths and keys + -- disabled or not -- are allowed + to be multi-line **/ + INI_BUT_COMMENTS = 1, /**< Only section paths and keys -- + disabled or not -- are allowed to + be multi-line **/ + INI_BUT_DISABLED_AND_COMMENTS = 2, /**< Only active section paths and + active keys are allowed to be + multi-line **/ + INI_NO_MULTILINE = 3 /**< Multi-line escape sequences are + disabled **/ +}; + + +/** + @brief A model format for standard INI files +**/ +static const IniFormat INI_DEFAULT_FORMAT = _LIBCONFINI_DEFAULT_FORMAT_; + + +/** + @brief A model format for Unix-like .conf files (where space characters + are delimiters between keys and values) +**/ +/* All fields are set to `0` here. */ +static const IniFormat INI_UNIXLIKE_FORMAT = _LIBCONFINI_UNIXLIKE_FORMAT_; + + +/** + + @brief If set to `true`, key and section names in case-insensitive + INI formats will be dispatched lowercase, verbatim + otherwise (default value: `false`) + @deprecated Deprecated since version 1.15.0 (it will be removed in + version 2.0.0) +**/ +extern bool INI_GLOBAL_LOWERCASE_MODE; +#define INI_GLOBAL_LOWERCASE_MODE \ + _LIBCONFINI_WARNING_("global variable `INI_GLOBAL_LOWERCASE_MODE` is deprecated") \ + INI_GLOBAL_LOWERCASE_MODE + + +/** + @brief Value to be assigned to implicit keys (default value: `NULL`) +**/ +extern char * INI_GLOBAL_IMPLICIT_VALUE; + + +/** + @brief Length of the value assigned to implicit keys (default value: `0`) +**/ +extern size_t INI_GLOBAL_IMPLICIT_V_LEN; + + + +/* CLEAN THE PRIVATE ENVIRONMENT */ + + +#undef _LIBCONFINI_UNIXLIKE_FORMAT_ +#undef _LIBCONFINI_DEFAULT_FORMAT_ +#undef _LIBCONFINI_INIFORMAT_TYPE_ +#undef __INIFORMAT_TABLE_CB_ZERO__ +#undef __INIFORMAT_TABLE_CB_DEFAULT__ +#undef __INIFORMAT_TABLE_CB_FIELDS__ + + + +/* END OF `__CONFINI_H__` */ + + +#ifdef __cplusplus +} +#endif + + +#endif + + +/* EOF */ + diff --git a/cmake/OS_Detection.cmake b/cmake/OS_Detection.cmake index b7c014f4b6..1740a9a0fe 100644 --- a/cmake/OS_Detection.cmake +++ b/cmake/OS_Detection.cmake @@ -151,5 +151,6 @@ if(WIN32) include_directories(3rdparty/uthash/src/) include_directories(3rdparty/wepoll/) + include_directories(3rdparty/libconfini/) #include_directories(libdap-chain-net-srv-vpn/) endif() diff --git a/dap-sdk/core/CMakeLists.txt b/dap-sdk/core/CMakeLists.txt index 5956ea5531..37dac848db 100755 --- a/dap-sdk/core/CMakeLists.txt +++ b/dap-sdk/core/CMakeLists.txt @@ -21,18 +21,20 @@ if(WIN32) src/*.c src/etc/*.c src/rpmalloc/*.c + ../../3rdparty/libconfini/*.c ) file(GLOB CORE_HEADERS - include/*.h + include/*.h + ../../3rdparty/libconfini/*.h ) endif() add_library(${PROJECT_NAME} STATIC ${CORE_SRCS} ${CORE_HEADERS}) target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../3rdparty/uthash/src) -#if (WIN32) -# target_include_directories(${PROJECT_NAME} PRIVATE ../../3rdparty/wepoll) -#endif() +if (WIN32) + target_include_directories(${PROJECT_NAME} PRIVATE ../../3rdparty/libconfini) +endif() #This paths will be used by project-dependent project libraries target_include_directories(${PROJECT_NAME} INTERFACE include/ src/rpmalloc/) diff --git a/dap-sdk/net/server/notify_server/include/dap_notify_srv.h b/dap-sdk/net/server/notify_server/include/dap_notify_srv.h index b6ace1bd8e..dadd0220f2 100644 --- a/dap-sdk/net/server/notify_server/include/dap_notify_srv.h +++ b/dap-sdk/net/server/notify_server/include/dap_notify_srv.h @@ -26,4 +26,5 @@ int dap_notify_server_init(); void dap_notify_server_deinit(); int dap_notify_server_send_f_inter(uint32_t a_worker_id, const char * a_format,...); +int dap_notify_server_send_mt(const char * a_data); int dap_notify_server_send_f_mt(const char * a_format,...); diff --git a/dap-sdk/net/server/notify_server/src/dap_notify_srv.c b/dap-sdk/net/server/notify_server/src/dap_notify_srv.c index 6bfda796f7..0a2474b3dc 100644 --- a/dap-sdk/net/server/notify_server/src/dap_notify_srv.c +++ b/dap-sdk/net/server/notify_server/src/dap_notify_srv.c @@ -132,6 +132,19 @@ int dap_notify_server_send_f_inter(uint32_t a_worker_id, const char * a_format,. return dap_events_socket_queue_ptr_send_to_input(l_input,l_str); } +/** + * @brief dap_notify_server_send_fmt_mt + * @param a_format + * @return + */ +int dap_notify_server_send_mt(const char *a_data) +{ + if(!s_notify_server_queue) // If not initialized - nothing to notify + return 0; + return dap_events_socket_queue_ptr_send(s_notify_server_queue, dap_strdup(a_data)); +} + + /** * @brief dap_notify_server_send_fmt_mt * @param a_format diff --git a/modules/chain/dap_chain_ledger.c b/modules/chain/dap_chain_ledger.c index a5e276d477..07c1616ed5 100644 --- a/modules/chain/dap_chain_ledger.c +++ b/modules/chain/dap_chain_ledger.c @@ -56,6 +56,9 @@ #include "dap_chain_mempool.h" #include "dap_chain_global_db.h" #include "dap_chain_ledger.h" +#include "json-c/json.h" +#include "json-c/json_object.h" +#include "dap_notify_srv.h" #define LOG_TAG "dap_chain_ledger" @@ -217,6 +220,9 @@ static size_t s_treshold_txs_max = 10000; static bool s_debug_more = false; static bool s_token_supply_limit_disable = false; +struct json_object *wallet_info_json_collect(dap_ledger_t *a_ledger, dap_ledger_wallet_balance_t* a_bal); +static void wallet_info_notify(); + /** * @brief dap_chain_ledger_init * current function version set s_debug_more parameter, if it define in config, and returns 0 @@ -290,6 +296,32 @@ void dap_chain_ledger_load_end(dap_ledger_t *a_ledger) } +struct json_object *wallet_info_json_collect(dap_ledger_t *a_ledger, dap_ledger_wallet_balance_t *a_bal) { + struct json_object *l_json = json_object_new_object(); + json_object_object_add(l_json, "class", json_object_new_string("Wallet")); + struct json_object *l_network = json_object_new_object(); + json_object_object_add(l_network, "name", json_object_new_string(a_ledger->net_name)); + char *pos = strrchr(a_bal->key, ' '); + if (pos) { + char *l_addr_str = DAP_NEW_S_SIZE(char, pos - a_bal->key + 1); + memcpy(l_addr_str, a_bal->key, pos - a_bal->key); + json_object_object_add(l_network, "address", json_object_new_string(l_addr_str)); + } else { + json_object_object_add(l_network, "address", json_object_new_string("Unknown")); + } + struct json_object *l_token = json_object_new_object(); + json_object_object_add(l_token, "name", json_object_new_string(a_bal->token_ticker)); + char *l_balance_coins = dap_chain_balance_to_coins(a_bal->balance); + char *l_balance_datoshi = dap_chain_balance_print(a_bal->balance); + json_object_object_add(l_token, "full_balance", json_object_new_string(l_balance_coins)); + json_object_object_add(l_token, "datoshi", json_object_new_string(l_balance_datoshi)); + DAP_DELETE(l_balance_coins); + DAP_DELETE(l_balance_datoshi); + json_object_object_add(l_network, "tokens", l_token); + json_object_object_add(l_json, "networks", l_network); + return l_json; +} + /** * @brief dap_chain_ledger_token_check * @param a_ledger @@ -1151,6 +1183,10 @@ void dap_chain_ledger_load_cache(dap_ledger_t *a_ledger) l_balance_item->balance = *(uint128_t *)l_objs[i].value; HASH_ADD_KEYPTR(hh, l_ledger_pvt->balance_accounts, l_balance_item->key, strlen(l_balance_item->key), l_balance_item); + /* notify dashboard */ + struct json_object *l_json = wallet_info_json_collect(a_ledger, l_balance_item); + dap_notify_server_send_mt(json_object_get_string(l_json)); + json_object_put(l_json); } dap_chain_global_db_objs_delete(l_objs, l_objs_count); DAP_DELETE(l_gdb_group); @@ -2176,6 +2212,10 @@ static int s_balance_cache_update(dap_ledger_t *a_ledger, dap_ledger_wallet_bala return -1; } DAP_DELETE(l_gdb_group); + /* Notify dashboard */ + struct json_object *l_json = wallet_info_json_collect(a_ledger, a_balance); + dap_notify_server_send_mt(json_object_get_string(l_json)); + json_object_put(l_json); return 0; } diff --git a/modules/net/dap_chain_net.c b/modules/net/dap_chain_net.c index c67902413a..2c3c67a903 100644 --- a/modules/net/dap_chain_net.c +++ b/modules/net/dap_chain_net.c @@ -101,6 +101,9 @@ #include "dap_chain_node_dns_client.h" #include "dap_module.h" +#include "json-c/json.h" +#include "json-c/json_object.h" + #include <stdio.h> #include <sys/types.h> #include <dirent.h> @@ -239,8 +242,8 @@ static const dap_chain_node_client_callbacks_t s_node_link_callbacks={ static bool s_net_states_proc(dap_proc_thread_t *a_thread, void *a_arg); // Notify about net states -static void s_net_states_notify(dap_chain_net_t * l_net ); -static void s_net_links_notify(dap_chain_net_t * a_net ); +struct json_object *net_states_json_collect(dap_chain_net_t * l_net); +static void s_net_states_notify(dap_chain_net_t * l_net); // Prepare link success/error endpoints static void s_net_state_link_prepare_success(dap_worker_t * a_worker,dap_chain_node_info_t * a_node_info, void * a_arg); @@ -598,14 +601,15 @@ static void s_net_state_link_replace_error(dap_worker_t *a_worker, dap_chain_nod inet_ntop(AF_INET, &a_node_info->hdr.ext_addr_v4, l_node_addr_str, sizeof (a_node_info->hdr.ext_addr_v4)); log_it(L_WARNING,"Link " NODE_ADDR_FP_STR " (%s) replace error with code %d", NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address), l_node_addr_str,a_errno ); - dap_notify_server_send_f_mt("{" - "\"class\":\"NetLinkReplaceError\"," - "\"net_id\":\"0x%016" DAP_UINT64_FORMAT_X "\"," - "\"cell_id\":\"0x%016"DAP_UINT64_FORMAT_X"\"," - "\"address\":\""NODE_ADDR_FP_STR"\"," - "\"error\": %d" - "}", l_net->pub.id.uint64, a_node_info->hdr.cell_id.uint64, - NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address), a_errno); + struct json_object *l_json = net_states_json_collect(l_net); + char l_err_str[128] = { }; + dap_snprintf(l_err_str, sizeof(l_err_str) + , "Link " NODE_ADDR_FP_STR " [%s] replace errno %d" + , NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address), l_node_addr_str, a_errno); + json_object_object_add(l_json, "errorMessage", json_object_new_string(l_err_str)); + dap_notify_server_send_mt(json_object_get_string(l_json)); + json_object_put(l_json); + DAP_DELETE(a_node_info); dap_chain_node_info_t *l_link_node_info = NULL; for (int i = 0; i < 1000; i++) { @@ -659,14 +663,14 @@ static void s_net_state_link_replace_success(dap_worker_t *a_worker, dap_chain_n pthread_rwlock_wrlock(&l_net_pvt->rwlock); l_net_pvt->net_links = dap_list_append(l_net_pvt->net_links, l_new_link); pthread_rwlock_unlock(&l_net_pvt->rwlock); - - dap_notify_server_send_f_mt("{" - "\"class\":\"NetLinkReplaceSuccess\"," - "\"net_id\":\"0x%016" DAP_UINT64_FORMAT_X "\"," - "\"cell_id\":\"0x%016"DAP_UINT64_FORMAT_X"\"," - "\"address\":\""NODE_ADDR_FP_STR"\"" - "}", l_net->pub.id.uint64, a_node_info->hdr.cell_id.uint64, - NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address)); + struct json_object *l_json = net_states_json_collect(l_net); + char l_err_str[128] = { }; + dap_snprintf(l_err_str, sizeof(l_err_str) + , "Link " NODE_ADDR_FP_STR " replace success" + , NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address)); + json_object_object_add(l_json, "errorMessage", json_object_new_string(l_err_str)); + dap_notify_server_send_mt(json_object_get_string(l_json)); + json_object_put(l_json); DAP_DELETE(l_dns_request); } @@ -694,7 +698,14 @@ static void s_node_link_callback_connected(dap_chain_node_client_t * a_node_clie pthread_rwlock_wrlock(&l_net_pvt->rwlock); l_net_pvt->links_connected_count++; a_node_client->is_connected = true; - s_net_links_notify(l_net); + struct json_object *l_json = net_states_json_collect(l_net); + char l_err_str[128] = { }; + dap_snprintf(l_err_str, sizeof(l_err_str) + , "Established connection with link " NODE_ADDR_FP_STR + , NODE_ADDR_FP_ARGS_S(a_node_client->info->hdr.address)); + json_object_object_add(l_json, "errorMessage", json_object_new_string(l_err_str)); + dap_notify_server_send_mt(json_object_get_string(l_json)); + json_object_put(l_json); if(l_net_pvt->state == NET_STATE_LINKS_CONNECTING ){ l_net_pvt->state = NET_STATE_LINKS_ESTABLISHED; dap_proc_queue_add_callback_inter(a_node_client->stream_worker->worker->proc_queue_input,s_net_states_proc,l_net ); @@ -785,15 +796,10 @@ static void s_node_link_callback_stage(dap_chain_node_client_t * a_node_client,d if( s_debug_more) log_it(L_INFO,"%s."NODE_ADDR_FP_STR" stage %s",l_net->pub.name,NODE_ADDR_FP_ARGS_S(a_node_client->remote_node_addr), dap_client_stage_str(a_stage)); - dap_notify_server_send_f_mt("{" - "\"class\":\"NetLinkStage\"," - "\"net_id\":\"0x%016" DAP_UINT64_FORMAT_X "\"," - "\"cell_id\":\"0x%016"DAP_UINT64_FORMAT_X"\"," - "\"address\":\""NODE_ADDR_FP_STR"\"," - "\"state\":\"%s\"" - "}", a_node_client->net->pub.id.uint64, a_node_client->info->hdr.cell_id.uint64, - NODE_ADDR_FP_ARGS_S(a_node_client->info->hdr.address), - dap_chain_node_client_state_to_str(a_node_client->state) ); + struct json_object *l_json = net_states_json_collect(l_net); + json_object_object_add(l_json, "errorMessage", json_object_new_string(" ")); + dap_notify_server_send_mt(json_object_get_string(l_json)); + json_object_put(l_json); } /** @@ -807,15 +813,16 @@ static void s_node_link_callback_error(dap_chain_node_client_t * a_node_client, dap_chain_net_t * l_net = (dap_chain_net_t *) a_arg; log_it(L_WARNING, "Can't establish link with %s."NODE_ADDR_FP_STR, l_net->pub.name, NODE_ADDR_FP_ARGS_S(a_node_client->remote_node_addr)); - dap_notify_server_send_f_mt("{" - "\"class\":\"NetLinkError\"," - "\"net_id\":\"0x%016" DAP_UINT64_FORMAT_X "\"," - "\"cell_id\":\"0x%016"DAP_UINT64_FORMAT_X"\"," - "\"address\":\""NODE_ADDR_FP_STR"\"," - "\"error\":\%d" - "}", a_node_client->net->pub.id.uint64, a_node_client->info->hdr.cell_id.uint64, - NODE_ADDR_FP_ARGS_S(a_node_client->info->hdr.address), - a_error); + struct json_object *l_json = net_states_json_collect(l_net); + char l_node_addr_str[INET_ADDRSTRLEN] = {}; + inet_ntop(AF_INET, &a_node_client->info->hdr.ext_addr_v4, l_node_addr_str, sizeof (a_node_client->info->hdr.ext_addr_v4)); + char l_err_str[128] = { }; + dap_snprintf(l_err_str, sizeof(l_err_str) + , "Link " NODE_ADDR_FP_STR " [%s] can't be established, errno %d" + , NODE_ADDR_FP_ARGS_S(a_node_client->info->hdr.address), l_node_addr_str, a_error); + json_object_object_add(l_json, "errorMessage", json_object_new_string(l_err_str)); + dap_notify_server_send_mt(json_object_get_string(l_json)); + json_object_put(l_json); } /** @@ -828,13 +835,10 @@ static void s_node_link_callback_delete(dap_chain_node_client_t * a_node_client, dap_chain_net_t * l_net = (dap_chain_net_t *) a_arg; dap_chain_net_pvt_t * l_net_pvt = PVT(l_net); if (!a_node_client->keep_connection) { - dap_notify_server_send_f_mt("{" - "\"class\":\"NetLinkDelete\"," - "\"net_id\":\"0x%016" DAP_UINT64_FORMAT_X "\"," - "\"cell_id\":\"0x%016"DAP_UINT64_FORMAT_X"\"," - "\"address\":\""NODE_ADDR_FP_STR"\"" - "}", a_node_client->net->pub.id.uint64, a_node_client->info->hdr.cell_id.uint64, - NODE_ADDR_FP_ARGS_S(a_node_client->info->hdr.address)); + struct json_object *l_json = net_states_json_collect(l_net); + json_object_object_add(l_json, "errorMessage", json_object_new_string("Link deleted")); + dap_notify_server_send_mt(json_object_get_string(l_json)); + json_object_put(l_json); return; } else if (a_node_client->is_connected){ a_node_client->is_connected = false; @@ -852,13 +856,10 @@ static void s_node_link_callback_delete(dap_chain_node_client_t * a_node_client, } } pthread_rwlock_unlock(&l_net_pvt->rwlock); - dap_notify_server_send_f_mt("{" - "\"class\":\"NetLinkRestart\"," - "\"net_id\":\"0x%016" DAP_UINT64_FORMAT_X "\"," - "\"cell_id\":\"0x%016"DAP_UINT64_FORMAT_X"\"," - "\"address\":\""NODE_ADDR_FP_STR"\"" - "}", a_node_client->net->pub.id.uint64, a_node_client->info->hdr.cell_id.uint64, - NODE_ADDR_FP_ARGS_S(a_node_client->info->hdr.address)); + struct json_object *l_json = net_states_json_collect(l_net); + json_object_object_add(l_json, "errorMessage", json_object_new_string("Link restart")); + dap_notify_server_send_mt(json_object_get_string(l_json)); + json_object_put(l_json); // Then a_alient wiil be destroyed in a right way } @@ -900,13 +901,14 @@ static void s_net_state_link_prepare_success(dap_worker_t * a_worker,dap_chain_n dap_proc_queue_add_callback_inter( a_worker->proc_queue_input,s_net_states_proc,l_net ); } pthread_rwlock_unlock(&l_net_pvt->rwlock); - dap_notify_server_send_f_mt("{" - "\"class\":\"NetLinkPrepareSuccess\"," - "\"net_id\":\"0x%016" DAP_UINT64_FORMAT_X "\"," - "\"cell_id\":\"0x%016"DAP_UINT64_FORMAT_X"\"," - "\"address\":\""NODE_ADDR_FP_STR"\"" - "}", l_net->pub.id.uint64, a_node_info->hdr.cell_id.uint64, - NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address)); + struct json_object *l_json = net_states_json_collect(l_net); + char l_err_str[128] = { }; + dap_snprintf(l_err_str, sizeof(l_err_str) + , "Link " NODE_ADDR_FP_STR " prepared" + , NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address)); + json_object_object_add(l_json, "errorMessage", json_object_new_string(l_err_str)); + dap_notify_server_send_mt(json_object_get_string(l_json)); + json_object_put(l_json); DAP_DELETE(l_dns_request); } @@ -927,15 +929,14 @@ static void s_net_state_link_prepare_error(dap_worker_t * a_worker,dap_chain_nod log_it(L_WARNING,"Link " NODE_ADDR_FP_STR " (%s) prepare error with code %d", NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address), l_node_addr_str,a_errno ); - dap_notify_server_send_f_mt("{" - "\"class\":\"NetLinkPrepareError\"," - "\"net_id\":\"0x%016" DAP_UINT64_FORMAT_X "\"," - "\"cell_id\":\"0x%016"DAP_UINT64_FORMAT_X"\"," - "\"address\":\""NODE_ADDR_FP_STR"\"," - "\"error\": %d" - "}", l_net->pub.id.uint64, a_node_info->hdr.cell_id.uint64, - NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address),a_errno); - + struct json_object *l_json = net_states_json_collect(l_net); + char l_err_str[128] = { }; + dap_snprintf(l_err_str, sizeof(l_err_str) + , "Link " NODE_ADDR_FP_STR " [%s] can't be prepared, errno %d" + , NODE_ADDR_FP_ARGS_S(a_node_info->hdr.address), l_node_addr_str, a_errno); + json_object_object_add(l_json, "errorMessage", json_object_new_string(l_err_str)); + dap_notify_server_send_mt(json_object_get_string(l_json)); + json_object_put(l_json); pthread_rwlock_wrlock(&l_net_pvt->rwlock); if(l_net_pvt->links_dns_requests) l_net_pvt->links_dns_requests--; @@ -955,68 +956,30 @@ static void s_net_state_link_prepare_error(dap_worker_t * a_worker,dap_chain_nod DAP_DELETE(l_dns_request); } -/** - * @brief s_net_states_notify - * @param l_net - */ -static void s_net_states_notify(dap_chain_net_t * l_net ) -{ - dap_notify_server_send_f_mt("{" - "\"class\":\"NetStates\"," - "\"net_id\": \"0x%016" DAP_UINT64_FORMAT_X "\"," - "\"state\": \"%s\"," - "\"state_target\":\"%s\"" - "}", l_net->pub.id.uint64, - dap_chain_net_state_to_str( PVT(l_net)->state), - dap_chain_net_state_to_str(PVT(l_net)->state_target)); - +struct json_object *net_states_json_collect(dap_chain_net_t * l_net) { + struct json_object *l_json = json_object_new_object(); + json_object_object_add(l_json, "class" , json_object_new_string("NetStates")); + json_object_object_add(l_json, "name" , json_object_new_string((const char*)l_net->pub.name)); + json_object_object_add(l_json, "networkState" , json_object_new_string(dap_chain_net_state_to_str(PVT(l_net)->state))); + json_object_object_add(l_json, "targetState" , json_object_new_string(dap_chain_net_state_to_str(PVT(l_net)->state_target))); + json_object_object_add(l_json, "linksCount" , json_object_new_int(dap_list_length(PVT(l_net)->net_links))); + json_object_object_add(l_json, "activeLinksCount" , json_object_new_int(PVT(l_net)->links_connected_count)); + char l_node_addr_str[24] = {'\0'}; + dap_snprintf(l_node_addr_str, sizeof(l_node_addr_str), NODE_ADDR_FP_STR, NODE_ADDR_FPS_ARGS(PVT(l_net)->node_addr)); + json_object_object_add(l_json, "nodeAddress" , json_object_new_string(l_node_addr_str)); + return l_json; } /** - * @brief s_net_links_notify + * @brief s_net_states_notify * @param l_net */ -static void s_net_links_notify(dap_chain_net_t * a_net ) -{ - dap_chain_net_pvt_t * l_net_pvt = PVT(a_net); - dap_string_t * l_str_reply = dap_string_new("["); - - size_t i =0; - for (dap_list_t * l_item = l_net_pvt->net_links; l_item; l_item = l_item->next ) { - dap_chain_node_client_t * l_node_client = ((struct net_link *)l_item->data)->link; - - if(l_node_client){ - dap_chain_node_info_t * l_info = l_node_client->info; - char l_ext_addr_v4[INET_ADDRSTRLEN]={}; - char l_ext_addr_v6[INET6_ADDRSTRLEN]={}; - inet_ntop(AF_INET,&l_info->hdr.ext_addr_v4,l_ext_addr_v4,sizeof (l_info->hdr.ext_addr_v4)); - inet_ntop(AF_INET6,&l_info->hdr.ext_addr_v6,l_ext_addr_v6,sizeof (l_info->hdr.ext_addr_v6)); - - dap_string_append_printf(l_str_reply,"{" - "id:%zu," - "address:\""NODE_ADDR_FP_STR"\"," - "alias:\"%s\"," - "cell_id:0x%016"DAP_UINT64_FORMAT_X"," - "ext_ipv4:\"%s\"," - "ext_ipv6:\"%s\"," - "ext_port:%hu" - "state:\"%s\"" - "}", i,NODE_ADDR_FP_ARGS_S(l_info->hdr.address), l_info->hdr.alias, l_info->hdr.cell_id.uint64, - l_ext_addr_v4, l_ext_addr_v6,l_info->hdr.ext_port - , dap_chain_node_client_state_to_str(l_node_client->state) ); - } - i++; - } - - - dap_notify_server_send_f_mt("{" - "\"class\":\"NetLinks\"," - "\"net_id\":\"0x%016"DAP_UINT64_FORMAT_X"\"," - "\"links\":\"%s\"]" - "}", a_net->pub.id.uint64, - l_str_reply->str); - dap_string_free(l_str_reply,true); - +static void s_net_states_notify(dap_chain_net_t * l_net) +{ + struct json_object *l_json = net_states_json_collect(l_net); + json_object_object_add(l_json, "errorMessage", json_object_new_string(" ")); // regular notify has no error + dap_notify_server_send_mt(json_object_get_string(l_json)); + json_object_put(l_json); } /** -- GitLab